diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
new file mode 100644
index 00000000..331eba3d
--- /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, flake8, isortcheck, rstcheck]
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v2
+ with:
+ python-version: '3.10'
+ - 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: 5
+ matrix:
+ os: [ubuntu-latest]
+ python-version: [3.6, 3.7, 3.8, 3.9, '3.10']
+ 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
diff --git a/.gitignore b/.gitignore
index 4f3a167a..fc62191c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,12 @@
/freegames.egg-info/
.DS_Store
+
+
+.vscode/
+
+#unnecessary files
+*.mo
+*.pot
+
+
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/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 133bde98..32d87e5c 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.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
-.. 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
@@ -337,7 +334,7 @@ References
Free Python Games License
-------------------------
-Copyright 2017-2020 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/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/docs/Makefile b/docs/Makefile
index 359209f0..b7540d92 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -3,18 +3,42 @@
# You can set these variables from the command line.
SPHINXOPTS =
-SPHINXBUILD = python -msphinx
+SPHINXBUILD = python3 -msphinx
+SPHINXINTL = python3 -msphinx_intl
SPHINXPROJ = FreeGames
SOURCEDIR = .
BUILDDIR = _build
+LOCALES = $(patsubst locale/%,%,$(wildcard locale/*))
+
+MV = mv
+RM = rm -r
+
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-.PHONY: help Makefile
+.PHONY: help Makefile update-po
+
+update-po: gettext
+ $(SPHINXINTL) update -p "$(BUILDDIR)/gettext" $(0)
+
+translated:
+ @for locale in $(LOCALES); do \
+ SPHINXOPTS="-D language=$$locale"; \
+ BUILDDIR="$(BUILDDIR)/$$locale"; \
+ TARGETDIR="$(BUILDDIR)/$(TARGET)/$$locale"; \
+ $(SPHINXBUILD) -M "$(TARGET)" "$(SOURCEDIR)" "$$BUILDDIR" $$SPHINXOPTS; \
+ $(MV) "$$BUILDDIR/$(TARGET)" "$$TARGETDIR"; \
+ $(RM) "$$BUILDDIR"; \
+ echo "$$locale docs was moved to $$TARGETDIR directory"; \
+ done
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
- @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+ @if [ $@ != clean -a $@ != gettext ]; then \
+ TARGET=$@ make translated; \
+ fi
diff --git a/docs/_templates/gumroad.html b/docs/_templates/gumroad.html
index c9c9687e..2a0ad8c5 100644
--- a/docs/_templates/gumroad.html
+++ b/docs/_templates/gumroad.html
@@ -1,5 +1,5 @@
-
Donate
-If you or your organization uses Free Games, consider donating:
+{{ _('Donate') }}
+{{ _('If you or your organization uses Free Games, consider donating:') }}
- Donate to Free Python Games
+ {{ _('Donate to Free Python Games') }}
diff --git a/docs/conf.py b/docs/conf.py
index 0c339e44..eeae7b6e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -199,3 +199,7 @@
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
+
+# Gettext / i18n
+locale_dirs = ['locale/']
+gettext_compact = False
diff --git a/docs/development.rst b/docs/development.rst
index 9bebb26b..0e2bde2d 100644
--- a/docs/development.rst
+++ b/docs/development.rst
@@ -73,3 +73,13 @@ simply run::
The test argument to setup.py will download a minimal testing infrastructure
and run the tests.
+
+Translate
+----------
+
+Translation files are available in the locale/ directory, if you want to contribute a translation make changes to its content.
+
+if you want to translate to another language, you need to create the lang folder. To update the lang folder you need to use the command:
+
+ $ make update-po
+
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/locale/pt_BR/LC_MESSAGES/ant.po b/docs/locale/pt_BR/LC_MESSAGES/ant.po
new file mode 100644
index 00000000..10ce7d57
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/ant.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../ant.rst:2
+msgid "Ant"
+msgstr ""
+
+#: ../../ant.rst:4
+msgid "Ant, simple animation demo."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/api.po b/docs/locale/pt_BR/LC_MESSAGES/api.po
new file mode 100644
index 00000000..5d143791
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/api.po
@@ -0,0 +1,121 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../api.rst:2
+msgid "Free Python Games API Reference"
+msgstr ""
+
+#: ../../api.rst:4
+msgid ""
+":doc:`Free Python Games ` includes a few helpful utilities. The "
+"best way to expose beginners to these functions is with Python's built-in"
+" help function. Learners should be able to understand and write the "
+"drawing functions themselves."
+msgstr ""
+
+#: ../../api.rst:13
+msgid "Drawing Functions"
+msgstr ""
+
+#: freegames.utils.line:1 of
+msgid "Draw line from `(a, b)` to `(x, y)`."
+msgstr ""
+
+#: freegames.utils.square:1 of
+msgid "Draw square at `(x, y)` with side length `size` and fill color `name`."
+msgstr ""
+
+#: freegames.utils.square:3 of
+msgid "The square is oriented so the bottom left corner is at (x, y)."
+msgstr ""
+
+#: ../../api.rst:20
+msgid "Helper Functions"
+msgstr ""
+
+#: freegames.utils.floor:1 of
+msgid "Floor of `value` given `size` and `offset`."
+msgstr ""
+
+#: freegames.utils.floor:3 of
+msgid "The floor function is best understood with a diagram of the number line::"
+msgstr ""
+
+#: freegames.utils.floor:8 of
+msgid ""
+"The number line shown has offset 200 denoted by the left-hand tick mark "
+"at -200 and size 100 denoted by the tick marks at -100, 0, 100, and 200. "
+"The floor of a value is the left-hand tick mark of the range where it "
+"lies. So for the points show above: ``floor(x)`` is -200, ``floor(y)`` is"
+" 0, and ``floor(z)`` is 100."
+msgstr ""
+
+#: freegames.utils.path:1 of
+msgid "Return full path to `filename` in freegames module."
+msgstr ""
+
+#: ../../api.rst:27
+msgid "Vectors"
+msgstr ""
+
+#: freegames.utils.vector:1 of
+msgid "Two-dimensional vector."
+msgstr ""
+
+#: freegames.utils.vector:3 of
+msgid "Vectors can be modified in-place."
+msgstr ""
+
+#: freegames.utils.vector.__init__:1 of
+msgid "Initialize vector with coordinates: x, y."
+msgstr ""
+
+#: freegames.utils.vector.__add__:1 of
+msgid "v.__add__(w) -> v + w"
+msgstr ""
+
+#: freegames.utils.vector.__mul__:1 of
+msgid "v.__mul__(w) -> v * w"
+msgstr ""
+
+#: freegames.utils.vector.copy:1 of
+msgid "Return copy of vector."
+msgstr ""
+
+#: freegames.utils.vector.move:1 of
+msgid "Move vector by other (in-place)."
+msgstr ""
+
+#: freegames.utils.vector.rotate:1 of
+msgid "Rotate vector counter-clockwise by angle (in-place)."
+msgstr ""
+
+#: freegames.utils.vector.scale:1 of
+msgid "Scale vector by other (in-place)."
+msgstr ""
+
+#: freegames.vector.x:1 of
+msgid "X-axis component of vector."
+msgstr ""
+
+#: freegames.vector.y:1 of
+msgid "Y-axis component of vector."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/bagels.po b/docs/locale/pt_BR/LC_MESSAGES/bagels.po
new file mode 100644
index 00000000..51c2afc1
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/bagels.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../bagels.rst:2
+msgid "Bagels"
+msgstr ""
+
+#: ../../bagels.rst:4
+msgid "Bagels, a number puzzle game."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/bounce.po b/docs/locale/pt_BR/LC_MESSAGES/bounce.po
new file mode 100644
index 00000000..c8d4fe54
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/bounce.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../bounce.rst:2
+msgid "Bounce"
+msgstr ""
+
+#: ../../bounce.rst:4
+msgid "Bounce, a simple animation demo."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/cannon.po b/docs/locale/pt_BR/LC_MESSAGES/cannon.po
new file mode 100644
index 00000000..21439876
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/cannon.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../cannon.rst:2
+msgid "Cannon"
+msgstr ""
+
+#: ../../cannon.rst:4
+msgid "Cannon, hitting targets with projectiles."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/connect.po b/docs/locale/pt_BR/LC_MESSAGES/connect.po
new file mode 100644
index 00000000..21a9ee18
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/connect.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../connect.rst:2
+msgid "Connect Four"
+msgstr ""
+
+#: ../../connect.rst:4
+msgid "Connect Four, two-player connection game."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/crypto.po b/docs/locale/pt_BR/LC_MESSAGES/crypto.po
new file mode 100644
index 00000000..5aa42d53
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/crypto.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../crypto.rst:2
+msgid "Crypto"
+msgstr ""
+
+#: ../../crypto.rst:4
+msgid "Crypto: tool for encrypting and decrypting messages."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/curriculum.po b/docs/locale/pt_BR/LC_MESSAGES/curriculum.po
new file mode 100644
index 00000000..5d0de036
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/curriculum.po
@@ -0,0 +1,505 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../curriculum.rst:2
+msgid "Free Python Games Curriculum"
+msgstr ""
+
+#: ../../curriculum.rst:4
+msgid ""
+"What follows are notes for a week-long curriculum developed for Maker "
+"Camp at The River Church Community. Maker Camp was a summer day camp for "
+"Middle School students in the California Bay Area."
+msgstr ""
+
+#: ../../curriculum.rst:8
+msgid "Each game has exercises at the top."
+msgstr ""
+
+#: ../../curriculum.rst:9
+msgid "Visualize code with http://pythontutor.com"
+msgstr ""
+
+#: ../../curriculum.rst:10
+msgid "Extras: $ python3 -m turtledemo"
+msgstr ""
+
+#: ../../curriculum.rst:14
+msgid "Day 1"
+msgstr ""
+
+#: ../../curriculum.rst:16
+msgid "What is a computer? Calculator + Clock."
+msgstr ""
+
+#: ../../curriculum.rst:17
+msgid "Discuss Alan Turing and Turing Machines."
+msgstr ""
+
+#: ../../curriculum.rst:18
+msgid "Discuss John Von Neumann and Von Neumann Machines."
+msgstr ""
+
+#: ../../curriculum.rst:19
+msgid "What is a program? Computer Recipe."
+msgstr ""
+
+#: ../../curriculum.rst:20
+msgid "Introduce Terminal, black background, give the computer commands."
+msgstr ""
+
+#: ../../curriculum.rst:21
+msgid "Introduce Python Shell, white background, write Python statements."
+msgstr ""
+
+#: ../../curriculum.rst:22
+msgid "Activity: connect and decorate computers."
+msgstr ""
+
+#: ../../curriculum.rst:23
+msgid "Problem solving: divide and conquer."
+msgstr ""
+
+#: ../../curriculum.rst:24
+msgid "Statements: assignment, if/elif/else, while, import"
+msgstr ""
+
+#: ../../curriculum.rst:25
+msgid "Data types: int, str"
+msgstr ""
+
+#: ../../curriculum.rst:26
+msgid "Functions: print, help, int"
+msgstr ""
+
+#: ../../curriculum.rst:27
+msgid "Modules: random"
+msgstr ""
+
+#: ../../curriculum.rst:30 ../../curriculum.rst:70 ../../curriculum.rst:115
+#: ../../curriculum.rst:157 ../../curriculum.rst:184
+msgid "Games"
+msgstr ""
+
+#: ../../curriculum.rst:32
+msgid ":doc:`guess`.py - Guess number within range."
+msgstr ""
+
+#: ../../curriculum.rst:34
+msgid "Explain: from random import randint"
+msgstr ""
+
+#: ../../curriculum.rst:35
+msgid "randint(...)"
+msgstr ""
+
+#: ../../curriculum.rst:36
+msgid "Variables"
+msgstr ""
+
+#: ../../curriculum.rst:37
+msgid "Equality"
+msgstr ""
+
+#: ../../curriculum.rst:38
+msgid "print(...)"
+msgstr ""
+
+#: ../../curriculum.rst:39
+msgid "Inequality"
+msgstr ""
+
+#: ../../curriculum.rst:40
+msgid "input(...)"
+msgstr ""
+
+#: ../../curriculum.rst:41
+msgid "int(...)"
+msgstr ""
+
+#: ../../curriculum.rst:42
+msgid "Increase range to 1,000 and guess the number."
+msgstr ""
+
+#: ../../curriculum.rst:43
+msgid "Discuss method of guessing. That's an algorithm!"
+msgstr ""
+
+#: ../../curriculum.rst:45
+msgid ":doc:`snake`.py - Classic arcade game."
+msgstr ""
+
+#: ../../curriculum.rst:48 ../../curriculum.rst:97 ../../curriculum.rst:139
+#: ../../curriculum.rst:166 ../../curriculum.rst:191
+msgid "Bible Story"
+msgstr ""
+
+#: ../../curriculum.rst:50
+msgid "Genesis 1 - God the Creator (Creation)"
+msgstr ""
+
+#: ../../curriculum.rst:52
+msgid "What did God do at the beginning?"
+msgstr ""
+
+#: ../../curriculum.rst:53
+msgid "What did God see in creation?"
+msgstr ""
+
+#: ../../curriculum.rst:54
+msgid "What is special about people?"
+msgstr ""
+
+#: ../../curriculum.rst:55
+msgid "Why do we create things?"
+msgstr ""
+
+#: ../../curriculum.rst:59
+msgid "Day 2"
+msgstr ""
+
+#: ../../curriculum.rst:61
+msgid "Activity: Blind partner obstacle course."
+msgstr ""
+
+#: ../../curriculum.rst:62
+msgid "Problem solving: brute-force."
+msgstr ""
+
+#: ../../curriculum.rst:63
+msgid "Famous Christian programmer: Donald Knuth"
+msgstr ""
+
+#: ../../curriculum.rst:64
+msgid "Statements: try/except, for, def"
+msgstr ""
+
+#: ../../curriculum.rst:65
+msgid "Data types: float, bool"
+msgstr ""
+
+#: ../../curriculum.rst:66
+msgid "Functions: type, dir, str, ord, chr"
+msgstr ""
+
+#: ../../curriculum.rst:67
+msgid "Modules: turtle"
+msgstr ""
+
+#: ../../curriculum.rst:72
+msgid ":doc:`crypto`.py - Encrypt, decrypt and decode messages."
+msgstr ""
+
+#: ../../curriculum.rst:74
+msgid "ord function and chr function"
+msgstr ""
+
+#: ../../curriculum.rst:75
+msgid "modulo operator"
+msgstr ""
+
+#: ../../curriculum.rst:76
+msgid "Write decode function"
+msgstr ""
+
+#: ../../curriculum.rst:77
+msgid "Encrypt numbers"
+msgstr ""
+
+#: ../../curriculum.rst:79
+msgid ":doc:`paint`.py - Draw shapes."
+msgstr ""
+
+#: ../../curriculum.rst:81
+msgid "Draw line"
+msgstr ""
+
+#: ../../curriculum.rst:82
+msgid "Draw square"
+msgstr ""
+
+#: ../../curriculum.rst:83
+msgid "for-statement, range function."
+msgstr ""
+
+#: ../../curriculum.rst:84
+msgid "Draw five-pointed star: forward(50); right(144) (x5)"
+msgstr ""
+
+#: ../../curriculum.rst:85
+msgid "Draw six-pointed star: forward(50); left(60); forward(50); right(120) (x6)"
+msgstr ""
+
+#: ../../curriculum.rst:86
+msgid "help(...)"
+msgstr ""
+
+#: ../../curriculum.rst:87
+msgid "undo(...)"
+msgstr ""
+
+#: ../../curriculum.rst:88
+msgid "def-statement, refactor code to star function"
+msgstr ""
+
+#: ../../curriculum.rst:89
+msgid "color('green'); color('blue', 'yellow')"
+msgstr ""
+
+#: ../../curriculum.rst:90
+msgid "begin_fill(); end_fill()"
+msgstr ""
+
+#: ../../curriculum.rst:91
+msgid "width function"
+msgstr ""
+
+#: ../../curriculum.rst:92
+msgid "Write polygon(sides, length) function"
+msgstr ""
+
+#: ../../curriculum.rst:94
+msgid ":doc:`flappy`.py - Flappy Bird inspired game."
+msgstr ""
+
+#: ../../curriculum.rst:99
+msgid "Genesis 6:5-22 - God the Engineer (Noah)"
+msgstr ""
+
+#: ../../curriculum.rst:101
+msgid "Why did God regret making people?"
+msgstr ""
+
+#: ../../curriculum.rst:102
+msgid "How was Noah different?"
+msgstr ""
+
+#: ../../curriculum.rst:103
+msgid "What was God's plan?"
+msgstr ""
+
+#: ../../curriculum.rst:104
+msgid "How are we washed today?"
+msgstr ""
+
+#: ../../curriculum.rst:108
+msgid "Day 3"
+msgstr ""
+
+#: ../../curriculum.rst:110
+msgid "Activity: Simon Says"
+msgstr ""
+
+#: ../../curriculum.rst:111
+msgid "Famous Christian programmer: Fred Brooks"
+msgstr ""
+
+#: ../../curriculum.rst:112
+msgid "Functions: onscreenclick, onkey, ontimer"
+msgstr ""
+
+#: ../../curriculum.rst:117
+msgid ":doc:`bagels`.py - Digit guessing puzzle."
+msgstr ""
+
+#: ../../curriculum.rst:118
+msgid "Animation"
+msgstr ""
+
+#: ../../curriculum.rst:120
+msgid "Draw arc: circle(100, 90)"
+msgstr ""
+
+#: ../../curriculum.rst:121
+msgid "flower(...)"
+msgstr ""
+
+#: ../../curriculum.rst:122
+msgid "Draw flower and rotate"
+msgstr ""
+
+#: ../../curriculum.rst:123
+msgid "ontimer(...)"
+msgstr ""
+
+#: ../../curriculum.rst:124
+msgid "hideturtle(); tracer(False); polygon(4, 200); update()"
+msgstr ""
+
+#: ../../curriculum.rst:126
+msgid ":doc:`tictactoe`.py - Tic-tac-toe."
+msgstr ""
+
+#: ../../curriculum.rst:128
+msgid "line(...)"
+msgstr ""
+
+#: ../../curriculum.rst:129
+msgid "grid(...)"
+msgstr ""
+
+#: ../../curriculum.rst:130
+msgid "drawx(...)"
+msgstr ""
+
+#: ../../curriculum.rst:131
+msgid "drawo(...)"
+msgstr ""
+
+#: ../../curriculum.rst:132
+msgid "floor(...)"
+msgstr ""
+
+#: ../../curriculum.rst:133
+msgid "onscreenclick(goto)"
+msgstr ""
+
+#: ../../curriculum.rst:135
+msgid ":doc:`simonsays`.py - Simon Says"
+msgstr ""
+
+#: ../../curriculum.rst:136
+msgid ":doc:`cannon`.py - Hitting targets with projectiles."
+msgstr ""
+
+#: ../../curriculum.rst:141
+msgid "Mark 1:1-18 - God the Programmer (\"fishers of people\")"
+msgstr ""
+
+#: ../../curriculum.rst:143
+msgid "What did Isaiah say would happen?"
+msgstr ""
+
+#: ../../curriculum.rst:144
+msgid "What did John the Baptist say would happen?"
+msgstr ""
+
+#: ../../curriculum.rst:145
+msgid "What did God say about Jesus? When?"
+msgstr ""
+
+#: ../../curriculum.rst:146
+msgid "How did Jesus give his disciples new jobs?"
+msgstr ""
+
+#: ../../curriculum.rst:150
+msgid "Day 4"
+msgstr ""
+
+#: ../../curriculum.rst:152
+msgid "Activity: Collage of concepts."
+msgstr ""
+
+#: ../../curriculum.rst:153
+msgid "Famous Christian programmer: Larry Wall"
+msgstr ""
+
+#: ../../curriculum.rst:154
+msgid "Data types: list, dict, vector"
+msgstr ""
+
+#: ../../curriculum.rst:159
+msgid ":doc:`bounce`.py - Simple animation demo."
+msgstr ""
+
+#: ../../curriculum.rst:160
+msgid ":doc:`pong`.py - Classic arcade game."
+msgstr ""
+
+#: ../../curriculum.rst:161
+msgid ":doc:`ant`.py - Simple animation demo."
+msgstr ""
+
+#: ../../curriculum.rst:162
+msgid ":doc:`tron`.py - Classic arcade game."
+msgstr ""
+
+#: ../../curriculum.rst:163
+msgid ":doc:`tiles`.py - Puzzle game of number shuffling."
+msgstr ""
+
+#: ../../curriculum.rst:168
+msgid "John 9:1-33 - God the Debugger (Blind Man and Jesus)"
+msgstr ""
+
+#: ../../curriculum.rst:170
+msgid "What does Jesus tell the disciples?"
+msgstr ""
+
+#: ../../curriculum.rst:171
+msgid "What does the man tell the Pharisees?"
+msgstr ""
+
+#: ../../curriculum.rst:172
+msgid "What does the man believe about Jesus?"
+msgstr ""
+
+#: ../../curriculum.rst:173
+msgid "What do you believe about Jesus?"
+msgstr ""
+
+#: ../../curriculum.rst:177
+msgid "Day 5"
+msgstr ""
+
+#: ../../curriculum.rst:179
+msgid "Activity: Make or modify your own game."
+msgstr ""
+
+#: ../../curriculum.rst:180
+msgid "Famous Christian programmer: Jon Skeet"
+msgstr ""
+
+#: ../../curriculum.rst:181
+msgid "Answer: What next?"
+msgstr ""
+
+#: ../../curriculum.rst:186
+msgid ":doc:`connect`.py - Connect Four"
+msgstr ""
+
+#: ../../curriculum.rst:187
+msgid ":doc:`memory`.py - Puzzle game of number pairs."
+msgstr ""
+
+#: ../../curriculum.rst:188
+msgid ":doc:`pacman`.py - Classic arcade game."
+msgstr ""
+
+#: ../../curriculum.rst:193
+msgid "Revelation 21 - God the Restorer (New Heaven and New Earth)"
+msgstr ""
+
+#: ../../curriculum.rst:195
+msgid "What does God make? When?"
+msgstr ""
+
+#: ../../curriculum.rst:196
+msgid "Who is the Lamb and the Bride?"
+msgstr ""
+
+#: ../../curriculum.rst:197
+msgid "What is special about the city?"
+msgstr ""
+
+#: ../../curriculum.rst:198
+msgid "How can we live in the Holy City?"
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/development.po b/docs/locale/pt_BR/LC_MESSAGES/development.po
new file mode 100644
index 00000000..6d077318
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/development.po
@@ -0,0 +1,185 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-10-12 16:44-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../development.rst:2
+msgid "Free Python Games Development"
+msgstr ""
+
+#: ../../development.rst:4
+msgid ""
+":doc:`Free Python Games ` development is lead by Grant Jenks "
+"."
+msgstr ""
+
+#: ../../development.rst:8
+msgid "Collaborators Welcome"
+msgstr ""
+
+#: ../../development.rst:10
+msgid "Search issues or open a new issue to start a discussion around a bug."
+msgstr ""
+
+#: ../../development.rst:11
+msgid "Fork the `GitHub repository`_ and make your changes in a new branch."
+msgstr ""
+
+#: ../../development.rst:12
+msgid "Write a test which shows the bug was fixed."
+msgstr ""
+
+#: ../../development.rst:13
+msgid ""
+"Send a pull request and message the development lead until its merged and"
+" published."
+msgstr ""
+
+#: ../../development.rst:19
+msgid "Requests for Contributions"
+msgstr ""
+
+#: ../../development.rst:21
+msgid "Simplifications to existing games."
+msgstr ""
+
+#: ../../development.rst:22
+msgid "Refactoring to simplify games."
+msgstr ""
+
+#: ../../development.rst:23
+msgid "Improved documentation."
+msgstr ""
+
+#: ../../development.rst:24
+msgid "Additional games. Requirements for new games:"
+msgstr ""
+
+#: ../../development.rst:26
+msgid "Fun to play."
+msgstr ""
+
+#: ../../development.rst:27
+msgid "Matching code style."
+msgstr ""
+
+#: ../../development.rst:28
+msgid "Limited Python feature set."
+msgstr ""
+
+#: ../../development.rst:29
+msgid "Short (less than 100 lines of code)."
+msgstr ""
+
+#: ../../development.rst:32
+msgid "Get the Code"
+msgstr ""
+
+#: ../../development.rst:34
+msgid ""
+":doc:`Free Python Games ` is actively developed in a `GitHub "
+"repository`_."
+msgstr ""
+
+#: ../../development.rst:37
+msgid "You can either clone the public repository::"
+msgstr ""
+
+#: ../../development.rst:41
+msgid ""
+"Download the `tarball `_::"
+msgstr ""
+
+#: ../../development.rst:45
+msgid ""
+"Or, download the `zipball `_::"
+msgstr ""
+
+#: ../../development.rst:50
+msgid "Installing Dependencies"
+msgstr ""
+
+#: ../../development.rst:52
+msgid ""
+"Install development dependencies with `pip `_::"
+msgstr ""
+
+#: ../../development.rst:56
+msgid ""
+"All packages for running tests and building documentation will be "
+"installed."
+msgstr ""
+
+#: ../../development.rst:59
+msgid "Testing"
+msgstr ""
+
+#: ../../development.rst:61
+msgid ""
+":doc:`Free Python Games ` currently tests against three versions "
+"of Python:"
+msgstr ""
+
+#: ../../development.rst:64
+msgid "CPython 3.4"
+msgstr ""
+
+#: ../../development.rst:65
+msgid "CPython 3.5"
+msgstr ""
+
+#: ../../development.rst:66
+msgid "CPython 3.6"
+msgstr ""
+
+#: ../../development.rst:68
+msgid ""
+"Testing uses `tox `_. If you don't want"
+" to install all the development requirements, then, after downloading, "
+"you can simply run::"
+msgstr ""
+
+#: ../../development.rst:74
+msgid ""
+"The test argument to setup.py will download a minimal testing "
+"infrastructure and run the tests."
+msgstr ""
+
+#: ../../development.rst:78
+msgid "Translate"
+msgstr ""
+
+#: ../../development.rst:80
+msgid ""
+"Translation files are available in the locale/ directory, if you want to "
+"contribute a translation make changes to its content."
+msgstr ""
+
+#: ../../development.rst:82
+msgid ""
+"if you want to translate to another language, you need to create the lang"
+" folder. To update the lang folder you need to use the command:"
+msgstr ""
+
+#: ../../development.rst:84
+msgid "$ make update-po"
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/fidget.po b/docs/locale/pt_BR/LC_MESSAGES/fidget.po
new file mode 100644
index 00000000..c3f289db
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/fidget.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../fidget.rst:2
+msgid "Fidget"
+msgstr ""
+
+#: ../../fidget.rst:4
+msgid "Fidget, inspired by fidget spinners."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/flappy.po b/docs/locale/pt_BR/LC_MESSAGES/flappy.po
new file mode 100644
index 00000000..e45bcaf2
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/flappy.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../flappy.rst:2
+msgid "Flappy"
+msgstr ""
+
+#: ../../flappy.rst:4
+msgid "Flappy, game inspired by Flappy Bird."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/give-gift-python.po b/docs/locale/pt_BR/LC_MESSAGES/give-gift-python.po
new file mode 100644
index 00000000..d0e75df4
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/give-gift-python.po
@@ -0,0 +1,212 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../give-gift-python.rst:2
+msgid "Give the Gift of Python"
+msgstr ""
+
+#: ../../give-gift-python.rst:4
+msgid "*Talk given at SF Python Holiday Party on December 5, 2018.*"
+msgstr ""
+
+#: ../../give-gift-python.rst:7
+msgid "Who am I?"
+msgstr ""
+
+#: ../../give-gift-python.rst:9
+msgid "Python programmer."
+msgstr ""
+
+#: ../../give-gift-python.rst:10
+msgid "Hundreds of hours of classroom instruction."
+msgstr ""
+
+#: ../../give-gift-python.rst:13
+msgid "What we need?"
+msgstr ""
+
+#: ../../give-gift-python.rst:15
+msgid "Interest!"
+msgstr ""
+
+#: ../../give-gift-python.rst:16
+msgid "Typing skills."
+msgstr ""
+
+#: ../../give-gift-python.rst:17
+msgid "Math: Arithmetic, Algebra, Geometry"
+msgstr ""
+
+#: ../../give-gift-python.rst:20
+msgid "Setup"
+msgstr ""
+
+#: ../../give-gift-python.rst:22
+msgid "Install Python, https://www.python.org/"
+msgstr ""
+
+#: ../../give-gift-python.rst:23
+msgid "Run IDLE, ``$ python -m idlelib.idle``"
+msgstr ""
+
+#: ../../give-gift-python.rst:24
+msgid "Write code."
+msgstr ""
+
+#: ../../give-gift-python.rst:27
+msgid "Open the Turtle Window"
+msgstr ""
+
+#: ../../give-gift-python.rst:35
+msgid "Commands"
+msgstr ""
+
+#: ../../give-gift-python.rst:49
+msgid "Loops"
+msgstr ""
+
+#: ../../give-gift-python.rst:59
+msgid "Shapes"
+msgstr ""
+
+#: ../../give-gift-python.rst:71
+msgid "Dots"
+msgstr ""
+
+#: ../../give-gift-python.rst:79
+msgid "Functions"
+msgstr ""
+
+#: ../../give-gift-python.rst:93
+msgid "Colors"
+msgstr ""
+
+#: ../../give-gift-python.rst:102
+msgid "Locations"
+msgstr ""
+
+#: ../../give-gift-python.rst:113
+msgid "Inputs"
+msgstr ""
+
+#: ../../give-gift-python.rst:115
+msgid "listen"
+msgstr ""
+
+#: ../../give-gift-python.rst:116
+msgid "onclick"
+msgstr ""
+
+#: ../../give-gift-python.rst:117
+msgid "onkey"
+msgstr ""
+
+#: ../../give-gift-python.rst:120
+msgid "Animation"
+msgstr ""
+
+#: ../../give-gift-python.rst:122
+msgid "ontimer"
+msgstr ""
+
+#: ../../give-gift-python.rst:123
+msgid "hideturtle"
+msgstr ""
+
+#: ../../give-gift-python.rst:124
+msgid "tracer"
+msgstr ""
+
+#: ../../give-gift-python.rst:125
+msgid "clear"
+msgstr ""
+
+#: ../../give-gift-python.rst:126
+msgid "update"
+msgstr ""
+
+#: ../../give-gift-python.rst:139
+msgid "Tips"
+msgstr ""
+
+#: ../../give-gift-python.rst:141
+msgid "help(...)"
+msgstr ""
+
+#: ../../give-gift-python.rst:142
+msgid "undo(...)"
+msgstr ""
+
+#: ../../give-gift-python.rst:143
+msgid "Embrace copy/paste"
+msgstr ""
+
+#: ../../give-gift-python.rst:144
+msgid "Close window/reset()"
+msgstr ""
+
+#: ../../give-gift-python.rst:147
+msgid "Activities"
+msgstr ""
+
+#: ../../give-gift-python.rst:149
+msgid "Spell your name."
+msgstr ""
+
+#: ../../give-gift-python.rst:150
+msgid "``python -m pip install freegames``"
+msgstr ""
+
+#: ../../give-gift-python.rst:153
+msgid "Notes"
+msgstr ""
+
+#: ../../give-gift-python.rst:155
+msgid "Start simple! Start easy! Start plain!"
+msgstr ""
+
+#: ../../give-gift-python.rst:156
+msgid "Focus on fun! No PEP8. No Pylint."
+msgstr ""
+
+#: ../../give-gift-python.rst:157
+msgid "Make it readable! Say it aloud."
+msgstr ""
+
+#: ../../give-gift-python.rst:158
+msgid "No special shells! No IPython."
+msgstr ""
+
+#: ../../give-gift-python.rst:159
+msgid "Show them mistakes! Red is your favorite color!"
+msgstr ""
+
+#: ../../give-gift-python.rst:160
+msgid "No virtual environments!"
+msgstr ""
+
+#: ../../give-gift-python.rst:161
+msgid "If they're not ready, don't push them!"
+msgstr ""
+
+#: ../../give-gift-python.rst:162
+msgid "No dunder methods or attributes! No __name__ or __main__."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/guess.po b/docs/locale/pt_BR/LC_MESSAGES/guess.po
new file mode 100644
index 00000000..6c7b2be8
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/guess.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../guess.rst:2
+msgid "Guess"
+msgstr ""
+
+#: ../../guess.rst:4
+msgid "Guess a number within a range."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/index.po b/docs/locale/pt_BR/LC_MESSAGES/index.po
new file mode 100644
index 00000000..a7b9e0c0
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/index.po
@@ -0,0 +1,522 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-10-12 16:44-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../../README.rst:2
+msgid "Free Python Games"
+msgstr ""
+
+#: ../../../README.rst:4
+msgid ""
+"`Free Python Games`_ is an Apache2 licensed collection of free Python "
+"games intended for education and fun. The games are written in simple "
+"Python code and designed for experimentation and changes. Simplified "
+"versions of several classic arcade games are included."
+msgstr ""
+
+#: ../../../README.rst:9
+msgid ""
+"Python is one of the top-five most popular programming languages in the "
+"world and available for free from `Python.org "
+"`_. Python includes an extensive Standard "
+"Library distributed with your installation. The Standard Library has a "
+"module called Turtle which is a popular way to introduce programming to "
+"kids. Turtle was part of the original Logo programming language developed"
+" by Wally Feurzig and Seymour Papert in 1966. All of the games in `Free "
+"Python Games`_ are implemented using Python and its Turtle module."
+msgstr ""
+
+#: ../../../README.rst:17
+msgid ""
+"Starting in 2012, `Free Python Games`_ began as an after school program "
+"to teach programming to inner-city youth. The goal was to have fun as "
+"much as it was to learn. Since then the games have been improved and used"
+" in a variety of settings ranging from classrooms to summer day-camps."
+msgstr ""
+
+#: ../../../README.rst:22
+msgid ""
+"The games run anywhere Python can be installed which includes desktop "
+"computers running Windows, Mac OS, or Linux and older or low-power "
+"hardware such as the Raspberry Pi. Kids across the United States in "
+"grades 6th-12th have enjoyed learning about topics such as encryption and"
+" projectile motion through games."
+msgstr ""
+
+#: ../../../README.rst:27
+msgid ""
+"Each game is entirely independent from the others and includes comments "
+"along 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."
+msgstr ""
+
+#: ../../../README.rst:36
+msgid "Testimonials"
+msgstr ""
+
+#: ../../../README.rst:38
+msgid ""
+"*\"I love Free Python Games because the games are fun and they're easy to"
+" understand and change. I like making my own games now.\"*"
+msgstr ""
+
+#: ../../../README.rst:41
+msgid "-- Luke Martin, Student"
+msgstr ""
+
+#: ../../../README.rst:43
+msgid ""
+"*\"Free Python Games inspired and introduced a new hobby to our son. "
+"Thank you so much for exposing him to coding. He is having so much "
+"fun!\"*"
+msgstr ""
+
+#: ../../../README.rst:46
+msgid "-- Mary Lai, Parent"
+msgstr ""
+
+#: ../../../README.rst:48
+msgid ""
+"*\"Free Python Games are great because they really engage students and "
+"let them learn at their own pace.\"*"
+msgstr ""
+
+#: ../../../README.rst:51
+msgid "-- Rick Schertle, Teacher, Steindorf STEAM School"
+msgstr ""
+
+#: ../../../README.rst:53
+msgid ""
+"*\"Free Python Games combines play and learning in a flexible environment"
+" that reduces the stress of a difficult topic like programming.\"*"
+msgstr ""
+
+#: ../../../README.rst:56
+msgid "-- Brett Bymaster, Youth Pastor, The River Church Community"
+msgstr ""
+
+#: ../../../README.rst:58
+msgid ""
+"*\"Free Python Games is great for students, is highly organized and "
+"flexible, and seeks to unleash inquiry and understanding.\"*"
+msgstr ""
+
+#: ../../../README.rst:61
+msgid "-- Terri Furton, Principal, Downtown College Prep"
+msgstr ""
+
+#: ../../../README.rst:65
+msgid "Features"
+msgstr ""
+
+#: ../../../README.rst:67
+msgid "Fun to play!"
+msgstr ""
+
+#: ../../../README.rst:68
+msgid "Simple Python code"
+msgstr ""
+
+#: ../../../README.rst:69
+msgid "Easy to install"
+msgstr ""
+
+#: ../../../README.rst:70
+msgid "Designed for education"
+msgstr ""
+
+#: ../../../README.rst:71
+msgid "Depends only on the Python Standard Library"
+msgstr ""
+
+#: ../../../README.rst:72
+msgid "Used in hundreds of hours of classroom instruction"
+msgstr ""
+
+#: ../../../README.rst:73
+msgid "Fully Documented"
+msgstr ""
+
+#: ../../../README.rst:74
+msgid "100% Test Coverage"
+msgstr ""
+
+#: ../../../README.rst:75
+msgid "Developed on Python 3.9"
+msgstr ""
+
+#: ../../../README.rst:76
+msgid "Tested on CPython 3.6, 3.7, 3.8, 3.9"
+msgstr ""
+
+#: ../../../README.rst:77
+msgid "Tested on Linux, Mac OS X, and Windows"
+msgstr ""
+
+#: ../../../README.rst:78
+msgid "Tested using GitHub Actions"
+msgstr ""
+
+#: ../../../README.rst:85
+msgid "Quickstart"
+msgstr ""
+
+#: ../../../README.rst:87
+msgid ""
+"Installing Free Python Games is simple with `pip "
+"`_::"
+msgstr ""
+
+#: ../../../README.rst:92
+msgid ""
+"Free Python Games supports a command-line interface (CLI). Help for the "
+"CLI is available using::"
+msgstr ""
+
+#: ../../../README.rst:97
+msgid ""
+"The CLI supports three commands: list, copy, and show. For a list of all "
+"games run::"
+msgstr ""
+
+#: ../../../README.rst:102
+msgid ""
+"Any of the listed games may be played by executing the Python module from"
+" the command-line. To reference the Python module, combine \"freegames\" "
+"with the name of the game. For example, to play the \"snake\" game run::"
+msgstr ""
+
+#: ../../../README.rst:108
+msgid ""
+"Games can be modified by copying their source code. The copy command will"
+" create a Python file in your local directory which you can edit. For "
+"example, to copy and play the \"snake\" game run::"
+msgstr ""
+
+#: ../../../README.rst:115
+msgid ""
+"Python includes a built-in text editor named IDLE which can also execute "
+"Python code. To launch the editor and make changes to the \"snake\" game "
+"run::"
+msgstr ""
+
+#: ../../../README.rst:120
+msgid ""
+"You can also access documentation in the interpreter with Python's built-"
+"in help function::"
+msgstr ""
+
+#: ../../../README.rst:128
+msgid "Free Games"
+msgstr ""
+
+#: ../../../README.rst:131
+msgid "Paint"
+msgstr ""
+
+#: ../../../README.rst:133
+msgid ""
+"`Paint`_ -- draw lines and shapes on the screen. Click to mark the start "
+"of a shape and click again to mark its end. Different shapes and colors "
+"can be selected using the keyboard."
+msgstr ""
+
+#: ../../../README.rst:143
+msgid "Snake"
+msgstr ""
+
+#: ../../../README.rst:145
+msgid ""
+"`Snake`_ -- classic arcade game. Use the arrow keys to navigate and eat "
+"the green food. Each time the food is consumed, the snake grows one "
+"segment longer. Avoid eating yourself or going out of bounds!"
+msgstr ""
+
+#: ../../../README.rst:155
+msgid "Pacman"
+msgstr ""
+
+#: ../../../README.rst:157
+msgid ""
+"`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."
+msgstr ""
+
+#: ../../../README.rst:166
+msgid "Cannon"
+msgstr ""
+
+#: ../../../README.rst:168
+msgid ""
+"`Cannon`_ -- projectile motion. Click the screen to fire your "
+"cannnonball. The cannonball pops blue balloons in its path. Pop all the "
+"balloons before they can cross the screen."
+msgstr ""
+
+#: ../../../README.rst:178
+msgid "Connect"
+msgstr ""
+
+#: ../../../README.rst:180
+msgid ""
+"`Connect`_ -- Connect 4 game. Click a row to drop a disc. The first "
+"player to connect four discs vertically, horizontally, or diagonally "
+"wins!"
+msgstr ""
+
+#: ../../../README.rst:189
+msgid "Flappy"
+msgstr ""
+
+#: ../../../README.rst:191
+msgid ""
+"`Flappy`_ -- Flappy-bird inspired game. Click the screen to flap your "
+"wings. Watch out for black ravens as you fly across the screen."
+msgstr ""
+
+#: ../../../README.rst:200
+msgid "Memory"
+msgstr ""
+
+#: ../../../README.rst:202
+msgid ""
+"`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."
+msgstr ""
+
+#: ../../../README.rst:211
+msgid "Pong"
+msgstr ""
+
+#: ../../../README.rst:213
+msgid ""
+"`Pong`_ -- classic arcade game. Use the keyboard to move your paddle up "
+"and down. The first player to miss the ball loses."
+msgstr ""
+
+#: ../../../README.rst:222
+msgid "Simon Says"
+msgstr ""
+
+#: ../../../README.rst:224
+msgid ""
+"`Simon Says`_ -- classic memory puzzle game. Click the screen to start. "
+"Watch the pattern and then click the tiles in the same order. Each time "
+"you get the sequence right the pattern gets one step longer."
+msgstr ""
+
+#: ../../../README.rst:234
+msgid "Tic Tac Toe"
+msgstr ""
+
+#: ../../../README.rst:236
+msgid ""
+"`Tic Tac Toe`_ -- classic game. Click the screen to place an X or O. "
+"Connect three in a row and you win!"
+msgstr ""
+
+#: ../../../README.rst:245
+msgid "Tiles"
+msgstr ""
+
+#: ../../../README.rst:247
+msgid ""
+"`Tiles`_ -- puzzle game of sliding numbers into place. Click a tile "
+"adjacent to the empty square to swap positions. Can you make the tiles "
+"count one to fifteen from left to right and bottom to top?"
+msgstr ""
+
+#: ../../../README.rst:257
+msgid "Tron"
+msgstr ""
+
+#: ../../../README.rst:259
+msgid ""
+"`Tron`_ -- classic arcade game. Use the keyboard to change the direction "
+"of your Tron player. Avoid touching the line drawn by your opponent."
+msgstr ""
+
+#: ../../../README.rst:268
+msgid "Life"
+msgstr ""
+
+#: ../../../README.rst:270
+msgid ""
+"`Life`_ -- Conway's Game of Life. The classic, zero-player, cellular "
+"automation created in 1970 by John Conway."
+msgstr ""
+
+#: ../../../README.rst:279
+msgid "Maze"
+msgstr ""
+
+#: ../../../README.rst:281
+msgid ""
+"`Maze`_ -- move from one side to another. Inspired by `A Universe in One "
+"Line of Code with 10 PRINT`_. Tap the screen to trace a path from one "
+"side to another."
+msgstr ""
+
+#: ../../../README.rst:292
+msgid "Fidget"
+msgstr ""
+
+#: ../../../README.rst:294
+msgid ""
+"`Fidget`_ -- fidget spinner inspired animation. Click the screen to "
+"accelerate the fidget spinner."
+msgstr ""
+
+#: ../../../README.rst:304
+msgid "User Guide"
+msgstr ""
+
+#: ../../../README.rst:306
+msgid ""
+"For those wanting more details, this part of the documentation describes "
+"curriculum, API, and development."
+msgstr ""
+
+#: ../../../README.rst:309
+msgid "`Talk: Give the Gift of Python`_"
+msgstr ""
+
+#: ../../../README.rst:310
+msgid "`Free Python Games Curriculum`_"
+msgstr ""
+
+#: ../../../README.rst:311
+msgid "`Free Python Games API Reference`_"
+msgstr ""
+
+#: ../../../README.rst:312
+msgid "`Free Python Games Development`_"
+msgstr ""
+
+#: ../../../README.rst:321
+msgid "References"
+msgstr ""
+
+#: ../../../README.rst:323
+msgid "`Free Python Games Documentation`_"
+msgstr ""
+
+#: ../../../README.rst:324
+msgid "`Free Python Games at PyPI`_"
+msgstr ""
+
+#: ../../../README.rst:325
+msgid "`Free Python Games at GitHub`_"
+msgstr ""
+
+#: ../../../README.rst:326
+msgid "`Free Python Games Issue Tracker`_"
+msgstr ""
+
+#: ../../../README.rst:335
+msgid "Free Python Games License"
+msgstr ""
+
+#: ../../../README.rst:337
+msgid "Copyright 2017-2021 Grant Jenks"
+msgstr ""
+
+#: ../../../README.rst:339
+msgid ""
+"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 License at"
+msgstr ""
+
+#: ../../../README.rst:343
+msgid "http://www.apache.org/licenses/LICENSE-2.0"
+msgstr ""
+
+#: ../../../README.rst:345
+msgid ""
+"Unless required by applicable law or agreed to in writing, software "
+"distributed under the License is distributed on an \"AS IS\" BASIS, "
+"WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied."
+" See the License for the specific language governing permissions and "
+"limitations under the License."
+msgstr ""
+
+#~ msgid "Developed on Python 3.7"
+#~ msgstr ""
+
+#~ msgid "Tested on CPython 2.7, 3.4, 3.5, 3.6, and 3.7"
+#~ msgstr ""
+
+#~ msgid "Tested on Windows, Mac OS X, Raspbian (Raspberry Pi), and Linux"
+#~ msgstr ""
+
+#~ msgid "Tested using Travis CI and AppVeyor CI"
+#~ msgstr ""
+
+#~ msgid "Paint Free Python Game"
+#~ msgstr ""
+
+#~ msgid "Snake Free Python Game"
+#~ msgstr ""
+
+#~ msgid "Pacman Free Python Game"
+#~ msgstr ""
+
+#~ msgid "Cannon Free Python Game"
+#~ msgstr ""
+
+#~ msgid "Connect 4 Free Python Game"
+#~ msgstr ""
+
+#~ msgid "Flappy Bird Free Python Game"
+#~ msgstr ""
+
+#~ msgid "Memory Free Python Game"
+#~ msgstr ""
+
+#~ msgid "Pong Free Python Game"
+#~ msgstr ""
+
+#~ msgid "Simon Says Free Python Game"
+#~ msgstr ""
+
+#~ msgid "Tic Tac Toe Free Python Game"
+#~ msgstr ""
+
+#~ msgid "Tiles Free Python Game"
+#~ msgstr ""
+
+#~ msgid "Tron Free Python Game"
+#~ msgstr ""
+
+#~ msgid "Game of Life Free Python Game"
+#~ msgstr ""
+
+#~ msgid "Maze Free Python Game"
+#~ msgstr ""
+
+#~ msgid "Fidget Spinner Free Python Game"
+#~ msgstr ""
+
+#~ msgid "Copyright 2017-2020 Grant Jenks"
+#~ msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/life.po b/docs/locale/pt_BR/LC_MESSAGES/life.po
new file mode 100644
index 00000000..1eef9ba2
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/life.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../life.rst:2
+msgid "Life"
+msgstr ""
+
+#: ../../life.rst:4
+msgid "Game of Life simulation."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/madlibs.po b/docs/locale/pt_BR/LC_MESSAGES/madlibs.po
new file mode 100644
index 00000000..d3eb94ff
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/madlibs.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2021, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-10-12 16:44-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../madlibs.rst:2
+msgid "Mad Libs"
+msgstr ""
+
+#: ../../madlibs.rst:4
+msgid "Create a funny story from a not-so funny story"
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/maze.po b/docs/locale/pt_BR/LC_MESSAGES/maze.po
new file mode 100644
index 00000000..59df0e8e
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/maze.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../maze.rst:2
+msgid "Maze"
+msgstr ""
+
+#: ../../maze.rst:4
+msgid "Maze, move from one side to another."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/memory.po b/docs/locale/pt_BR/LC_MESSAGES/memory.po
new file mode 100644
index 00000000..891e017f
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/memory.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../memory.rst:2
+msgid "Memory"
+msgstr ""
+
+#: ../../memory.rst:4
+msgid "Memory, puzzle game of number pairs."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/pacman.po b/docs/locale/pt_BR/LC_MESSAGES/pacman.po
new file mode 100644
index 00000000..c6e5a90f
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/pacman.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../pacman.rst:2
+msgid "Pacman"
+msgstr ""
+
+#: ../../pacman.rst:4
+msgid "Pacman, classic arcade game."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/paint.po b/docs/locale/pt_BR/LC_MESSAGES/paint.po
new file mode 100644
index 00000000..1791e0be
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/paint.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../paint.rst:2
+msgid "Paint"
+msgstr ""
+
+#: ../../paint.rst:4
+msgid "Paint, for drawing shapes."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/pong.po b/docs/locale/pt_BR/LC_MESSAGES/pong.po
new file mode 100644
index 00000000..ee8d2d29
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/pong.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../pong.rst:2
+msgid "Pong"
+msgstr ""
+
+#: ../../pong.rst:4
+msgid "Pong, classic arcade game."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/simonsays.po b/docs/locale/pt_BR/LC_MESSAGES/simonsays.po
new file mode 100644
index 00000000..666d9f77
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/simonsays.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../simonsays.rst:2
+msgid "Simon Says"
+msgstr ""
+
+#: ../../simonsays.rst:4
+msgid "A game of watching and recalling patterns."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/snake.po b/docs/locale/pt_BR/LC_MESSAGES/snake.po
new file mode 100644
index 00000000..92dbf086
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/snake.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../snake.rst:2
+msgid "Snake"
+msgstr ""
+
+#: ../../snake.rst:4
+msgid "Snake, classic arcade game."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/sphinx.po b/docs/locale/pt_BR/LC_MESSAGES/sphinx.po
new file mode 100644
index 00000000..fc9b7ced
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/sphinx.po
@@ -0,0 +1,40 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../_templates/gumroad.html:1
+msgid "Donate"
+msgstr ""
+
+#: ../../_templates/gumroad.html:2
+msgid "If you or your organization uses Free Games, consider donating:"
+msgstr ""
+
+#: ../../_templates/gumroad.html:4
+msgid "Donate to Free Python Games"
+msgstr ""
+
+#: ../../_templates/pagetoc.html:11
+msgid "Contents"
+msgstr ""
+
+#: ../../_templates/search.html:14 ../../_templates/search.html:17
+msgid "Search"
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/tictactoe.po b/docs/locale/pt_BR/LC_MESSAGES/tictactoe.po
new file mode 100644
index 00000000..2663f6e3
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/tictactoe.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../tictactoe.rst:2
+msgid "Tic Tac Toe"
+msgstr ""
+
+#: ../../tictactoe.rst:4
+msgid "A paper-and-pencil game for two players."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/tiles.po b/docs/locale/pt_BR/LC_MESSAGES/tiles.po
new file mode 100644
index 00000000..fbd80dbb
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/tiles.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../tiles.rst:2
+msgid "Tiles"
+msgstr ""
+
+#: ../../tiles.rst:4
+msgid "Tiles, number swapping game."
+msgstr ""
+
diff --git a/docs/locale/pt_BR/LC_MESSAGES/tron.po b/docs/locale/pt_BR/LC_MESSAGES/tron.po
new file mode 100644
index 00000000..f1d3af29
--- /dev/null
+++ b/docs/locale/pt_BR/LC_MESSAGES/tron.po
@@ -0,0 +1,28 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2017-2020, Grant Jenks
+# This file is distributed under the same license as the Free Python Games
+# package.
+# FIRST AUTHOR , 2021.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Free Python Games 2.3.2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-08-24 21:25-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 2.9.1\n"
+
+#: ../../tron.rst:2
+msgid "Tron"
+msgstr ""
+
+#: ../../tron.rst:4
+msgid "Tron, classic arcade game."
+msgstr ""
+
diff --git a/docs/madlibs.rst b/docs/madlibs.rst
new file mode 100644
index 00000000..359ffbc5
--- /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
diff --git a/docs/requirements-doc.txt b/docs/requirements-doc.txt
new file mode 100644
index 00000000..1054d912
--- /dev/null
+++ b/docs/requirements-doc.txt
@@ -0,0 +1,2 @@
+sphinx
+sphinx-intl
diff --git a/freegames/__init__.py b/freegames/__init__.py
index 8cc4db82..93a04f93 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
@@ -63,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-2020, Grant Jenks'
+__copyright__ = '2017-2022, Grant Jenks'
diff --git a/freegames/__main__.py b/freegames/__main__.py
index e92a46ce..3212a184 100644
--- a/freegames/__main__.py
+++ b/freegames/__main__.py
@@ -1,5 +1,4 @@
"""Free Games CLI
-
"""
import argparse
@@ -8,20 +7,22 @@
directory = os.path.dirname(os.path.realpath(__file__))
contents = os.listdir(directory)
+
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('__')
and name != 'utils.py'
)
+
games = sorted(name[:-3] for name in contents if 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 ae388d0d..45b32fcd 100644
--- a/freegames/ant.py
+++ b/freegames/ant.py
@@ -6,22 +6,24 @@
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 *
from turtle import *
+
from freegames import vector
ant = vector(0, 0)
aim = vector(2, 0)
+
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)
@@ -33,13 +35,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()
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 be92e587..ddc14ee8 100644
--- a/freegames/bounce.py
+++ b/freegames/bounce.py
@@ -7,22 +7,25 @@
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 *
from turtle import *
+
from freegames import vector
+
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])
+
ball = vector(0, 0)
aim = vector(value(), value())
+
def draw():
- "Move ball and draw game."
+ """Move ball and draw game."""
ball.move(aim)
x = ball.x
@@ -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..c68da9b1 100644
--- a/freegames/cannon.py
+++ b/freegames/cannon.py
@@ -6,31 +6,34 @@
2. Vary the effect of gravity.
3. Apply gravity to the targets.
4. Change the speed of the ball.
-
"""
from random import randrange
from turtle import *
+
from freegames import vector
ball = vector(-200, -200)
speed = vector(0, 0)
targets = []
+
def tap(x, y):
- "Respond to screen tap."
+ """Respond to screen tap."""
if not inside(ball):
ball.x = -199
ball.y = -199
speed.x = (x + 200) / 25
speed.y = (y + 200) / 25
+
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:
@@ -43,8 +46,9 @@ def draw():
update()
+
def move():
- "Move ball and targets."
+ """Move ball and targets."""
if randrange(40) == 0:
y = randrange(-150, 150)
target = vector(200, y)
@@ -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..5c39e166 100644
--- a/freegames/connect.py
+++ b/freegames/connect.py
@@ -7,17 +7,18 @@
3. Add logic to detect a full row.
4. Create a random computer player.
5. How would you detect a winner?
-
"""
from turtle import *
+
from freegames import line
turns = {'red': 'yellow', 'yellow': 'red'}
state = {'player': 'yellow', 'rows': [0] * 8}
+
def grid():
- "Draw Connect Four grid."
+ """Draw Connect Four grid."""
bgcolor('light blue')
for x in range(-150, 200, 50):
@@ -31,8 +32,9 @@ def grid():
update()
+
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']
@@ -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..a59affda 100644
--- a/freegames/crypto.py
+++ b/freegames/crypto.py
@@ -9,11 +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.
@@ -29,7 +29,8 @@ def encrypt(message, key):
if letter.isupper():
base = ord('A')
- elif letter.islower():
+ else:
+ assert letter.islower()
base = ord('a')
# The encryption equation:
@@ -48,16 +49,19 @@ def encrypt(message, key):
return result
+
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)
@@ -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..84d50d50 100644
--- a/freegames/fidget.py
+++ b/freegames/fidget.py
@@ -6,15 +6,15 @@
2. Respond to mouse clicks.
3. Change its acceleration.
4. Make it go forwards and backwards.
-
"""
from turtle import *
state = {'turn': 0}
+
def spinner():
- "Draw fidget spinner."
+ """Draw fidget spinner."""
clear()
angle = state['turn'] / 10
right(angle)
@@ -32,18 +32,21 @@ def spinner():
right(120)
update()
+
def animate():
- "Animate fidget spinner."
+ """Animate fidget spinner."""
if state['turn'] > 0:
state['turn'] -= 1
spinner()
ontimer(animate, 20)
+
def flick():
- "Flick fidget spinner."
+ """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..1894c2d5 100644
--- a/freegames/flappy.py
+++ b/freegames/flappy.py
@@ -6,27 +6,30 @@
2. Vary the speed.
3. Vary the size of the balls.
4. Allow the bird to move forward and back.
-
"""
from random import *
from turtle import *
+
from freegames import vector
bird = vector(0, 0)
balls = []
+
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)
@@ -42,8 +45,9 @@ def draw(alive):
update()
+
def move():
- "Update object positions."
+ """Update object positions."""
bird.y -= 5
for ball in balls:
@@ -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..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
@@ -16,17 +15,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..fcee86af 100644
--- a/freegames/life.py
+++ b/freegames/life.py
@@ -9,17 +9,18 @@
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
from turtle import *
+
from freegames import square
cells = {}
+
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
@@ -28,8 +29,9 @@ 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."
+ """Compute one step in the Game of Life."""
neighbors = {}
for x in range(-190, 190, 10):
@@ -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,8 +49,9 @@ def step():
elif count == 3:
cells[cell] = True
+
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
new file mode 100644
index 00000000..0328d7fc
--- /dev/null
+++ b/freegames/madlibs.py
@@ -0,0 +1,34 @@
+"""Mad Libs: Funny Story Creation Game
+
+Exercises:
+
+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.
+template = 'The |1| |2| |3| |4| over the |5| |6|.'
+parts = {
+ '1': 'adjective',
+ '2': 'adjective',
+ '3': 'noun',
+ '4': 'verb',
+ '5': 'adjective',
+ '6': 'noun',
+}
+
+chunks = []
+
+for chunk in template.split('|'):
+ if chunk in parts:
+ description = parts[chunk]
+ prompt = 'Enter [{}]: '.format(description)
+ word = input(prompt)
+ chunks.append(word)
+ else:
+ chunks.append(chunk)
+
+print('=' * 80)
+story = ''.join(chunks)
+print(story)
diff --git a/freegames/maze.py b/freegames/maze.py
index 9fe41bf6..ca40786a 100644
--- a/freegames/maze.py
+++ b/freegames/maze.py
@@ -5,15 +5,16 @@
1. Keep score by counting taps.
2. Make the maze harder.
3. Generate the same maze twice.
-
"""
-from turtle import *
from random import random
+from turtle import *
+
from freegames import line
+
def draw():
- "Draw maze."
+ """Draw maze."""
color('black')
width(5)
@@ -26,8 +27,9 @@ def draw():
update()
+
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:
@@ -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..c0d2ad3d 100644
--- a/freegames/memory.py
+++ b/freegames/memory.py
@@ -7,11 +7,11 @@
3. Detect when all tiles are revealed.
4. Center single-digit tile.
5. Use letters instead of tiles.
-
"""
from random import *
from turtle import *
+
from freegames import path
car = path('car.gif')
@@ -19,8 +19,9 @@
state = {'mark': None}
hide = [True] * 64
+
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()
@@ -31,16 +32,19 @@ def square(x, y):
left(90)
end_fill()
+
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']
@@ -51,8 +55,9 @@ def tap(x, y):
hide[mark] = False
state['mark'] = None
+
def draw():
- "Draw image and tiles."
+ """Draw image and tiles."""
clear()
goto(0, 0)
shape(car)
@@ -75,6 +80,7 @@ def draw():
update()
ontimer(draw, 100)
+
shuffle(tiles)
setup(420, 420, 370, 0)
addshape(car)
diff --git a/freegames/minesweeper.py b/freegames/minesweeper.py
index 87b772ca..4278af03 100644
--- a/freegames/minesweeper.py
+++ b/freegames/minesweeper.py
@@ -5,11 +5,11 @@
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
from turtle import *
+
from freegames import floor, square
seed(0)
@@ -19,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
@@ -41,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]:
@@ -63,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 f249a27d..644b35a4 100644
--- a/freegames/pacman.py
+++ b/freegames/pacman.py
@@ -7,11 +7,11 @@
3. Change where pacman starts.
4. Make the ghosts faster/slower.
5. Make the ghosts smarter.
-
"""
from random import choice
from turtle import *
+
from freegames import floor, vector
state = {'score': 0}
@@ -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,9 +48,11 @@
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)."
+ """Draw square using path at (x, y)."""
path.up()
path.goto(x, y)
path.down()
@@ -61,15 +64,17 @@ def square(x, y):
path.end_fill()
+
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)
return index
+
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:
@@ -82,8 +87,9 @@ def valid(point):
return point.x % 20 == 0 or point.y % 20 == 0
+
def world():
- "Draw world using path."
+ """Draw world using path."""
bgcolor('black')
path.color('blue')
@@ -100,8 +106,9 @@ def world():
path.goto(x + 10, y + 10)
path.dot(2, 'white')
+
def move():
- "Move pacman and all ghosts."
+ """Move pacman and all ghosts."""
writer.undo()
writer.write(state['score'])
@@ -149,12 +156,14 @@ def move():
ontimer(move, 100)
+
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
+
setup(420, 420, 370, 0)
hideturtle()
tracer(False)
diff --git a/freegames/paint.py b/freegames/paint.py
index b5fb2f2c..adc5dec5 100644
--- a/freegames/paint.py
+++ b/freegames/paint.py
@@ -7,21 +7,23 @@
3. Complete rectangle.
4. Complete triangle.
5. Add width parameter.
-
"""
from turtle import *
+
from freegames import vector
+
def line(start, end):
- "Draw line from start to end."
+ """Draw line from start to end."""
up()
goto(start.x, start.y)
down()
goto(end.x, end.y)
+
def square(start, end):
- "Draw square from start to end."
+ """Draw square from start to end."""
up()
goto(start.x, start.y)
down()
@@ -33,20 +35,24 @@ def square(start, end):
end_fill()
+
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:
@@ -57,10 +63,12 @@ def tap(x, y):
shape(start, end)
state['start'] = None
+
def store(key, value):
- "Store value in state at key."
+ """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..cbabedc9 100644
--- a/freegames/pong.py
+++ b/freegames/pong.py
@@ -9,27 +9,31 @@
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
from turtle import *
+
from freegames import vector
+
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])
+
ball = vector(0, 0)
aim = vector(value(), value())
state = {1: 0, 2: 0}
+
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()
@@ -41,8 +45,9 @@ def rectangle(x, y, width, height):
left(90)
end_fill()
+
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)
@@ -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..d583afc8 100644
--- a/freegames/simonsays.py
+++ b/freegames/simonsays.py
@@ -4,12 +4,12 @@
1. Speed up tile flash rate.
2. Add more tiles.
-
"""
from random import choice
from time import sleep
from turtle import *
+
from freegames import floor, square, vector
pattern = []
@@ -21,16 +21,18 @@
vector(-200, -200): ('yellow', 'khaki'),
}
+
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')
square(-200, -200, 200, 'khaki')
update()
+
def flash(tile):
- "Flash tile in grid."
+ """Flash tile in grid."""
glow, dark = tiles[tile]
square(tile.x, tile.y, 200, glow)
update()
@@ -39,8 +41,9 @@ def flash(tile):
update()
sleep(0.5)
+
def grow():
- "Grow pattern and flash tiles."
+ """Grow pattern and flash tiles."""
tile = choice(list(tiles))
pattern.append(tile)
@@ -50,8 +53,9 @@ def grow():
print('Pattern length:', len(pattern))
guesses.clear()
+
def tap(x, y):
- "Respond to screen tap."
+ """Respond to screen tap."""
onscreenclick(None)
x = floor(x, 200)
y = floor(y, 200)
@@ -69,11 +73,13 @@ def tap(x, y):
onscreenclick(tap)
+
def start(x, y):
- "Start game."
+ """Start game."""
grow()
onscreenclick(tap)
+
setup(420, 420, 370, 0)
hideturtle()
tracer(False)
diff --git a/freegames/snake.py b/freegames/snake.py
index 3b8c7b43..f1f2599f 100644
--- a/freegames/snake.py
+++ b/freegames/snake.py
@@ -5,29 +5,32 @@
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 turtle import *
from random import randrange
+from turtle import *
+
from freegames import square, vector
food = vector(0, 0)
snake = [vector(10, 0)]
aim = vector(0, -10)
+
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)
@@ -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..23a3142e 100644
--- a/freegames/tictactoe.py
+++ b/freegames/tictactoe.py
@@ -6,40 +6,46 @@
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 *
+
from freegames import line
+
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)
line(-200, 67, 200, 67)
+
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()
circle(62)
+
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
+
state = {'player': 0}
players = [drawx, drawo]
+
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']
@@ -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..94efda28 100644
--- a/freegames/tiles.py
+++ b/freegames/tiles.py
@@ -6,11 +6,11 @@
2. Permit diagonal squares as neighbors.
3. Respond to arrow keys instead of mouse clicks.
4. Make the grid bigger.
-
"""
from random import *
from turtle import *
+
from freegames import floor, vector
tiles = {}
@@ -21,8 +21,9 @@
vector(0, -100),
]
+
def load():
- "Load tiles and scramble."
+ """Load tiles and scramble."""
count = 1
for y in range(-200, 200, 100):
@@ -43,8 +44,9 @@ def load():
tiles[mark] = number
mark = spot
+
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()
@@ -63,8 +65,9 @@ def square(mark, number):
write(number, font=('Arial', 60, 'normal'))
+
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)
@@ -79,12 +82,14 @@ def tap(x, y):
tiles[mark] = None
square(mark, None)
+
def draw():
- "Draw all tiles."
+ """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..6cdf0b1a 100644
--- a/freegames/tron.py
+++ b/freegames/tron.py
@@ -6,10 +6,10 @@
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 *
+
from freegames import square, vector
p1xy = vector(-100, 0)
@@ -20,12 +20,14 @@
p2aim = vector(-4, 0)
p2body = set()
+
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()
@@ -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..30f63292 100644
--- a/freegames/utils.py
+++ b/freegames/utils.py
@@ -1,9 +1,8 @@
"""Utilities
-
"""
# pylint: disable=no-member
-import collections
+import collections.abc
import math
import os
@@ -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,8 +45,9 @@ 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()
turtle.goto(a, b)
turtle.down()
@@ -61,6 +61,7 @@ def square(x, y, size, name):
"""
import turtle
+
turtle.up()
turtle.goto(x, y)
turtle.down()
@@ -74,7 +75,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.
@@ -88,6 +89,7 @@ class vector(collections.Sequence):
vector(-2.0, 1.0)
"""
+
# pylint: disable=invalid-name
PRECISION = 6
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/setup.py b/setup.py
index addb36b4..8d4e9e3f 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
@@ -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)
@@ -27,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',
@@ -44,10 +53,11 @@ 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 :: 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 5e7d0572..26e5d52b 100644
--- a/tests/mockturtle.py
+++ b/tests/mockturtle.py
@@ -1,10 +1,10 @@
"""Mock turtle module.
-
"""
state = {}
events = []
+
class Turtle:
def __init__(self, visible=True):
pass
@@ -78,6 +78,7 @@ def update(self):
def undo(self):
pass
+
_turtle = Turtle()
goto = _turtle.goto
up = _turtle.up
@@ -103,22 +104,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_bounce.py b/tests/test_bounce.py
index d397163c..eef2bc20 100644
--- a/tests/test_bounce.py
+++ b/tests/test_bounce.py
@@ -1,6 +1,5 @@
import random
import runpy
-import turtle
import sys
import mockturtle
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_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_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
new file mode 100644
index 00000000..7e7c4708
--- /dev/null
+++ b/tests/test_madlibs.py
@@ -0,0 +1,21 @@
+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')
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_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
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_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),
diff --git a/tests/test_pong.py b/tests/test_pong.py
index e3a0746c..c3500585 100644
--- a/tests/test_pong.py
+++ b/tests/test_pong.py
@@ -9,16 +9,17 @@
def test_pong_1():
random.seed(0)
- mockturtle.events[:] = (
- [('timer',), ('key s',)] * 8
- + [('timer', True)] * 600
- )
+ mockturtle.events.clear()
+ mockturtle.events += [('timer',), ('key s',)] * 12
+ mockturtle.events += [('timer',), ('key w',)] * 4
+ 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',)] * 18
+ mockturtle.events += [('timer',), ('key i',)] * 6
+ mockturtle.events += [('timer', True)] * 600
runpy.run_module('freegames.pong')
diff --git a/tests/test_snake.py b/tests/test_snake.py
index c6f25a14..2b12276e 100644
--- a/tests/test_snake.py
+++ b/tests/test_snake.py
@@ -9,8 +9,20 @@
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',),
+ ('timer',),
+ ('key Right',),
+ ('timer',),
+ ('key Down',),
+ ('timer',),
+ ('key Left',),
+ ('timer',),
+ ('key Up',),
+ ]
+ mockturtle.events += [('timer', True)] * 300
runpy.run_module('freegames.snake')
diff --git a/tests/test_tron.py b/tests/test_tron.py
index 1e9d7362..0d871d16 100644
--- a/tests/test_tron.py
+++ b/tests/test_tron.py
@@ -9,16 +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')
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)
diff --git a/tox.ini b/tox.ini
index 57a6a7c6..5d835318 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,11 +1,79 @@
[tox]
-envlist=py34,py35,py36,py37
+envlist=bluecheck,doc8,docs,flake8,isortcheck,rstcheck,py36,py37,py38,py39,py310
+skip_missing_interpreters=True
[testenv]
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
+
+[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 --report warning {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 = 80
+
+[pytest]
+addopts=
+ --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]
+ignore=E722,F403,F405,W503
+max-line-length=120