~piotr-machura/python-miniprojects

b5646b056ea00b2317d0947a36539076d4c42fff — Piotr Machura 2 years ago fcd1696
Addicted to pylint's score, had to refactor everything
12 files changed, 194 insertions(+), 184 deletions(-)

D collatz-gui/__init__.py
D collatz-gui/app.py
A collatz/__init__.py
A collatz/app.py
R collatz-gui/dialog.py => collatz/dialog.py
R collatz-gui/icon.svg => collatz/icon.svg
R collatz-gui/main.py => collatz/main.py
R collatz-gui/validator.py => collatz/validator.py
D tic-tac-toe/main.py
R tic-tac-toe/__init__.py => tictactoe/__init__.py
R tic-tac-toe/tictactoe.py => tictactoe/game.py
A tictactoe/main.py
D collatz-gui/__init__.py => collatz-gui/__init__.py +0 -2
@@ 1,2 0,0 @@
""" This is a samll GUI for checking the Collatz conjecture on your favourite natural numbers and seeing the steps performed to reach 1. It utilizes the Qt5 framework.
"""

D collatz-gui/app.py => collatz-gui/app.py +0 -132
@@ 1,132 0,0 @@
import os
import webbrowser
from PyQt5 import QtCore, QtGui, QtWidgets
from validator import IntValidator
from dialog import BrowserDialog


class CollatzApp(QtWidgets.QMainWindow):
    """Main app window."""

    def __init__(self):
        super().__init__()
        self.setWindowTitle(' Collatz')
        self.setFixedSize(550, 700)
        scriptDir = os.path.dirname(os.path.realpath(__file__))
        self.setWindowIcon(QtGui.QIcon(scriptDir + os.path.sep + 'icon.svg'))

        # Create start button
        self.startButton = QtWidgets.QPushButton('Start', self)
        self.startButton.clicked.connect(self._on_click_start)

        # Create textbox for number input
        self.inputBox = QtWidgets.QLineEdit(self)
        self.inputBox.setValidator(IntValidator(bottom=1))
        # Enter to press 'Start' button
        self.inputBox.returnPressed.connect(self.startButton.click)
        # Set textbox font
        self.inputBox.font()
        f = self.inputBox.font()
        f.setPointSize(14)
        self.inputBox.setFont(f)

        # Create 'Steps' label
        self.stepsLabel = QtWidgets.QLabel(self)
        self.stepsLabel.setText('Steps')
        self.stepsLabel.setAlignment(QtCore.Qt.AlignCenter)
        f = self.stepsLabel.font()
        f.setPointSize(10)
        self.stepsLabel.setFont(f)

        # Create textarea for iterations display
        self.stepsDisplay = QtWidgets.QPlainTextEdit(self)
        f = self.stepsDisplay.font()
        f.setPointSize(12)
        self.stepsDisplay.setFont(f)
        # Set tab width to 4 spaces
        self.stepsDisplay.setTabStopDistance(
            QtGui.QFontMetricsF(f).horizontalAdvance(' ') * 4)
        self.stepsDisplay.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap)
        self.stepsDisplay.setReadOnly(True)

        # Create clear button
        self.clearButton = QtWidgets.QPushButton('Clear', self)
        self.clearButton.clicked.connect(self._on_click_clear)

        # Create info button
        self.infoButton = QtWidgets.QPushButton('Info', self)
        self.infoButton.clicked.connect(self._on_click_info)

        # Add widgets to window
        hBoxTop = QtWidgets.QHBoxLayout()
        hBoxTop.addWidget(self.inputBox)
        hBoxTop.addWidget(self.startButton)
        vBox = QtWidgets.QVBoxLayout()
        vBox.addLayout(hBoxTop)
        vBox.addWidget(self.stepsLabel)
        vBox.addWidget(self.stepsDisplay)
        hBoxBottom = QtWidgets.QHBoxLayout()
        hBoxBottom.addStretch()
        hBoxBottom.addWidget(self.clearButton)
        hBoxBottom.addWidget(self.infoButton)
        vBox.addLayout(hBoxBottom)
        # Central widget
        central = QtWidgets.QWidget(self)
        central.setLayout(vBox)
        self.setCentralWidget(central)
        self.show()
        self.inputBox.setFocus()

    def _sequence(self, number):
        """Perform a recursive check with the Collatz sequence and print each step to `stepsDisplay`.

        Raises `Exception` and terminates if the `number` is 1.
        """

        self.stepsDisplay.insertPlainText(str(number))
        if number == 1:
            self.stepsDisplay.insertPlainText('\n')
            raise Exception()
        else:
            self.counter += 1
            if number % 2 == 0:
                self.stepsDisplay.insertPlainText('\t| /2\n')
                self._sequence(int(number / 2))
            else:
                self.stepsDisplay.insertPlainText('\t| *3 + 1\n')
                self._sequence(int(3 * number + 1))

    @ QtCore.pyqtSlot()
    def _on_click_start(self):
        """Clear the `stepsDisplay` and start the Collatz sequence."""

        self.stepsDisplay.clear()
        try:
            textboxValue = int(self.inputBox.text())
        except Exception:
            # Empty input - ignore
            return
        try:
            self.counter = 0
            self._sequence(textboxValue)
        except Exception:
            self.stepsDisplay.insertPlainText(
                f'Terminated after {self.counter} iterations.')
            self.stepsDisplay.moveCursor(QtGui.QTextCursor.End)

    @ QtCore.pyqtSlot()
    def _on_click_clear(self):
        """Clear the `inputBox` and `stepsDisplay`."""

        self.inputBox.clear()
        self.stepsDisplay.clear()
        self.inputBox.setFocus()

    @ QtCore.pyqtSlot()
    def _on_click_info(self):
        """Open a dialog window to acces the relevant Wikipedia article."""

        if BrowserDialog(self).exec():
            webbrowser.open('https://en.wikipedia.org/wiki/Collatz_conjecture')
        else:
            self.inputBox.setFocus()

A collatz/__init__.py => collatz/__init__.py +4 -0
@@ 0,0 1,4 @@
""" This is a samll GUI for checking the Collatz conjecture on your
favourite natural numbers and seeing the steps performed to reach 1.
It utilizes the Qt5 framework.
"""

A collatz/app.py => collatz/app.py +132 -0
@@ 0,0 1,132 @@
"""This module contains the main app window"""
import os
import webbrowser
from PyQt5 import QtCore, QtGui, QtWidgets
from validator import IntValidator
from dialog import BrowserDialog


class CollatzApp(QtWidgets.QMainWindow):
    """Main app window."""

    def __init__(self):
        super().__init__()
        self.setWindowTitle(' Collatz')
        self.setFixedSize(550, 700)
        dir_name = os.path.dirname(os.path.realpath(__file__))
        self.setWindowIcon(QtGui.QIcon(dir_name + os.path.sep + 'icon.svg'))
        self.counter = 0

        # Create start button
        self.start_button = QtWidgets.QPushButton('Start', self)
        self.start_button.clicked.connect(self._on_click_start)

        # Create textbox for number input
        self.input_box = QtWidgets.QLineEdit(self)
        self.input_box.setValidator(IntValidator(bottom=1))
        # Enter to press 'Start' button
        self.input_box.returnPressed.connect(self.start_button.click)
        # Set textbox font
        self.input_box.font()
        font_ = self.input_box.font()
        font_.setPointSize(14)
        self.input_box.setFont(font_)

        # Create 'Steps' label
        self.steps_label = QtWidgets.QLabel(self)
        self.steps_label.setText('Steps')
        self.steps_label.setAlignment(QtCore.Qt.AlignCenter)
        font_ = self.steps_label.font()
        font_.setPointSize(10)
        self.steps_label.setFont(font_)

        # Create textarea for iterations display
        self.steps_display = QtWidgets.QPlainTextEdit(self)
        font_ = self.steps_display.font()
        font_.setPointSize(12)
        self.steps_display.setFont(font_)
        # Set tab width to 4 spaces
        self.steps_display.setTabStopDistance(
            QtGui.QFontMetricsF(font_).horizontalAdvance(' ') * 4)
        self.steps_display.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap)
        self.steps_display.setReadOnly(True)

        # Create clear button
        self.clear_button = QtWidgets.QPushButton('Clear', self)
        self.clear_button.clicked.connect(self._on_click_clear)

        # Create info button
        self.info_button = QtWidgets.QPushButton('Info', self)
        self.info_button.clicked.connect(self._on_click_info)

        # Add widgets to window
        h_box_top = QtWidgets.QHBoxLayout()
        h_box_top.addWidget(self.input_box)
        h_box_top.addWidget(self.start_button)
        v_box = QtWidgets.QVBoxLayout()
        v_box.addLayout(h_box_top)
        v_box.addWidget(self.steps_label)
        v_box.addWidget(self.steps_display)
        h_box_bottom = QtWidgets.QHBoxLayout()
        h_box_bottom.addStretch()
        h_box_bottom.addWidget(self.clear_button)
        h_box_bottom.addWidget(self.info_button)
        v_box.addLayout(h_box_bottom)
        # Central widget
        central = QtWidgets.QWidget(self)
        central.setLayout(v_box)
        self.setCentralWidget(central)
        self.show()
        self.input_box.setFocus()

    def _sequence(self, number):
        """Perform a recursive check with the Collatz sequence
        and print each step to `stepsDisplay`.

        Raises `Exception` and terminates if the `number` is 1.
        """

        self.steps_display.insertPlainText(str(number))
        if number == 1:
            self.steps_display.insertPlainText('\n')
        else:
            self.counter += 1
            if number % 2 == 0:
                self.steps_display.insertPlainText('\t| /2\n')
                self._sequence(int(number / 2))
            else:
                self.steps_display.insertPlainText('\t| *3 + 1\n')
                self._sequence(int(3 * number + 1))

    @ QtCore.pyqtSlot()
    def _on_click_start(self):
        """Clear the `stepsDisplay` and start the Collatz sequence."""

        self.steps_display.clear()
        try:
            input_value = int(self.input_box.text())
        except ValueError:
            # Empty input - ignore
            return
        self.counter = 0
        self._sequence(input_value)
        self.steps_display.insertPlainText(
            f'Terminated after {self.counter} iterations.')
        self.steps_display.moveCursor(QtGui.QTextCursor.End)

    @ QtCore.pyqtSlot()
    def _on_click_clear(self):
        """Clear the `inputBox` and `stepsDisplay`."""

        self.input_box.clear()
        self.steps_display.clear()
        self.input_box.setFocus()

    @ QtCore.pyqtSlot()
    def _on_click_info(self):
        """Open a dialog window to acces the relevant Wikipedia article."""

        if BrowserDialog(self).exec():
            webbrowser.open('https://en.wikipedia.org/wiki/Collatz_conjecture')
        else:
            self.input_box.setFocus()

R collatz-gui/dialog.py => collatz/dialog.py +8 -6
@@ 1,9 1,11 @@
"""This module contains the custom dialog box."""
import os
from PyQt5 import QtGui, QtWidgets, QtCore


class BrowserDialog(QtWidgets.QDialog):
    """ This is a custom dialog box for opening the Collatz conjecture Wikipedia page in a web browser.
    """ This is a custom dialog box for opening the Collatz
    conjecture Wikipedia page in a web browser.
    """

    def __init__(self, *args, **kwargs):


@@ 14,10 16,10 @@ class BrowserDialog(QtWidgets.QDialog):
            os.path.dirname(__file__) + '/icon.svg'))

        # Buttons
        QBtn = QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
        self.buttonBox = QtWidgets.QDialogButtonBox(QBtn)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)
        q_btn = QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
        self.button_box = QtWidgets.QDialogButtonBox(q_btn)
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)

        self.message = QtWidgets.QLabel(
            'This will open your browser.\nContinue?', self)


@@ 25,5 27,5 @@ class BrowserDialog(QtWidgets.QDialog):

        self.layout = QtWidgets.QVBoxLayout()
        self.layout.addWidget(self.message)
        self.layout.addWidget(self.buttonBox)
        self.layout.addWidget(self.button_box)
        self.setLayout(self.layout)

R collatz-gui/icon.svg => collatz/icon.svg +0 -0
R collatz-gui/main.py => collatz/main.py +4 -3
@@ 1,11 1,12 @@
"""This is the __main__ module."""
import sys
import os
from app import CollatzApp
from PyQt5 import QtWidgets, QtGui
from app import CollatzApp

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    scriptDir = os.path.dirname(os.path.realpath(__file__))
    app.setWindowIcon(QtGui.QIcon(scriptDir + os.path.sep + 'icon.svg'))
    dir_name = os.path.dirname(os.path.realpath(__file__))
    app.setWindowIcon(QtGui.QIcon(dir_name + os.path.sep + 'icon.svg'))
    ex = CollatzApp()
    app.exec()

R collatz-gui/validator.py => collatz/validator.py +6 -3
@@ 1,9 1,11 @@
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
"""This module contains the arbitraryly big int validator."""
from PyQt5 import QtGui


class IntValidator(QtGui.QDoubleValidator):
    """Normal QIntValidator is limited to 32-bit signed size. This version utilizes QDoubleValidator to allow for arbitraryly big integers, which are possible in Python.
    """Normal QIntValidator is limited to 32-bit signed size.
    This version utilizes QDoubleValidator to allow for arbitraryly big
    integers, which are possible in Python.
    """

    def __init__(self, bottom=float('-inf'), top=float('inf')):


@@ 11,6 13,7 @@ class IntValidator(QtGui.QDoubleValidator):
        self.setNotation(QtGui.QDoubleValidator.StandardNotation)

    def validate(self, text, pos):
        """Validate which disalows the '.' symbol."""
        # Disallow '.' symbol since only ints are allowed
        if text.endswith('.'):
            return QtGui.QValidator.Invalid, text, pos

D tic-tac-toe/main.py => tic-tac-toe/main.py +0 -13
@@ 1,13 0,0 @@
from tictactoe import print_board, input_position, check_win
import random

if __name__ == "__main__":
    current_player = random.choice(['o', 'x'])
    while True:
        print_board()
        check_win()
        input_position(current_player)
        if current_player == 'x':
            current_player = 'o'
        else:
            current_player = 'x'

R tic-tac-toe/__init__.py => tictactoe/__init__.py +0 -0
R tic-tac-toe/tictactoe.py => tictactoe/game.py +26 -25
@@ 1,21 1,23 @@
"""This module contains the game rules."""
import os
import sys

# Global variable board is a 0-9 list, which eventually get replaced with x/o
board = list(range(9))
BOARD = list(range(9))


def print_board():
    """Clear the terminal screen and print the board."""

    global board
    global BOARD
    # Clear the screen with cls (Windows) or clear (other systems)
    os.system('cls' if os.name == 'nt' else 'clear')
    # Print the board
    print(f'{board[0]} | {board[1]} | {board[2]}')
    print(f'{BOARD[0]} | {BOARD[1]} | {BOARD[2]}')
    print('----------')
    print(f'{board[3]} | {board[4]} | {board[5]}')
    print(f'{BOARD[3]} | {BOARD[4]} | {BOARD[5]}')
    print('----------')
    print(f'{board[6]} | {board[7]} | {board[8]}', end='\n\n')
    print(f'{BOARD[6]} | {BOARD[7]} | {BOARD[8]}', end='\n\n')


def input_position(player):


@@ 25,13 27,12 @@ def input_position(player):
        try:
            pos_string = input(f'{player}: ')
            if pos_string == 'q':
                exit()
                sys.exit()
            position = int(pos_string)
            if position in board:
                board[position] = player
            if position in BOARD:
                BOARD[position] = player
                break
            else:
                raise ValueError
            raise ValueError
        except ValueError:
            print('Invalid position, try again or "q" to quit.')



@@ 39,32 40,32 @@ def input_position(player):
def check_win():
    """Check if there is a winner (or draw) and end the game if there is."""

    global board
    global BOARD
    h_index = 0
    v_index = 0
    # Check horizontal lines with h_index and vertical lines with v_index
    for _ in range(0, 3):
        if board[h_index] == board[h_index + 1] == board[h_index + 2]:
            print(f'{board[h_index]} won!')
            exit()
        if board[v_index] == board[v_index + 3] == board[v_index + 6]:
            print(f'{board[v_index]} won!')
            exit()
        if BOARD[h_index] == BOARD[h_index + 1] == BOARD[h_index + 2]:
            print(f'{BOARD[h_index]} won!')
            sys.exit()
        if BOARD[v_index] == BOARD[v_index + 3] == BOARD[v_index + 6]:
            print(f'{BOARD[v_index]} won!')
            sys.exit()
        h_index += 3
        v_index += 1

    # Check diagonals
    if board[0] == board[4] == board[8]:
        print(f'{board[0]} won!')
        exit()
    if board[2] == board[4] == board[6]:
        print(f'{board[2]} won!')
        exit()
    if BOARD[0] == BOARD[4] == BOARD[8]:
        print(f'{BOARD[0]} won!')
        sys.exit()
    if BOARD[2] == BOARD[4] == BOARD[6]:
        print(f'{BOARD[2]} won!')
        sys.exit()
    # Check if the board is NOT full
    for cell in board:
    for cell in BOARD:
        if str(cell) in '012345678':
            break
    # If full draw the game
    else:
        print('Draw!')
        exit()
        sys.exit()

A tictactoe/main.py => tictactoe/main.py +14 -0
@@ 0,0 1,14 @@
"""This is the __main__ module."""
import random
from game import print_board, input_position, check_win

if __name__ == "__main__":
    CURRENT_PLAYER = random.choice(['o', 'x'])
    while True:
        print_board()
        check_win()
        input_position(CURRENT_PLAYER)
        if CURRENT_PLAYER == 'x':
            CURRENT_PLAYER = 'o'
        else:
            CURRENT_PLAYER = 'x'