{"id":20027987,"url":"https://github.com/vojay-dev/pypong","last_synced_at":"2025-09-20T06:30:56.879Z","repository":{"id":223182730,"uuid":"219591487","full_name":"vojay-dev/pypong","owner":"vojay-dev","description":"PyPong - Learn Python with Pong","archived":false,"fork":false,"pushed_at":"2021-06-25T15:05:13.000Z","size":995,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-11-13T09:12:39.303Z","etag":null,"topics":["game-development","guide","machine-learning","pong","pygame","python","tutorial"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vojay-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2019-11-04T20:34:15.000Z","updated_at":"2024-01-09T17:03:20.000Z","dependencies_parsed_at":"2024-02-18T20:53:20.889Z","dependency_job_id":null,"html_url":"https://github.com/vojay-dev/pypong","commit_stats":null,"previous_names":["vojay-dev/pypong"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vojay-dev%2Fpypong","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vojay-dev%2Fpypong/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vojay-dev%2Fpypong/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vojay-dev%2Fpypong/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vojay-dev","download_url":"https://codeload.github.com/vojay-dev/pypong/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":233635398,"owners_count":18706167,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["game-development","guide","machine-learning","pong","pygame","python","tutorial"],"created_at":"2024-11-13T09:12:46.222Z","updated_at":"2025-09-20T06:30:51.340Z","avatar_url":"https://github.com/vojay-dev.png","language":"Python","readme":"# PyPong - Learn Python and Machine Learning with Pong\n\n![screenshot](doc/screenshot.png)\n\nHi there, this is a learning project to teach people the basics of Python, game development and machine learning. This document is a guide that shows you step by step how to implement an object-oriented Pong clone on your own with PyGame and how to create an AI playing it.\n\nThis guide includes:\n\n* Python basics\n* Python project and dependency management with Poetry\n* How to use PyCharm with virtual environments\n* How to implement Pong with PyGame\n* Use NumPy for vector calculations\n* Use scikit-learn to predict actions\n\n## Table of contents\n\n- [PyPong - Learn Python and Machine Learning with Pong](#pypong---learn-python-and-machine-learning-with-pong)\n  * [Requirements](#requirements)\n  * [Project setup](#project-setup)\n    + [Create the project with Poetry](#create-the-project-with-poetry)\n    + [Generated files overview](#generated-files-overview)\n    + [First steps with Poetry](#first-steps-with-poetry)\n  * [Hello world](#hello-world)\n  * [Configuration](#configuration)\n    + [Cleanup files](#cleanup-files)\n    + [Configure sources root in PyCharm](#configure-sources-root-in-pycharm)\n    + [Configure virtualenv in PyCharm](#configure-virtualenv-in-pycharm)\n    + [Makefile](#makefile)\n  * [PyGame](#pygame)\n    + [Add PyGame depencency](#add-pygame-depencency)\n    + [Hello PyGame](#hello-pygame)\n    + [PyGame basics](#pygame-basics)\n      - [Surface](#surface)\n      - [Update display](#update-display)\n      - [Draw](#draw)\n      - [Coordinates](#coordinates)\n      - [Time](#time)\n      - [User input](#user-input)\n  * [Implementing the basic game](#implementing-the-basic-game)\n    + [Paddles](#paddles)\n    + [Game loop](#game-loop)\n    + [The ball - task](#the-ball---task)\n    + [The ball - solution](#the-ball---solution)\n    + [NumPy and ball acceleration](#numpy-and-ball-acceleration)\n    + [Collision detection - theory and task](#collision-detection---theory-and-task)\n    + [Collision detecion - solution](#collision-detecion---solution)\n    + [Ball movement - task](#ball-movement---task)\n    + [Ball movement - solution](#ball-movement---solution)\n    + [Make it a game](#make-it-a-game)\n  * [The final game](#the-final-game)\n  * [Machine Learning introduction with Pong](#machine-learning-introduction-with-pong)\n    + [Simple AI](#simple-ai)\n    + [scikit-learn](#scikit-learn)\n    + [Linear Regression and Logistic Regression](#linear-regression-and-logistic-regression)\n    + [Training data](#training-data)\n    + [Predict actions](#predict-actions)\n    + [More theory and data visualization](#more-theory-and-data-visualization)\n  * [Fin](#fin)\n\nThis repository already contains the final result. This guide will tell you step by step how to get there.\n\n---\n\n## Requirements\n\nEnsure to have the following components installed on your system:\n\n- Python 3.7+\n- Poetry (https://poetry.eustace.io/)\n- PyCharm (_Communiy Edition_)\n\nThis guide was created on a Linux environment. You should have basic knowledge about how to interact with your system in a terminal.\n\n## Project setup\n\nThe next steps are about setting up the project and getting familiar with Poetry.\n\n### Create the project with Poetry\n\nPoetry is a tool for dependency management and packaging in Python. It allows you to declare the libraries your project depends on and it will manage (install/update) them for you.\n\nWe will now use it to create a new project. Just execute the following command in your terminal:\n\n```\npoetry new pypong\n```\n\n### Generated files overview\n\nLet us have a look at the generated files without changing them:\n\n```\n.\n├── pypong\n│   └── __init__.py\n├── pyproject.toml\n├── README.rst\n└── tests\n    ├── __init__.py\n    └── test_pypong.py\n```\n\n* `pypong/`: Contains project source\n* `pyproject.toml`: Poetry config and project dependencies\n* `README.rst`: Markup file for project documentation\n* `tests/`: Tests for the project\n* `__init__.py`: Marks directories on disk as Python packages, might contain special attributes (e.g. project version)\n\nOr in short: our code goes into `pypong/` and dependencies will be added to `pyproject.toml`. For now this is everything we need to know.\n\n### First steps with Poetry\n\nNow that there is a basic Poetry project, let us get familiar with how it works. Poetry is creating a vitual Python environment on your machine for each project and takes care of interacting with that so called `virtualenv` (e.g. installing dependencies).\n\nWhen you run code with Poetry it will interact with the virtual environmeent of the project and executes the code within that environment.\n\nThat way projects are isolated and your system will be kept clean.\n\nThe first step is to install the dependencies defined in the `pyproject.toml` file. Just exectue the following command in the project root dir:\n\n```\npoetry install\n```\n\nThis will not only install the dependencies but also create a new file:\n\n* `poetry.lock`: locks the installed dependency versions\n\nFrom now on the install command will use the dependency versions from the lock file. Execute the following command to update the lock file and dependency versions:\n\n```\npoetry update\n```\n\nNothing should change as we have the latest versions already.\n\nThe next step is to execute commands via Poetry in the virtual environment. Let us start with a simple command:\n\n```\npoetry run python -V\n```\n\nThis will run `python -V` in your virtual environment and prints your Python version.\n\n## Hello world\n\nIt is time to write some Python code.\n\nCreate a new Python file in the source directory:\n\n```\ntouch pypong/game.py\n```\n\nOpen the project in PyCharm and paste the following code in the newly created file:\n\n```python\ndef main():\n    print('Hello world')\n\n\nif __name__ == '__main__':\n    main()\n```\n\nAs you can see, the code has a special element, which is: `if __name__ == '__main__':`. Whatever is part of this code block will only be executed when you run this file explicitly with the Python interpreter.\n\nIf you would get rid of the if-clause, the code would still work in our case as expected. However if someone includes this file into his project, he would also see the \"Hello world\" output because Python interprets all code in a file when it is imported and this might be an unexpected behavior.\n\nThe variable `__name__` only contains the text `__name__` when you run the file with Python explicitly so when importing this file, no output would appear.\n\nThat is why you should use this condition to mark the entry point to your application.\n\nThe rest of the code should be self explaining. Instead of just printing out `Hello world` to stdout, we are calling a function that will do it for us.\n\nGive it a try and execute the code in your IDE (`Ctrl+Shift+F10`).\n\nKeep in mind, executing your code like this will use your main Python environment and not the virtual environment managed by Poetry. This is not what we want so in the next chapter we will take care of configuring everything accordingly.\n\n## Configuration\n\nBefore we continue with the actual project, let us do some preparation to make everything more awesome :-).\n\n### Cleanup files\n\nWe want to keep the project structure clean so let's get rid of files and folders we don't need for this particular project.\n\nThis guide will not cover tests, even though testing is an essential part of software development. Therefore we can delete the `tests/` directory for now.\n\n```\nrm -rf tests\n```\n\n### Configure sources root in PyCharm\n\nIn PyCharm, right click on the `pypong/pypong` directory and click on `Mark Directory as -\u003e Sources Root`.\n\n### Configure virtualenv in PyCharm\n\nThe next step is a bit tricky. Poetry uses a virtual environment to run Python. We want PyCharm to use the exact same environment when we run code via the IDE.\n\nThe first step is to find the location of the virtual environment. Execute this command in the terminal within the root directory of your project:\n\n```\npoetry show -v\n```\n\nThe first line of the output is what we are looking for, it should look something like this:\n\n```\nUsing virtualenv: /home/pre/.cache/pypoetry/virtualenvs/pypong-py3.7\n```\n\nThis is the location of the virtual environment.\n\nCopy the path and switch to PyCharm. In PyCharm click on `File -\u003e Settings` and navigate to: `Project: pypong -\u003e Project Interpreter`. Now click on the cog on the right and choose `Add`. A new dialog will open.\n\nIn this dialog, choose `Existing environment` then click on the three dots `...` and paste the path you copied before.\n\nFinally in this folder, navigate to `bin` and choose `python`. Then click `OK` to add the environment.\n\nYou can now close the settings window.\n\nRun the code in your IDE again with `Ctrl+Shift+F10`. The output should look slightly different now as PyCharm uses the virtual environment to execute the code. This also means when we add dependencies with Poetry, PyCharm will also be aware of it.\n\n### Makefile\n\nSince we want to be flexible and also run our project in the terminal, let us create a basic Makefile to make our lives easy.\n\nCreate a file called `Makefile` in the project root directory:\n\n```\ntouch Makefile\n```\n\nAnd paste the following content to it:\n\n```\nall:\n\t@echo 'see README'\n\nrun:\n\tpoetry run python pypong/game.py\n\n.PHONY: all run\n```\n\nYou should now also be able to run the project with:\n\n```\nmake run\n```\n\nThe basic setup is now done and we can continue adding our first dependency.\n\n## PyGame\n\nWe will use PyGame to create the Pong clone. This chapter will explain how to add it to our project and how PyGame works.\n\n### Add PyGame depencency\n\nWe want to create a game. To do that we need some basic things like a game loop, handling user input, draw things in a window etc. - of course we can create all that on our own but the good thing about Python is: there is a module for everything.\n\nTherefore let us add the first dependency: PyGame (see: https://www.pygame.org for details).\n\nAs we are using Poetry, adding a dependency is super easy, just run:\n\n```\npoetry add pygame\n```\n\n### Hello PyGame\n\nIn our `game.py` we already have a function called `main`. Replace the existing code in this function with the following snippet:\n\n```python\n    pygame.init()\n    screen = pygame.display.set_mode((800, 600))\n    pygame.display.set_caption(\"pypong\")\n\n    clock = pygame.time.Clock()\n\n    running = True\n    while running:\n        clock.tick(60)\n        screen.fill((0, 0, 0))\n\n        for event in pygame.event.get():\n            print(event)\n\n        pygame.display.flip()\n```\n\nAlso add the following statement on the very top of the file to import the PyGame dependency:\n\n```python\nimport pygame\n```\n\nUse your IDE to format your code.\n\nThen run your code once in your IDE and once with `make run`. You will see a nice little window with a black background, furthermore you will see the user input on stdout.\n\n### PyGame basics\n\nBefore we continue, let us have a look at some PyGame basics. This chapter does not require any changes to your project. However read it carefully as we will apply these principles afterwards.\n\n#### Surface\n\nThe most important part of PyGame is the `surface`. Just think of a `surface` as a blank piece of paper. You can do a lot of things with a `surface`, you can draw shapes on it, fill parts of it with color or copy images to and from it.\n\nA `surface` can be any size and you can have as many of them as you like. One `surface` is special: the one you create with `pygame.display.set_mode()`. This display `surface` represents the screen. Whatever you do to it will appear on the users screen. You can only have one of these.\n\n#### Update display\n\nAfter adding elements on your surfaces, the display must be updated so that the outcome can be seen on the users screen. You can do this either with `pygame.display.update()` or `pygame.display.flip()`. It is recommended to use `flip` since it handles double-buffered hardware acceleration correctly.\n\n#### Draw\n\nWith `pygame.draw` you get a set of functions to draw basic elements on a `surface`. See https://www.pygame.org/docs/ref/draw.html for an overview. Let's have a closer look at this snippet:\n\n```python\npygame.draw.rect(\n    surface,\n    (255, 255, 255),\n    (self.x, self.y, self.width, self.height)\n)\n```\n\nThis function will draw a rectangle on the given `surface`. The second parameter is the color as a tuple indicating the red, green and blue values. The third parameter is a tuple with the coordinates and size of the rectangle.\n\n#### Coordinates\n\nThe coordinate system of each surface is a system that uses two numbers to uniquely determine the position of the points or other geometric elements. Usually those coordinates are named as `x` and `y` and are defined as a tuple. The `(0, 0)` tuple is the **top left corner** of the surface.\n\nThat means for example if an object moves down, its `y` coordinate is increasing.\n\n#### Time\n\nTime is a very important aspect in game development. If you move an object 10 pixels to the right in every frame, the speed of the movement depends on how many frames are rendered per second on the users machine. Because of that it is important to consider time when implementing the game loop.\n\nPyGame has an important helper for that: the Clock. A Clock can be created with:\n\n```python\nclock = pygame.time.Clock()\n```\n\nIn the game loop you can then call:\n\n```\nclock.tick(60)\n```\n\nWhich will pause the loop in a way so that there are a maximum of 60 frames per second. Also it will update the clock.\n\nAfterwards you can get the time passed since the last tick with:\n\n```python\ntime_passed_ms = clock.get_time()\n```\n\nThis will give you the time in milliseconds since the last tick. This value should be used in all state updates, e.g. moving objects.\n\n**However to keep this tutorial simple, we will not use this function as this Pong clone will run at 60 fps on almost all machines anyways ;-).**\n\n#### User input\n\nUser input is very important because every game needs to somehow react upon user input.\n\nFirst of all PyGame gives you a way to react to events:\n\n```python\nfor event in pygame.event.get():\n    print(event)\n```\n\nAn event might be mouse movement or pressing a key. However sometimes we want to not only react to a specific event but move an object as long as a key is pressed for instance.\n\nFor this kind of user interaction PyGame enables you the check the state of user input and react to this instead:\n\n```python\nif pygame.key.get_pressed()[pygame.K_w]:\n    paddle1.move_up()\n```\n\nThese were the basic building blocks of PyGame. Let's continue with creating the main elements of our game.\n\n## Implementing the basic game\n\nEnogh theory, let's jump into developing the game. Our game has some main elements: The paddles, a ball, the game loop and then some extras like an indication of who wins the game. This chapter will explain step by step how to implement these elements.\n\n### Paddles\n\nOkay, a black screen is boring so let's start adding the paddles. The goal is to have a rectangle on the left that can be controlled with `w` and `w` and a paddle on the right controlled with `arrow up` and `arrow down`. The paddles should not leave the window.\n\nPython is object-oriented so let us implement the game using this paradigm.\n\nCreate a new file called `paddle.py` in the source directory and add the following code:\n\n```python\nimport pygame\n\n\nclass Paddle:\n\n    def __init__(self, x=20):\n        self.x = x\n\n        self.width = 20\n        self.height = 150\n        self.speed = 10\n\n        # place paddle in the middle\n        surface_height = pygame.display.get_surface().get_height()\n        self.y = surface_height / 2 - self.height / 2\n\n    def move_up(self):\n        self.y = max(self.y - self.speed, 0)\n\n    def move_down(self):\n        surface_height = pygame.display.get_surface().get_height()\n        self.y = min(self.y + self.speed, surface_height - self.height)\n\n    def draw(self, surface):\n        pygame.draw.rect(\n            surface,\n            (255, 255, 255),\n            (self.x, self.y, self.width, self.height)\n        )\n\n```\n\nThe basic idea is that we have a `Paddle` class that can be used for the left and right paddle. The difference is the `x` position that can be set when creating an instance of a `Paddle`. The class also has methods to move the paddle up and down whereas the movemet is limited via the `min` and `max` functions that are part of the built-in Python functions.\n\nVisit https://docs.python.org/3/library/functions.html and check the documentation for `min` and `max` to get a better understanding of those functions. Also read through the list of functions to get an overview what else Python offers.\n\n### Game loop\n\nNow it is time to make use of the `Paddle` class. But before we do that, let us get a basic idea of how the game loop works. Essentially it looks like this:\n\n1. Setup objects\n2. While RUNNING do\n    * Handle input\n    * Update state (game and elements)\n    * Draw elements\n    * Refresh screen\n\nWith this basic structure in mind, go back to the `game.py` file and replace the **whole file** with the following code:\n\n```python\nimport pygame\n\nfrom paddle import Paddle\n\n\ndef main():\n    pygame.init()\n    screen = pygame.display.set_mode((800, 600))\n    pygame.display.set_caption(\"pypong\")\n\n    clock = pygame.time.Clock()\n\n    paddle1 = Paddle()\n    paddle2 = Paddle(760)\n\n    running = True\n    while running:\n        clock.tick(60)\n        screen.fill((0, 0, 0))\n\n        # handle input\n        for event in pygame.event.get():\n            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:\n                running = False\n\n        if pygame.key.get_pressed()[pygame.K_w]:\n            paddle1.move_up()\n\n        if pygame.key.get_pressed()[pygame.K_s]:\n            paddle1.move_down()\n\n        if pygame.key.get_pressed()[pygame.K_UP]:\n            paddle2.move_up()\n\n        if pygame.key.get_pressed()[pygame.K_DOWN]:\n            paddle2.move_down()\n\n        # draw objects\n        paddle1.draw(screen)\n        paddle2.draw(screen)\n\n        # refresh display\n        pygame.display.flip()\n\n\nif __name__ == '__main__':\n    main()\n\n```\n\nGive it a try and execute the project. You should now have two paddles that can be controlled with `w` and `s` or `arrow up` and `arrow down`.\n\nRead through the code carefully to get a basic understanding of how it interacts with the PyGame API.\n\n### The ball - task\n\nBy now you should know how to run your project, create classes, handle user input and how to use the basic functions that PyGame offers.\n\nNow it is your turn to create a new game object: the ball.\n\nCreate a new file `ball.py` and implement a `Ball` class, add it to your game and display it with the following requirements:\n\n* The ball must be a **yellow** square with a side length of **20 pixels**\n* Every frame it must **move 5 pixels to the left** until it reaches the **end of the screen**\n* Then it switches the direction and moves **5 pixels to the right** every frame until it reaches the **end of the screen**\n* Afterwards it must change its direction again and continue to move back and forth like this\n* The starting position must be the **middle of the screen**\n\nOnce you are done, continue reading this document as it will have the solution in the next chapter :-).\n\n### The ball - solution\n\nThis is one solution to the requirements mentioned before, please compare it to your solution.\n\nIt is recommended to replace your code with this solution as the following chapters are built on top of it.\n\nThe following solution uses NumPy. NumPy is the core library for scientific computing in Python. It provides a high-performance multidimensional array object, and tools for working with these arrays. See https://numpy.org/ for details.\n\nIt is absolutely not required to use NumPy in order to solve the task but we will use it so that you can learn the basics of this essential Python library.\n\nTo make the following code work, we need to add NumPy as a dependency with Poetry to our project:\n\n```\npoetry add numpy\n```\n\nNow you can integrate the following code and run the project again. Just replace the content of the files.\n\n\u003e ball.py\n\n```python\nimport pygame\nimport numpy as np\n\n\nclass Ball:\n\n    def __init__(self):\n        self.width = 20\n        self.height = 20\n\n        # place ball in the middle\n        surface_width = pygame.display.get_surface().get_width()\n        surface_height = pygame.display.get_surface().get_height()\n\n        self.position = np.array([\n            surface_width / 2 - self.width / 2,\n            surface_height / 2 - self.height / 2\n        ])\n\n        self.acceleration = np.array([5, 0])\n\n    def update(self):\n        if self._collision_screen_left() or self._collision_screen_right():\n            self.acceleration *= -1\n\n        self.position += self.acceleration\n\n    def draw(self, surface):\n        pygame.draw.rect(\n            surface,\n            (255, 255, 0),\n            (self.position[0], self.position[1], self.width, self.height)\n        )\n\n    def _collision_screen_right(self):\n        surface_width = pygame.display.get_surface().get_width()\n        return self.position[0] + self.width \u003e= surface_width\n\n    def _collision_screen_left(self):\n        return self.position[0] \u003c= 0\n\n```\n\n\u003e game.py\n\n```python\nimport pygame\n\nfrom paddle import Paddle\nfrom ball import Ball\n\n\ndef main():\n    pygame.init()\n    screen = pygame.display.set_mode((800, 600))\n    pygame.display.set_caption(\"pypong\")\n\n    clock = pygame.time.Clock()\n\n    paddle1 = Paddle()\n    paddle2 = Paddle(760)\n\n    ball = Ball()\n\n    running = True\n    while running:\n        clock.tick(60)\n        screen.fill((0, 0, 0))\n\n        # handle input\n        for event in pygame.event.get():\n            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:\n                running = False\n\n        if pygame.key.get_pressed()[pygame.K_w]:\n            paddle1.move_up()\n\n        if pygame.key.get_pressed()[pygame.K_s]:\n            paddle1.move_down()\n\n        if pygame.key.get_pressed()[pygame.K_UP]:\n            paddle2.move_up()\n\n        if pygame.key.get_pressed()[pygame.K_DOWN]:\n            paddle2.move_down()\n\n        # update state\n        ball.update()\n\n        # draw objects\n        paddle1.draw(screen)\n        paddle2.draw(screen)\n        ball.draw(screen)\n\n        # refresh display\n        pygame.display.flip()\n\n\nif __name__ == '__main__':\n    main()\n\n```\n\n### NumPy and ball acceleration\n\nOkay, now that you applied the solution of the previous chapter, you might have some questions about NumPy.\n\nIn Python, we have lists. But in NumPy we have arrays. They are like lists but better because they are faster, have more possibilities to interact with and take less memory.\n\nA NumPy array is a table of elements (usually numbers), all of the same type, indexed by a tuple of positive integers. The number of dimensions is the rank of the array. The shape of an array is a tuple of integers giving the size of the array along each dimension.\n\nA NumPy array can be instantiated from nested Python lists, and elements can be accessed using square brackets:\n\n```python\nself.position = np.array([\n    surface_width / 2 - self.width / 2,\n    surface_height / 2 - self.height / 2\n])\n\n# ...\n\nreturn self.position[0] + self.width \u003e= surface_width\n```\n\nBasic mathematical functions operate elementwise on arrays, and are available both as operator overloads and as functions in the NumPy module.\n\nThat is why we can use a NumPy array as an acceleration vector and add it to our position vector simply with:\n\n```python\nself.position += self.acceleration\n```\n\nWhich is nothing but vector addition.\n\nAlso the direction change when there is a collision with a screen boundary is simply done with:\n\n```python\nself.acceleration *= -1\n```\n\nWhich is nothing but a vector scalar multiplicaton with -1 which reverses its direction.\n\nSo as you can see, NumPy arrays are really useful and makes things simple. It will also help us later then we need more advanced movement of the ball object.\n\n### Collision detection - theory and task\n\nWe now have two paddles and a ball. The ball knows when it hits the wall since it knows its own position and can access the screen width via PyGame.\n\nNow we need to detect the collision between the paddles and the ball. This chapter will explain the theory behind it. Your task will then be to implement this in our game class as it manages the paddle and ball instances.\n\nBut before we start with the fun part, let's jump into theory :-).\n\nWe will use AABB Collision Detection or **Axis-Aligned Bounding Box** collision detection as it stands for is the simplest form, or one of the simplest forms of collision detection that you can implement in a 2D game.\n\nAABB is a rectangular collision shape and only works if the shapes are axis-aligned. Being axis-aligned means the rectangular box is not rotated and its edges are parallel to the base axes of the surface. So perfect for our Pong clone :-).\n\nSo how do we determine if there is a collision? A collision occurs when two shapes enter each others regions e.g. the first rectangle is in some way inside the second retangle. For AABBs this is quite easy to determine due to the fact that they're aligned to the scenes axes.\n\nWe check for each axis if the two object edges on that axis overlap. So basically we check if the horizontal edges overlap and if the vertical edges overlap of both objects. If both the horizontal and vertical edges overlap we have a collision.\n\nOr explained in a more visual way:\n\n![collision detection](doc/collision_detection.png)\n\nThe following pseudo code shows how it can be implemented:\n\n```\ncollision_x = a.x + a.width \u003e= b.x AND b.x + b.width \u003e= a.x\ncollision_y = a.y + a.height \u003e= b.y AND b.y + b.height \u003e= a.y\n\ncollision = collision_x AND collision_y\n```\n\nYour task is to implement this collision detection in one or multiple functions in the `game.py` file.\n\nThe possible collisions are between `paddle1` and `ball` as well as `paddle2` and `ball`.\n\nIn addition use the built-in function `print` to print out a text to stdout whenever there is a collision which also indicates which objects collided.\n\nContinue with the next chapter once you are done.\n\n### Collision detecion - solution\n\nThis is one solution to the requirements mentioned before, please compare it to your solution.\n\nIt is recommended to replace your code with this solution as the following chapters are built on top of it.\n\n\u003e game.py\n\n```python\nimport pygame\n\nfrom paddle import Paddle\nfrom ball import Ball\n\n\ndef main():\n    pygame.init()\n    screen = pygame.display.set_mode((800, 600))\n    pygame.display.set_caption(\"pypong\")\n\n    clock = pygame.time.Clock()\n\n    paddle1 = Paddle()\n    paddle2 = Paddle(760)\n\n    ball = Ball()\n\n    running = True\n    while running:\n        clock.tick(60)\n        screen.fill((0, 0, 0))\n\n        # handle input\n        for event in pygame.event.get():\n            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:\n                running = False\n\n        if pygame.key.get_pressed()[pygame.K_w]:\n            paddle1.move_up()\n\n        if pygame.key.get_pressed()[pygame.K_s]:\n            paddle1.move_down()\n\n        if pygame.key.get_pressed()[pygame.K_UP]:\n            paddle2.move_up()\n\n        if pygame.key.get_pressed()[pygame.K_DOWN]:\n            paddle2.move_down()\n\n        # update state\n        ball.update()\n\n        if collision(paddle1, ball):\n            print(\"collision between paddle1 and ball\")\n\n        if collision(paddle2, ball):\n            print(\"collision between paddle2 and ball\")\n\n        # draw objects\n        paddle1.draw(screen)\n        paddle2.draw(screen)\n        ball.draw(screen)\n\n        # refresh display\n        pygame.display.flip()\n\n\ndef collision(paddle, ball):\n    return aabb_collision(\n        paddle.x,\n        paddle.y,\n        paddle.width,\n        paddle.height,\n        ball.position[0],\n        ball.position[1],\n        ball.width,\n        ball.height\n    )\n\n\ndef aabb_collision(a_x, a_y, a_width, a_height, b_x, b_y, b_width, b_height):\n    collision_x = a_x + a_width \u003e= b_x and b_x + b_width \u003e= a_x\n    collision_y = a_y + a_height \u003e= b_y and b_y + b_height \u003e= a_y\n\n    return collision_x and collision_y\n\n\nif __name__ == '__main__':\n    main()\n\n```\n\n### Ball movement - task\n\nWe are now able to detect if the ball collides with the left edge, the right edge or one of the paddles.\n\nNow we need to adjust the movement of the ball and we must also detect and react to collisions with the top and bottom edges of the surface.\n\nYour task now is to implement the correct behavior of the ball object based on the following rules:\n\n* **(1)** When the ball hits the left or right border, the angle of reflection is equal to the angle of incidence.\n* **(2)** When the ball hits the top or bottom border, the angle of reflection is equal to the angle of incidence.\n* **(3)** When the ball hits the paddle, the angle of reflection will depend on how far the ball is away from the center of the paddle when hitting it.\n\nFor the implementation, consider the following requirements:\n\n* When a collision is detected in `game.py` call a function in the `Ball` class instance that changes the movement accordingly.\n* Try to use NumPy functionality for the math part.\n* Try different versions of how much impact the distance to the center of the paddle has.\n\nContinue when you have a ball that bounces from all edges and the paddles according to the rules explained previously.\n\nThe following overview visualizes the cases with some examples:\n\n![ball movement](doc/ball_movement.png)\n\n### Ball movement - solution\n\nThis is one solution to the requirements mentioned before, please compare it to your solution.\n\nIt is recommended to replace your code with this solution as the following chapters are built on top of it.\n\n\u003e ball.py\n```python\nimport pygame\nimport numpy as np\n\n\nclass Ball:\n\n    def __init__(self):\n        self.width = 20\n        self.height = 20\n\n        # place ball in the middle\n        surface_width = pygame.display.get_surface().get_width()\n        surface_height = pygame.display.get_surface().get_height()\n\n        self.position = np.array([\n            surface_width / 2 - self.width / 2,\n            surface_height / 2 - self.height / 2\n        ])\n\n        self.acceleration = np.array([5, 1])\n\n    def update(self):\n        if self._collision_screen_left() or self._collision_screen_right():\n            self.acceleration = np.multiply(self.acceleration, np.array([-1, 1]))\n\n        if self._collision_screen_top() or self._collision_screen_bottom():\n            self.acceleration = np.multiply(self.acceleration, np.array([1, -1]))\n\n        self.position += self.acceleration\n\n    def accelerate(self, factor):\n        self.acceleration = np.multiply(self.acceleration, np.array([-1, 1]))\n        self.acceleration[1] = factor\n\n    def draw(self, surface):\n        pygame.draw.rect(\n            surface,\n            (255, 255, 0),\n            (self.position[0], self.position[1], self.width, self.height)\n        )\n\n    def _collision_screen_left(self):\n        return self.position[0] \u003c= 0\n\n    def _collision_screen_right(self):\n        surface_width = pygame.display.get_surface().get_width()\n        return self.position[0] + self.width \u003e= surface_width\n\n    def _collision_screen_top(self):\n        return self.position[1] \u003c= 0\n\n    def _collision_screen_bottom(self):\n        surface_height = pygame.display.get_surface().get_height()\n        return self.position[1] + self.height \u003e= surface_height\n\n```\n\n\u003e game.py\n\n```python\nimport pygame\n\nfrom paddle import Paddle\nfrom ball import Ball\n\n\ndef main():\n    pygame.init()\n    screen = pygame.display.set_mode((800, 600))\n    pygame.display.set_caption(\"pypong\")\n\n    clock = pygame.time.Clock()\n\n    paddle1 = Paddle()\n    paddle2 = Paddle(760)\n\n    ball = Ball()\n\n    running = True\n    while running:\n        clock.tick(60)\n        screen.fill((0, 0, 0))\n\n        # handle input\n        for event in pygame.event.get():\n            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:\n                running = False\n\n        if pygame.key.get_pressed()[pygame.K_w]:\n            paddle1.move_up()\n\n        if pygame.key.get_pressed()[pygame.K_s]:\n            paddle1.move_down()\n\n        if pygame.key.get_pressed()[pygame.K_UP]:\n            paddle2.move_up()\n\n        if pygame.key.get_pressed()[pygame.K_DOWN]:\n            paddle2.move_down()\n\n        # update state\n        ball.update()\n\n        if collision(paddle1, ball):\n            ball.accelerate(acc_factor(paddle1, ball))\n\n        if collision(paddle2, ball):\n            ball.accelerate(acc_factor(paddle2, ball))\n\n        # draw objects\n        paddle1.draw(screen)\n        paddle2.draw(screen)\n        ball.draw(screen)\n\n        # refresh display\n        pygame.display.flip()\n\n\ndef acc_factor(paddle, ball):\n    ball_center = ball.position[1] + ball.height / 2\n    paddle_center = paddle.y + paddle.height / 2\n\n    distance = ball_center - paddle_center\n\n    return distance / 10\n\n\ndef collision(paddle, ball):\n    return aabb_collision(\n        paddle.x,\n        paddle.y,\n        paddle.width,\n        paddle.height,\n        ball.position[0],\n        ball.position[1],\n        ball.width,\n        ball.height\n    )\n\n\ndef aabb_collision(a_x, a_y, a_width, a_height, b_x, b_y, b_width, b_height):\n    collision_x = a_x + a_width \u003e= b_x and b_x + b_width \u003e= a_x\n    collision_y = a_y + a_height \u003e= b_y and b_y + b_height \u003e= a_y\n\n    return collision_x and collision_y\n\n\nif __name__ == '__main__':\n    main()\n\n```\n\n### Make it a game\n\nNow that we have the basic elements, it is time to make a game out of this prototype.\n\nThis is where you should become creative and find your own way of how to add the missing elements. The goal at the end is to have the following features:\n\n* Paddles / players should have lives and if there is a collision between the ball and a left or right border, the respective player should loose a live.\n* Implement a new GUI class that is able to show the lives of the players as text in the game. To give you some inspiration, this is how it might look like:\n\n```python\nimport pygame\n\n\nclass PaddleGui:\n\n    def __init__(self, paddle, x=20):\n        self.paddle = paddle\n        self.x = x\n        self.font = pygame.font.Font('freesansbold.ttf', 18)\n\n    def draw(self, surface):\n        text = self.font.render(\n            'Lives: {}'.format(self.paddle.lives),\n            True,\n            (102, 255, 153)\n        )\n\n        surface.blit(text, (self.x, 10))\n\n```\n\n* The code in `game.py` should be turned into a `Game` class.\n* Using a specific key (e.g. `r`), the players should be able to reset the game.\n* When a player reaches 0 lives, the game should end and show a winner.\n* Add an optional computer player.\n\n## The final game\n\nCheck out the code in this repository to see the final result.\n\n## Machine Learning introduction with Pong\n\nPython is often used for machine learning purposes. Even though this project does not really require any kind of machine learning to create an artifical player, I would still like to take the chance and give you a short introduction to machine learning using this project.\n\nGenerally speaking machine learning is about building programs with tunable parameters that are adjusted automatically to improve their behavior by adapting to previously seen data.\n\nThe next chapters are meant to explain the basics of machine learning with the PyPong project as an example. It will not cover a step by step implementation guide as before but all code can be found in this repository.\n\n### Simple AI\n\nThe following snippet shows how a simple AI might look like without any machine learning applied:\n\n\u003e computer.py\n\n```python\nclass Computer:\n\n    def move(self, paddle, ball):\n        paddle_center = paddle.y + paddle.height / 2\n        ball_center = ball.position[1] + ball.height / 2\n\n        distance = abs(paddle_center - ball_center)\n\n        if distance \u003e 20 and paddle_center \u003e ball_center:\n            paddle.move_up()\n\n        if distance \u003e 20 and paddle_center \u003c ball_center:\n            paddle.move_down()\n\n```\n\nAs you can see, there is no need for machine learning as the problem is simple enough to hardcode the behavior:\n\n* If the center of the paddle is below the center of the ball, move the paddle up.\n* If the center of the paddle is above the center of the ball, move the paddle down.\n\nThat is already working fine. However now imagine that it is not that trivial to see this behavior right away. You just have some sample data and know how a player should behave in those situations.\n\nMachine learning can now be used to learn from that data and predict future actions based on observations. The next chapters will explain the implementation that you can see in `computer_sklearn.py`.\n\n### scikit-learn\n\nPython has lots of modules connected to the topic of AI and machine learning, however scikit-learn is one of the most famous machine learning libraries and offers you a lot of different options:\n\n* Classification: Identifying to which category an object belongs to.\n* Regression: Predicting a continuous-valued attribute associated with an object.\n* Clustering: Automatic grouping of similar objects into sets.\n* Dimensionality reduction: Reducing the number of random variables to consider.\n* Model selection: Comparing, validating and choosing parameters and models.\n* Preprocessing: Feature extraction and normalization.\n\n### Linear Regression and Logistic Regression\n\nLinear regression models are used to show or predict the relationship between variables or factors.\n\nIn our case, we have the y position of the paddle center and the y position of the ball center as variables and we want to know if the paddle should move up or down.\n\nLogistic regression is like linear regression but used when the dependent variable is binary in nature. That means when our prediction can only be 0 or 1, like in our case!\n\n### Training data\n\nWhat we are about to do is supervised learning. That means we have training data that we use to train the model so that it can predict future values.\n\nTraining data in our case means: we have two dimensional tuples that represent these features:\n\n* Center of the paddle on the Y axis.\n* Center of the ball on the Y axis.\n\nThe other part of the training data are the target values, so did the paddle move up (0) or down (1).\n\nUsing `scikit-learn` we can train a Logistic Regression model like this:\n\n```python\nfrom sklearn.linear_model import LogisticRegression\n\n\nclass Computer:\n\n    def __init__(self):\n        # features: y position of paddle center, y position of ball center\n        training_data = [[50, 150], [400, 200], [100, 110], [210, 190]]\n\n        # 0 = move up, 1 = move down\n        target_values = [1, 0, 1, 0]\n\n        self.model = LogisticRegression()\n        self.model.fit(training_data, target_values)\n\n```\n\n### Predict actions\n\nNow that we have a trained model, we can use it to predict actions based on new observations:\n\n```python\n    def move(self, paddle, ball):\n        paddle_center = paddle.y + paddle.height / 2\n        ball_center = ball.position[1] + ball.height / 2\n\n        prediction = self.model.predict([[paddle_center, ball_center]])\n        print('input: [{}, {}], prediction: {}'.format(paddle_center, ball_center, prediction))\n\n        if prediction == 0:\n            paddle.move_up()\n\n        if prediction == 1:\n            paddle.move_down()\n\n```\n\nThe resulting AI (`computer_sklearn.py`) is acting similar to the hardcoded behavior in `computer.py`.\n\n### More theory and data visualization\n\nEven though the AI is already working, let us have a closer look to get a better understanding of what happens under the hood.\n\nShort recap: Our training data has two features: the y position of the paddle center and the y position of the ball center. The target value is either 0 or 1 whereas 0 means: move the paddle up and 1 means: move the paddle down.\n\nWe trained a logistic regression model so that it is able to predict the action (0 or 1) for new input tuples (i.e. paddle and ball positions).\n\nIn order to demystify this process, I added a simple visualization. If you want to try it on your own, just go to `computer_sklearn.py` and uncomment the following line:\n\n```python\nself._visualize_data_relationship()\n```\n\nWhen you now start the application, you will see the following graph:\n\n![data relationship](doc/data_relationship.png)\n\nThese are 1000 predictions of the model for random paddle and ball positions. Blue dots indicate that the model predicted to move the paddle up and magenta dots indicate that the model predicted to move the paddle down.\n\nAs you can see, our model found a way to clearly separate those two clusters: whenever the y position of the ball position is bigger than the y position of the paddle, the model predicts: move up. Otherwise it predicts: move down.\n\nOr in other words, the model found a function, that is giving us this result.\n\nHowever keep in mind: we are doing a logistic regression, that means we either get 0 or 1 as an output. This is usually done by applying a sigmoid function to the output of the underlying function of the model.\n\nHave a look at the following graph to get an idea what the effect of that function is:\n\n![sigmoid](doc/sigmoid.png)\n\nAs you can see, the sigmoid function returns 0 for smaller values and 1 for higher values. That means our model found a function that behaves in a way so that the result is low when the position value of the paddle is lower than the position value of the ball. \n\nLikewise this function returns a high value, when the position value of the paddle is higher than the position value of the ball.\n\nThe good thing is: we can even get this function from our model to get a better understanding how it looks like.\n\nTo do this, we can just use the following snippet after training our model:\n\n```python\nself.model = LogisticRegression()\nself.model.fit(training_data, target_values)\n\n# prediction function used for linear regression\nprint('f(paddle_center, ball_center) = {} + {} * paddle_center + {} * ball_center'.format(\n    round(self.model.intercept_[0], 4),\n    round(self.model.coef_[0][0], 4),\n    round(self.model.coef_[0][1], 4)\n))\n```\n\nWhich will give us the following output:\n\n```python\nf(paddle_center, ball_center) = 0.0124 + -0.2783 * paddle_center + 0.2853 * ball_center\n```\n\nThis is a simple function and we can even try it on our own with some data.\n\nLet us assume our paddle is at position 10 and the ball is at position 200. That means our paddle is above the ball and to get it, we need to move down. So what we expect is that the function returns a high value so that the sigmoid function turns it into a 1 which means: move down.\n\n```\npaddle_center = 10\nball_center = 200\n\n0.0124 - 0.2783 * 10 + 0.2853 * 200 = 54.2894\nsigmoid(54.2894) = 1 = down\n```\n\nNow let us turn it around. Our paddle is at position 400 and the ball is at position 30. So we would need to move up.\n\n```\npaddle_center = 400\nball_center = 30\n\n0.0124 - 0.2783 * 400 + 0.2853 * 30 = -102.7486\nsigmoid(-102.7486) = 0 = up\n```\n\nAs you can see: it works and this is what our model is doing all day long when we play the game ;-).\n\n## Fin\n\nUsing machine learning in this case does not really make sense but it is a good and simple example to understand the basics and how to implement a simple model.\n\nYou can switch between the hardcoded and machine learning AI by just changing the import statement in `game.py`.\n\nI hope this guide motivates you to look deeper into this topic. A good start is to go for another game and start from the scratch: implement the game logic and then use a machine learning model to create an AI.\n\nTo get some more inspiration: check out my FlapAI project on Github which is basically a Flappy Bird clone with JavaScript combined with an AI implementation.\n\nThe AI combines Neural Networks using TensorFlow.js (https://www.tensorflow.org/js) with evolutionary algorithms to evolve populations of AI players using genetic operators.\n\nSee: https://github.com/prenomenon/flapai\n\nHave fun and good luck :-).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvojay-dev%2Fpypong","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvojay-dev%2Fpypong","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvojay-dev%2Fpypong/lists"}