From 05fdb9136454e025c7780e7d7cdc4db21362f5af Mon Sep 17 00:00:00 2001 From: Pedro Rey Anca Date: Fri, 18 Oct 2024 20:48:09 +0200 Subject: [PATCH] Initial commit --- .gitignore | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++ animation.py | 81 ++++++++++++++++++++++++ main.py | 78 +++++++++++++++++++++++ 3 files changed, 335 insertions(+) create mode 100644 .gitignore create mode 100644 animation.py create mode 100644 main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad4a1f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,176 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python diff --git a/animation.py b/animation.py new file mode 100644 index 0000000..3362925 --- /dev/null +++ b/animation.py @@ -0,0 +1,81 @@ +import matplotlib.pyplot as plt +import matplotlib.animation as animation +from time import sleep +import copy + +TOWER_NAMES = ['A', 'B', 'C'] + + +def move_and_save(src, dest, towers, states): + disk = towers[src].pop() + towers[dest].append(disk) + states.append(((disk, src, dest), copy.deepcopy(towers))) + + +def solve_game(n, src, dest, aux, towers, states): + if n == 1: + move_and_save(src, dest, towers, states) + else: + solve_game(n - 1, src, aux, dest, towers, states) + move_and_save(src, dest, towers, states) + solve_game(n - 1, aux, dest, src, towers, states) + + +def animate_hanoi(n, frame_delay=0.5, src=0, dest=1, aux=2): + colors = [] + for i in range(n): + # Calculate the hue value + hue = i / n + # Convert HSL to RGB + rgb = plt.cm.hsv(hue)[:3] # Use HSV colormap and take RGB values + colors.append(rgb) + + # Create the towers + towers = [[], [], []] + for i in range(n): + towers[src].append(n - i) + # Solve it + states = [(None, copy.deepcopy(towers))] + solve_game(n, src, dest, aux, towers, states) + + fig, ax = plt.subplots() + fig.suptitle(f"Towers of Hanoi simulation for n={n}", fontsize=21) + ax.set_xlim(-1, 3) + ax.set_ylim(0, n + 1) + ax.set_xticks([0, 1, 2]) + ax.set_xticklabels(TOWER_NAMES) + ax.set_yticks([]) + + plot_elements = [] + + def update(frame): + move, towers = states[frame] + + # Clear previous plot + for el in plot_elements: + el.remove() + plot_elements.clear() + + # Draw the disks + for i, tower in enumerate(towers): + for j, disk in enumerate(tower): + plot_elements.append(ax.bar(i, 1, bottom=j, + width=0.5, color=colors[disk-1])) + plot_elements.append(ax.text(i, j + 0.5, str(disk), ha='center', + va='center', color='white', fontsize=16)) + + sleep(frame_delay) # Delay the frame + if move: + print( + f"🟦 Move {frame}: Disk ({move[0]}) on tower {TOWER_NAMES[move[1]]} to tower {TOWER_NAMES[move[2]]}") + return plot_elements + + ani = animation.FuncAnimation( + fig, update, frames=len(states), repeat=False) + plt.show() + + +if __name__ == "__main__": + n = int(input("❔ Number of disks in the game: ")) + d = int(input("❔ Delay between frames (in seconds): ")) + animate_hanoi(n, d) diff --git a/main.py b/main.py new file mode 100644 index 0000000..e2f5f19 --- /dev/null +++ b/main.py @@ -0,0 +1,78 @@ +class TowersOfHanoiGame(): + def __init__(self, n): + self.n = n + self.towers = [list(range(n-1, -1, -1)), [], []] + self.print_state() + + self.moves = 0 # Counter of moves used to solve the puzle + + def print_state(self): + print('-'*7) + + for i in range(n-1, -1, -1): + for tower in self.towers: + try: + print(tower[i], end='') + except IndexError: + print('#', end='') # Blank space instead of tower piece + + print(" ", end='') # Separate the towers + + print() # New row + + print('-'*7) + + def move(self, t1i, t2i): + t1 = self.towers[t1i] + t2 = self.towers[t2i] + if t1 != [] and (t2 == [] or t1[-1] < t2[-1]): + self.moves += 1 + print( + f"🟦 Move {self.moves}: Disk ({t1[-1]}) on tower {t1i} to tower {t2i}") + t2.append(t1.pop()) + else: + print( + f"❗ Illegal move, tried to move the disk on tower {t1i} to tower {t2i}") + return False + + return True + + def check_win(self): + if len(self.towers[1]) == self.n or len(self.towers[2]) == self.n: + print(f"🎉 You won in {self.moves} moves!!") + if (self.moves == 2**self.n-1): + print(f"🤩 And that was the best possible solution!!") + else: + print(f"😔 But that was not the best possible solution :(") + return True + return False + + +def solve_game(game, n, src, dest, aux): + if n == 1: + game.move(src, dest) + game.print_state() + return + solve_game(game, n-1, src, aux, dest) + game.move(src, dest) + game.print_state() + solve_game(game, n-1, aux, dest, src) + + +if __name__ == "__main__": + n = int(input("❔ Number of disks in the game: ")) + game = TowersOfHanoiGame(n) + what_to_do = input("❔ Play yourself (p) or let the bot solve it (b)?: ") + if what_to_do == 'p': + while True: + t1i, t2i = [int(i) for i in input( + "❔ Enter your move (tower1tower2): ").split()] + game.move(t1i, t2i) + game.print_state() + if game.check_win(): + break + elif what_to_do == 'b': + solve_game(game, n, 0, 1, 2) + game.check_win() + else: + print("❗ Incorrect option")