From 56120be3e7271a707fa43c8ff31a07767f048b4a Mon Sep 17 00:00:00 2001 From: farbian_2005 Date: Wed, 3 Jul 2024 20:43:32 +0000 Subject: [PATCH] first commit --- .devcontainer/devcontainer.json | 25 +++++ .github/dependabot.yml | 12 +++ .github/workflows/rock-paper-siccors.yml | 37 +++++++ .gitignore | 124 +++++++++++++++++++++++ README.md | 45 ++++++++ RPS.py | 45 ++++++++ RPS_game.py | 122 ++++++++++++++++++++++ main.py | 27 +++++ pyproject.toml | 16 +++ test_module.py | 38 +++++++ 10 files changed, 491 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/rock-paper-siccors.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 RPS.py create mode 100644 RPS_game.py create mode 100644 main.py create mode 100644 pyproject.toml create mode 100644 test_module.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..0266351 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,25 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python +{ + "name": "Python 3", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye", + "features": { + "ghcr.io/devcontainers-contrib/features/poetry:2": {} + } + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "pip3 install --user -r requirements.txt", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f33a02c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for more information: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://containers.dev/guide/dependabot + +version: 2 +updates: + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: weekly diff --git a/.github/workflows/rock-paper-siccors.yml b/.github/workflows/rock-paper-siccors.yml new file mode 100644 index 0000000..360e2c4 --- /dev/null +++ b/.github/workflows/rock-paper-siccors.yml @@ -0,0 +1,37 @@ +name: CI +on: + push: + branches: + - master + pull_request: + +jobs: + setup: + runs-on: ubuntu-latest + defaults: + run: + working-directory: src/machine-learning-with-python/rock-paper-scissors + steps: + - uses: actions/checkout@v4 + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + - name: Install poetry + with: + poetry-version: latest + uses: abatilo/actions-poetry@v2 + - name: Setup a local virtual environment (if no poetry.toml file) + run: | + poetry config virtualenvs.create true --local + poetry config virtualenvs.in-project true --local + - uses: actions/cache@v3 + name: Define a cache for the virtual environment based on the dependencies lock file + with: + path: ./.venv + key: venv-${{ hashFiles('poetry.lock') }} + - name: Install the project dependencies + run: poetry install + - name: run pytest + run: poetry run pytest -v + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1bd5406 --- /dev/null +++ b/.gitignore @@ -0,0 +1,124 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Distribution / packaging +.Python +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +.build/ +dist/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Poetry specific +poetry.lock +.cache/ +.pdm.toml +.pdm-python/ +.pdm.lock + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ +nosetests.xml +coverage/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.env.* +.venv +.venv/ +.env/ +env/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Profiling data +.prof + +# pytype static type analyzer +.pytype/ + +# PyCharm +.idea/ + +# Visual Studio Code +.vscode/ + +# Local history for IntelliJ-based IDEs +.idea + +# macOS +.DS_Store + +# Windows +Thumbs.db +ehthumbs.db + +# Windows 10 upgrade +$WINDOWS.~BT/ +$RECYCLE.BIN/ + +# Windows 10 set-up files +*.cab +*.msu +*.regtrans-ms +*.jrs +*.apx + +# Linux +*.lock +*~ + +# Logs +logs/ +*.log + +# Temp files +*.tmp +*.temp +tmp/ +temp/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..a0b78d6 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +### Future Plans + +- [ ] Do it in a better version with sklearn. + + +### Assignment + +For this challenge, you will create a program to play Rock, Paper, Scissors. A program that picks at random will usually win 50% of the time. To pass this challenge your program must play matches against four different bots, winning at least 60% of the games in each match. + +In the file `RPS.py` you are provided with a function called `player`. The function takes an argument that is a string describing the last move of the opponent ("R", "P", or "S"). The function should return a string representing the next move for it to play ("R", "P", or "S"). + +A player function will receive an empty string as an argument for the first game in a match since there is no previous play. + +The file `RPS.py` shows an example function that you will need to update. The example function is defined with two arguments (`player(prev_play, opponent_history = [])`). The function is never called with a second argument so that one is completely optional. The reason why the example function contains a second argument (`opponent_history = []`) is because that is the only way to save state between consecutive calls of the `player` function. You only need the `opponent_history` argument if you want to keep track of the opponent_history. + +*Hint: To defeat all four opponents, your program may need to have multiple strategies that change depending on the plays of the opponent.* + +### Development + +Do not modify `RPS_game.py`. Write all your code in `RPS.py`. For development, you can use `main.py` to test your code. + +`main.py` imports the game function and bots from `RPS_game.py`. + +To test your code, play a game with the `play` function. The `play` function takes four arguments: +- two players to play against each other (the players are actually functions) +- the number of games to play in the match +- an optional argument to see a log of each game. Set it to `True` to see these messages. + +```py +play(player1, player2, num_games[, verbose]) +``` +For example, here is how you would call the function if you want `player` and `quincy` to play 1000 games against each other and you want to see the results of each game: +```py +play(player, quincy, 1000, verbose=True) +``` + +Click the "run" button and `main.py` will run. + +### Testing + +The unit tests for this project are in `test_module.py`. We imported the tests from `test_module.py` to `main.py` for your convenience. If you uncomment the last line in `main.py`, the tests will run automatically whenever you hit the "run" button. + +### Submitting + +Copy your project's URL and submit it to freeCodeCamp. diff --git a/RPS.py b/RPS.py new file mode 100644 index 0000000..73060da --- /dev/null +++ b/RPS.py @@ -0,0 +1,45 @@ +def player(prev_play, opponent_history=[]): + opponent_history.append(prev_play) + guess: str = 'S' + if not prev_play: + opponent_history.clear() + return guess + + rock_count: int = 0 + paper_count: int = 0 + scissors_count: int = 0 + for i in range(len(opponent_history) - 1): + if opponent_history[i] == prev_play: + next = opponent_history[i + 1] + if next == 'R': + rock_count += 1 + if next == 'P': + paper_count += 1 + if next == 'S': + scissors_count += 1 + + rock_guess: int = scissors_count + paper_guess: int = rock_count + scissors_guess: int = paper_count + + max_guess = max(rock_guess, paper_guess, scissors_guess) + if max_guess == rock_guess: + guess = 'R' + if max_guess == paper_guess: + guess = 'P' + if max_guess == scissors_guess: + guess = 'S' + + big_brain = { + 'P': 'R', + 'R': 'S', + 'S': 'P', + } + if len(opponent_history) > 2: + if opponent_history[-1] == opponent_history[-2] != opponent_history[-3]: + guess = big_brain[prev_play] + + if rock_count == paper_count == scissors_count == 0: + guess = prev_play + + return guess diff --git a/RPS_game.py b/RPS_game.py new file mode 100644 index 0000000..147a982 --- /dev/null +++ b/RPS_game.py @@ -0,0 +1,122 @@ +# DO NOT MODIFY THIS FILE + +import random + + +def play(player1, player2, num_games, verbose=False): + p1_prev_play = "" + p2_prev_play = "" + results = {"p1": 0, "p2": 0, "tie": 0} + + for _ in range(num_games): + p1_play = player1(p2_prev_play) + p2_play = player2(p1_prev_play) + + if p1_play == p2_play: + results["tie"] += 1 + winner = "Tie." + elif (p1_play == "P" and p2_play == "R") or ( + p1_play == "R" and p2_play == "S") or (p1_play == "S" + and p2_play == "P"): + results["p1"] += 1 + winner = "Player 1 wins." + elif p2_play == "P" and p1_play == "R" or p2_play == "R" and p1_play == "S" or p2_play == "S" and p1_play == "P": + results["p2"] += 1 + winner = "Player 2 wins." + + if verbose: + print("Player 1:", p1_play, "| Player 2:", p2_play) + print(winner) + print() + + p1_prev_play = p1_play + p2_prev_play = p2_play + + games_won = results['p2'] + results['p1'] + + if games_won == 0: + win_rate = 0 + else: + win_rate = results['p1'] / games_won * 100 + + print("Final results:", results) + print(f"Player 1 win rate: {win_rate}%") + + return (win_rate) + + +def quincy(prev_play, counter=[0]): + + counter[0] += 1 + choices = ["R", "R", "P", "P", "S"] + return choices[counter[0] % len(choices)] + + +def mrugesh(prev_opponent_play, opponent_history=[]): + opponent_history.append(prev_opponent_play) + last_ten = opponent_history[-10:] + most_frequent = max(set(last_ten), key=last_ten.count) + + if most_frequent == '': + most_frequent = "S" + + ideal_response = {'P': 'S', 'R': 'P', 'S': 'R'} + return ideal_response[most_frequent] + + +def kris(prev_opponent_play): + if prev_opponent_play == '': + prev_opponent_play = "R" + ideal_response = {'P': 'S', 'R': 'P', 'S': 'R'} + return ideal_response[prev_opponent_play] + + +def abbey(prev_opponent_play, + opponent_history=[], + play_order=[{ + "RR": 0, + "RP": 0, + "RS": 0, + "PR": 0, + "PP": 0, + "PS": 0, + "SR": 0, + "SP": 0, + "SS": 0, + }]): + + if not prev_opponent_play: + prev_opponent_play = 'R' + opponent_history.append(prev_opponent_play) + + last_two = "".join(opponent_history[-2:]) + if len(last_two) == 2: + play_order[0][last_two] += 1 + + potential_plays = [ + prev_opponent_play + "R", + prev_opponent_play + "P", + prev_opponent_play + "S", + ] + + sub_order = { + k: play_order[0][k] + for k in potential_plays if k in play_order[0] + } + + prediction = max(sub_order, key=sub_order.get)[-1:] + + ideal_response = {'P': 'S', 'R': 'P', 'S': 'R'} + return ideal_response[prediction] + + +def human(prev_opponent_play): + play = "" + while play not in ['R', 'P', 'S']: + play = input("[R]ock, [P]aper, [S]cissors? ") + print(play) + return play + + +def random_player(prev_opponent_play): + return random.choice(['R', 'P', 'S']) diff --git a/main.py b/main.py new file mode 100644 index 0000000..b5cb2b0 --- /dev/null +++ b/main.py @@ -0,0 +1,27 @@ +# This entrypoint file to be used in development. Start by reading README.md +from RPS_game import play, mrugesh, abbey, quincy, kris, human, random_player +from RPS import player +from unittest import main + + + +# Uncomment line below to play interactively against a bot: +# play(human, abbey, 20, verbose=True) + +# Uncomment line below to play against a bot that plays randomly: +# play(human, random_player, 1000) + + +# Uncomment line below to run unit tests automatically +# main(module='test_module', exit=False) + +def main(): + play(player, quincy, 1000) + play(player, abbey, 1000) + play(player, kris, 1000) + play(player, mrugesh, 1000) + + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b6eac46 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.poetry] +name = "rock-paper-scissors" +version = "0.1.0" +description = "" +authors = ["farbian_2005 "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" + +[tool.poetry.scripts] +main = "main:main" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/test_module.py b/test_module.py new file mode 100644 index 0000000..c9e31f0 --- /dev/null +++ b/test_module.py @@ -0,0 +1,38 @@ +import unittest +from RPS_game import play, mrugesh, abbey, quincy, kris, human +from RPS import player + + +class UnitTests(unittest.TestCase): + print() + + def test_player_vs_quincy(self): + print("Testing game against quincy...") + actual = play(player, quincy, 1000) >= 60 + self.assertTrue( + actual, + 'Expected player to defeat quincy at least 60% of the time.') + + def test_player_vs_abbey(self): + print("Testing game against abbey...") + actual = play(player, abbey, 1000) >= 60 + self.assertTrue( + actual, + 'Expected player to defeat abbey at least 60% of the time.') + + def test_player_vs_kris(self): + print("Testing game against kris...") + actual = play(player, kris, 1000) >= 60 + self.assertTrue( + actual, 'Expected player to defeat kris at least 60% of the time.') + + def test_player_vs_mrugesh(self): + print("Testing game against mrugesh...") + actual = play(player, mrugesh, 1000) >= 60 + self.assertTrue( + actual, + 'Expected player to defeat mrugesh at least 60% of the time.') + + +if __name__ == "__main__": + unittest.main()