import _pickle as pickle
import os

from PyQt5.QtCore import QPoint, pyqtSlot, QObject, Qt
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton

import Global
from CadenceButton import CadenceButton, DEFAULT_BCK_COLOR
from Section import Section

TITLE_HEIGHT_FACTOR = 55

class MainFrame(QWidget):
    """
    MainFrame
   ---------------
    Represent tab's content
    """

    """
    Function - __init__ ( Constructor )

    Brief - init buttons display and functionality.

    Parameters -
                    editable : bool    - indicates if the box is editable.
                    parent   : Qwidget - button's responsible widget ( default = None ).

    Description -
                    1. Initiate class variables.
                    2. Create sections holder.
                    3. Create default buttons.
                    4. Set button's attributes.
    """

    def __init__(self, editable, tab_name="Default", parent=None, btn_set=None):
        super().__init__(parent)

        # Initiate class variables.
        self.last_max_btns = 0
        self.editable = editable
        self.tab_name = tab_name

        # Create sections holder.
        self.sections = QVBoxLayout()
        self.sections.setAlignment(Qt.AlignTop)
        self.sections.setSizeConstraint(self.sections.SetMinAndMaxSize)

        # Create default buttons.
        self.set_buttons(btn_set)

        # Set button's attributes.
        self.setLayout(self.sections)
        self.resize_window_height()

    ############################################################################
    #                                                                          #
    #                                                                          #
    #                             Mouse Events                                 #
    #                                                                          #
    ############################################################################
    """
    Function - mouseMoveEvent ( override function )

    Brief - Invoked when mouse movement has been detected.

    Parameters -    ev: QMouseEvent - mouse event parameters.

    Description -
                    1. If any button is on 'move' mode, then activate this button's method "mouseMoveEvent"
    """

    def mouseMoveEvent(self, ev: QMouseEvent):
        if Global.btn_on_move is not None:
            Global.btn_on_move.move(ev.globalPos())

    ############################################################################
    #                                                                          #
    #                                                                          #
    #                             Buttons Methods                              #
    #                                                                          #
    ############################################################################
    """
    Function    - create_button

    Brief       - Create button and connect its signals

    Parameters  -
                    title   : str     - button's name
                    section : Section - the section that will have the button.

    Returns     - The created button.

    Description -
                    1. Create button.
                    2. Connect button's signals
    """

    def create_button(self, title, command, tooltip, section: Section, color=DEFAULT_BCK_COLOR):
        # Create button.
        btn = CadenceButton(self.editable, self.tab_name, title, command, tooltip, color, self)

        # Connect button's signals
        btn.stop_moving_event.connect(self.replace_buttons)
        btn.start_moving_event.connect(self.button_start_moving)
        btn.remove_button_event.connect(section.remove_button)
        btn.cancel_moving_event.connect(self.replace_buttons)
        btn.save_button_event.connect(self.save_buttons)
        if not self.editable:
            btn.copy_btn_event.connect(self.add_to_section)
        btn.set_bold(Global.bold_all_btns)
        return btn

    ############################################################################
    #                                                                          #
    #                                                                          #
    #                             Section's Methods                            #
    #                                                                          #
    ############################################################################
    """
    Function    - rebuild_empty_section

    Brief       - Rebuild the input section, if its grid is empty.

    Parameters  -
                    section : Section     - section to rebuild, if needed.

    Returns     - if section rebuild, then the new section. Else, the input section.

    Description - Due to GUI limitation, if section is empty and already displayed, then added button will not be
                  display as expected. Therefore, the system need to remove and add again the section,
                  for displaying the new button.
    """

    def rebuild_empty_section(self, section: Section):
        if section.get_grid().isEmpty():
            index = self.sections.indexOf(section)
            coll_title = section.toggle_button.text()
            self.remove_section(section)
            section = self.add_section(coll_title, index)
        return section

    """
    Function    - add_section

    Brief       - Add new section to tab.

    Parameters  -
                    title : str - section title.
                    index : int - index to insert the section.

    Returns     - The created section.

    Description -
                    1. Create grid.
                    2. Create section.
                    3. Connect section's signals.
                    4. Insert grid into the section.
                    5. Insert section to the section holder.
                    6. Collapse section.
    """

    def add_section(self, title, index):
        # Create grid.
        grid = QVBoxLayout()
        grid.setSpacing(1)
        grid.setContentsMargins(5, 2, 5, 2)
        grid.setAlignment(Qt.AlignTop)

        # Create section.
        section = Section(self, self.editable, title)

        # Connect section's signals.
        section.cancel_moving_event.connect(self.replace_buttons)
        section.stop_moving_event.connect(self.drop_btn_in_section)
        section.add_exist_button_event.connect(self.create_existing_button)
        section.add_new_button_event.connect(self.create_new_button)
        section.remove_section_event.connect(self.remove_section)
        section.section_renamed_event.connect(self.save_buttons)
        section.move_section_event.connect(self.move_section)
        section.button_deleted.connect(self.save_buttons)

        # Insert grid into the section.
        section.set_grid(grid)

        # Insert section to the section holder.
        self.sections.insertWidget(index, section)

        # Collapse section.
        if not section.checked:
            section.collapse(1)
        return section

    """
    Function    - rearrange_section

    Brief       - Recreate section. Because API limitation, sometimes  when editing the buttons in a section, it will
                  not represent the buttons well. This method will delete and recreate the section, for display all
                  buttons, as expected.

    Parameters  -
                    section : Section - section to rearrange.

    Returns     - The new section section.

    Description -
                    1. Create grid.
                    2. Create section.
                    3. Connect section's signals.
                    4. Insert grid into the section.
                    5. Insert section to the section holder.
                    6. Collapse section.
    """
    def rearrange_section(self, section: Section):
        all_btns = []
        index = self.sections.indexOf(section)
        title = section.toggle_button.text()
        btn = section.remove_last_widget()
        while btn:
            all_btns.append(btn)
            btn = section.remove_last_widget()
        all_btns.reverse()
        # Create grid.
        grid = QVBoxLayout()
        grid.setSpacing(10)
        grid.setContentsMargins(10, 10, 10, 10)
        grid.setAlignment(Qt.AlignTop)

        # Create section.
        section = Section(self, self.editable, title)

        # Connect section's signals.
        section.cancel_moving_event.connect(self.replace_buttons)
        section.stop_moving_event.connect(self.drop_btn_in_section)
        section.add_exist_button_event.connect(self.create_existing_button)
        section.add_new_button_event.connect(self.create_new_button)
        section.remove_section_event.connect(self.remove_section)
        section.section_renamed_event.connect(self.save_buttons)
        section.move_section_event.connect(self.move_section)

        # Insert grid into the section.
        section.set_grid(grid)

        # Insert section to the section holder.
        self.sections.takeAt(index).widget().deleteLater()
        self.sections.insertWidget(index, section)
        for btn in all_btns:
            btn.remove_button_event.connect(section.remove_button)
            section.add_next_widget(btn)
        section.collapse(1)
        return section

    ############################################################################
    #                                                                          #
    #                                                                          #
    #                                Searches                                  #
    #                                                                          #
    ############################################################################
    """
    Function    - find_row_and_grid

    Brief       - Find button's section, grid and row.

    Parameters  -
                    required_btn : QPushButton - button to search its grid and row.

    Returns     - Button's section, grid and row.
    """

    def find_btn_parents(self, required_btn: QPushButton):
        for i in range(self.sections.count()):
            section = self.sections.itemAt(i).widget()
            # if type(item) == Section:
            grid = section.get_grid()
            for j in range(grid.count()):
                row = grid.itemAt(j).layout()
                for k in range(row.count()):
                    btn = row.itemAt(k).widget()
                    if type(btn) == CadenceButton and required_btn == btn:
                        return section, grid, row

    """
        Function    - find_section_by_grid

        Brief       - Find section by its grid.

        Parameters  -
                        grid : QVBoxLayout - grid to find its section.

        Returns     - grid's section.
        """

    def find_section_by_grid(self, grid: QVBoxLayout):
        for i in range(self.sections.count()):
            item = self.sections.itemAt(i).widget()
            if type(item) == Section:
                if grid == item.get_grid():
                    return item

    ############################################################################
    #                                                                          #
    #                                                                          #
    #                             Miscellaneous                                #
    #                                                                          #
    ############################################################################
    """
        Function    - setMouseTracking ( override )

        Brief       - Set in all application mouse tracking to 'flag' value.

        Parameters  -
                        flag : bool - value to assign.

        Description - The method contain sub method which go through all children and assign the 'flag' value to the
                      setMouseTracking method.
        """

    def setMouseTracking(self, flag):
        def recursive_set(parent):
            for child in parent.findChildren(QObject):
                try:
                    child.setMouseTracking(flag)
                except:
                    pass
                recursive_set(child)

        QWidget.setMouseTracking(self, flag)
        recursive_set(self)

    """
        Function    - resize_window_height

        Brief       - resize windows height according to the number of section in the tab.

        Description -
                        1. Resize main window.
                        2. Resize tab.
                        3. Reset sections layout.
                        4. Update changes in tab
                        5. Update changes in main window
        """

    def resize_window_height(self):
        total_height = 0
        screen_height_factor = 1.2
        for i in range(self.sections.count()):
            num_of_rows = self.sections.itemAt(i).widget().get_grid().count()
            if total_height + TITLE_HEIGHT_FACTOR - Global.screen_height*screen_height_factor < 0:
                total_height += TITLE_HEIGHT_FACTOR
            for j in range(0, num_of_rows):
                if total_height + Global.btn_height - Global.screen_height*screen_height_factor < 0:
                    total_height += Global.btn_height
        total_height = max(total_height, 700)
        self.window().setFixedSize(Global.btn_width*Global.number_of_btn_in_row, total_height)
        self.setFixedSize(Global.btn_width*Global.number_of_btn_in_row, total_height)
        self.setLayout(self.sections)
        self.update()
        self.window().update()
        if self.sections.count() > 0:
            checked = self.sections.itemAt(0).widget().checked
            self.sections.itemAt(0).widget().decollapse()
            self.sections.itemAt(0).widget().collapse()
            if checked:
                self.sections.itemAt(0).widget().decollapse()

    def set_buttons(self, btn_set):
        """
        set default/custom sections and buttons for tab.
        :param btn_set: list of buttons set for placing in the frame.

        Description:
            1. If this frame is not editable & tab name is "Default", then the buttons will be created
               from the defaultF10 file.
            2. If btn_set is None and frame config file exists, than the burrons set will be extracted from it.
            3. If btn_set is not None it will be used as buttons set
            4. If step 1-3 not processed, frame will not be set with buttons
            5. Setting buttons:
                5.1 Creating each section
                5.2 Creating buttons for each section

        """
        title = self.tab_name.replace(" ", ".f10.")
        if not self.editable and self.tab_name == "Default":
            btns_to_create = Global.all_buttons_dict

        elif not btn_set and os.path.isfile(f'{Global.path_to_save}/{title}_btns.pickle'):
            with open(f'{Global.path_to_save}/{title}_btns.pickle', "rb") as f:
                btns_to_create = pickle.load(f)
        else:
            btns_to_create = btn_set if btn_set else {}
        if not btns_to_create:
            self.add_section("Default Section", self.sections.count())
        else:
            for (key, val) in btns_to_create.items():
                coll = self.add_section(key, self.sections.count())
                if self.tab_name != "User" and 'tooltip' in val.keys() and val['tooltip'] != '':
                    coll.set_tooltip(val['tooltip'])

                buttons = val
                for name, props in buttons.items():
                    if not self.editable:
                        color = 'lightGray'
                        if isinstance(val, dict) and 'color' in val.keys():
                            color = val['color']
                    else:
                        color = DEFAULT_BCK_COLOR if len(props) == 2 else props[2]
                    btn = self.create_button(name, props[0], props[1], coll, color)

                    coll.add_next_widget(btn)

    ############################################################################
    #                                                                          #
    #                                                                          #
    #                                Slots                                     #
    #                                                                          #
    ############################################################################

    @pyqtSlot(CadenceButton)
    def add_to_section(self, btn: CadenceButton):
        """
        A callback to add ( copy ) button from "Default" Section to "User" Section
        :param btn: button to copy
        """
        self.window().add_btn_to_section(btn)

    """
        Function    - replace_buttons

        Brief       - Replace between the 'on move' button and the input button.

        Parameters  -
                       btn: QPushButton - button to switch with the 'on move' button.

        Description -   1. set mouseOnTracking variable to False in all widgets.
                        2. Enable all tabs.
                        3. When the input button is the 'on move' button then
                           the 'on move' button will return its original place.
                        4. In other case the buttons will be switched.
                        5. Set the 'is_on_move' attribute of the 'on move' button to false.
                        6. Reset global variables to None:
                            6.1. btn_on_move
                            6.2  btn_grid
                        7. save frame's buttons in file.
        """

    @pyqtSlot(QPushButton)
    def replace_buttons(self, btn: QPushButton):
        self.setMouseTracking(False)
        self.window().enable_all_tabs()

        if btn == Global.btn_on_move:
            # same button , move to original place
            section = Global.btn_section
            section.add_button_at(Global.btn_index[0], Global.btn_index[1], Global.btn_on_move)
            self.rearrange_section(section)
        else:

            section, grid, row = self.find_btn_parents(btn)
            if section == Global.btn_section:
                # replace buttons in same grid
                section.switch_btn_with_moved_btn(btn)
                self.rearrange_section(section)
            else:
                # replace buttons in different grids
                src_section = Global.btn_section
                try:
                    Global.btn_on_move.remove_button_event.disconnect()
                except Exception:
                    pass
                Global.btn_on_move.remove_button_event.connect(section.remove_button)
                btn.remove_button_event.disconnect()
                btn.remove_button_event.connect(src_section.remove_button)

                dst_index = section.get_button_cell_index(btn)
                section.remove_button_at(dst_index[0], dst_index[1])
                section.add_button_at(dst_index[0], dst_index[1], Global.btn_on_move)
                src_section.add_button_at(Global.btn_index[0], Global.btn_index[1], btn)
                self.rearrange_section(src_section)
                self.rearrange_section(section)
        Global.btn_on_move.set_is_on_move(False)
        Global.btn_on_move = None
        Global.btn_section = None
        self.resize_window_height()
        self.save_buttons()

    """
        Function    - button_start_moving

        Brief       - Prepare system to 'button on move' status, which means one of the button is in 'move' state.

        Parameters  -
                       btn: QPushButton - button that will enter to 'move' state.

        Description -
                        1. Disable all other tabs in the application.
                        2. Collapse all sections in tab.
                        3. Assign to the Global variable btn_index the index of the input tab.
                        4. Remove the input button from its section.
                        5. Set all widget's mouseTracking attribute to True.
                        6. Set Global variables:
                            6.1 Input button assigned to btn_on_move.
                            6.2 Input button's grid assigned to btn_grid

        """

    @pyqtSlot(CadenceButton)
    def button_start_moving(self, btn: CadenceButton):
        # Disable all other tabs in the application.
        self.window().enable_specific_tab(self)

        # Collapse all sections in tab.
        for i in range(self.sections.count()):
            item = self.sections.itemAt(i).widget()
            if type(item) == Section:
                if not item.checked:
                    item.collapse(1)
        # Assign to the Global variable btn_index the index of the input tab.
        section, grid, row = self.find_btn_parents(btn)
        Global.btn_index = section.get_button_cell_index(btn)

        # Remove the input button from its section.
        section.remove_specific_button(btn)
        section = self.rearrange_section(section)
        section.save_current_buttons_array()
        self.resize_window_height()
        btn.setParent(self)
        btn.show()
        # Set all widget's mouseTracking attribute to True.
        self.setMouseTracking(True)
        self.window().setMouseTracking(True)

        # Set Global variables
        Global.btn_on_move = btn
        Global.btn_section = section

    def save_buttons(self):
        """
        Saves the current main fram instance's buttons and sections
        """
        self.window().save_buttons(self.tab_name)

    """
        Function    - drop_btn_in_section

        Brief       - Add the 'on move' button to the input section.

        Parameters  -
                       point       :  QPoint  - Position to enter the button.
                       dst_section :  Section - section to drop the button.

        Description -   1. set mouseOnTracking variable to False in all widgets.
                        2. Enable all tabs.
                        3. reconnect signal 'remove_button_event' to the input section's method.
                        4. implement the button in the place which related to the input point.
                           the 'on move' button will return its original place.
                        5. Set the 'is_on_move' attribute of the 'on move' button to false.
                        6. Reset global variables to None:
                            6.1. btn_on_move
                            6.2  btn_grid

        """

    @pyqtSlot(QPoint, Section)
    def drop_btn_in_section(self, point: QPoint, dst_section: Section):
        self.setMouseTracking(False)
        self.window().enable_all_tabs()
        dst_section = self.rebuild_empty_section(dst_section)
        #Global.btn_on_move.remove_button_event.disconnect()
        Global.btn_on_move.remove_button_event.connect(dst_section.remove_button)
        grid = dst_section.get_grid()
        has_implemented = False
        for row_index in range(grid.count()):

            if not has_implemented:
                row = grid.itemAt(row_index).layout()
                bottom = row.geometry().bottom() + 20
                if point.y() <= bottom:
                    for col_index in range(row.count()):
                        if not has_implemented:
                            btn = row.itemAt(col_index).widget()
                            right = btn.geometry().right()
                            if point.x() <= right:
                                dst_section.add_button_at(row_index, col_index, Global.btn_on_move)
                                has_implemented = True
                    if not has_implemented:
                        dst_section.add_button_at(row_index, grid.itemAt(row_index).layout().count(), Global.btn_on_move)
                        has_implemented = True
        if not has_implemented:
            dst_section.add_next_widget(Global.btn_on_move)
        Global.btn_on_move.set_is_on_move(False)
        Global.btn_on_move = None
        Global.btn_section = None
        self.rearrange_section(dst_section)
        self.resize_window_height()
        self.save_buttons()

    """
        Function    - create_existing_button

        Brief       - Create new button from default buttons list.

        Parameters  -
                       title   :  str  - button title.
                       section :  Section - section to add the button.

        Description -   1. Rebuild the section if it is empty.
                        2. Create the new button.
                        3. Add it to the section.
                        4. Resize application's height.
                        5. save frame's buttons in file.

        """

    @pyqtSlot(str, str, str, QWidget)
    def create_existing_button(self, title: str, command, tooltip, section: Section):
        # Rebuild the section if it is empty.
        section = self.rebuild_empty_section(section)

        # Create the new button.
        btn = self.create_button(title, command, tooltip, section)

        # Add it to the section.
        section.add_next_widget(btn)

        # Resize application's height.
        if section.get_grid().count() > 1:
            self.rearrange_section(section)
            self.resize_window_height()
        self.save_buttons()

    """
        Function    - create_new_button

        Brief       - Create button that is not from default buttons list.

        Parameters  -
                       btn_params   :  list  - buttons parameters to build it ( name, description, procedure to invoke).
                       section :  Section - section to add the button.

        Description -   1. Rebuild the section if it is empty.
                        2. Add description to the buttons descriptions dictionary.
                        3. Create the button.
                        4. Add to button the requested procedure.
                        5. Add button to section.
                        5. Resize application's height.
                        6. save frame's buttons in file.

        """

    @pyqtSlot(list, QWidget)
    def create_new_button(self, btn_params: list, section: Section):
        # Rebuild the section if it is empty.
        section = self.rebuild_empty_section(section)

        # Add description to the buttons descriptions dictionary.
        #Global.all_buttons_desc[btn_params[0]] = btn_params[1]
        # Create the button.
        btn = self.create_button(btn_params[0], btn_params[1], btn_params[2], section)

        # Add to button the requested procedure.
        #btn.system_command = btn_params[2]

        # Add button to section.
        section.add_next_widget(btn)

        # Resize application's height.
        if section.get_grid().count() > 1:
            self.rearrange_section(section)
            self.resize_window_height()
        self.save_buttons()

    """
        Function    - remove_section

        Brief       - remove the requested section from tab.

        Parameters  -
                       section :  Section - section to remove.
        """

    @pyqtSlot(QWidget)
    def remove_section(self, section: Section):
        self.sections.takeAt(self.sections.indexOf(section))
        section.setParent(None)
        section.deleteLater()
        self.resize_window_height()
        self.save_buttons()

    """
            Function    - move_section

            Brief       - move section to different place in the frame

            Parameters  -
                       section      :  Section  - section to change place.
                       direction    :  str - the direction to place the section (Up, Down, Top, Bottom).
            """
    @pyqtSlot(QWidget, str)
    def move_section(self, section: Section, direction: str):
        index = self.sections.indexOf(section)
        if direction == "Up":
            if index != 0:
                self.sections.takeAt(index)
                self.sections.insertWidget(index - 1, section)
        elif direction == "Down":
            if index != self.sections.count() - 1:
                self.sections.takeAt(index)
                self.sections.insertWidget(index + 1, section)
        elif direction == "First":
            if index != 0:
                self.sections.takeAt(index)
                self.sections.insertWidget(0, section)
        elif direction == "Last":
            if index != self.sections.count() - 1:
                self.sections.takeAt(index)
                self.sections.insertWidget(self.sections.count(), section)
        self.save_buttons()


def num_2():
    n=int(input("enter an n value: "))
    if n==0:
        y=1
    elif n<0:
        y=0
    else:
        y = 0
        for i in range(1,n):
            y += (1+i)/(1+i**2)
    return y
num_2()


from sqlalchemy import select

from bot.logs import get_logger
from bot.database import session
from bot.database.schema import Language
from bot.database.schema import Edition
from bot.database.schema import Book
from bot.database.schema import Chapter
from bot.database.schema import VideoMarker
from bot.database.schema import File
from bot.database.schema import User
from bot import exc

logger = get_logger(__name__)

def sign_languages() -> list[Language]:
    return session.query(Language).filter(Language.is_sign_language == True).order_by(Language.meps_symbol.asc()).all()

def languages() -> list[Language]:
    return session.query(Language).order_by(Language.meps_symbol.asc()).all()

def language(code: str | None = None, meps_symbol: str | None = None) -> Language | None:
    q = session.query(Language)
    if code is not None:
        q = q.filter(Language.code == code)
    elif meps_symbol is not None:
        q = q.filter(Language.meps_symbol == meps_symbol)
    else:
        raise TypeError('get_language expected one argument')
    return q.one_or_none()

def parse_language(code_or_meps: str) -> Language | None:
    return language(code=code_or_meps.lower()) or language(meps_symbol=code_or_meps.upper())

def sign_languages_meps_symbol() -> list[str]:
    return session.scalars(select(Language.meps_symbol).filter(Language.is_sign_language == True)).all()

def user(telegram_user_id) -> User | None:
    return session.query(User).filter(User.telegram_user_id == telegram_user_id).one_or_none()

def users() -> list[User | None]:
    return session.query(User).all()

def banned_users() -> list[User | None]:
    return session.query(User).filter(User.status == -1).all()

def waiting_users() -> list[User | None]:
    return session.query(User).filter(User.status == 0).all()

def accepted_users() -> list[User | None]:
    return session.query(User).filter(User.status == 1).all()

def edition(language_code: str) -> Edition | None:
    return (session
            .query(Edition)
            .join(Language)
            .filter(Language.code == language_code)
            .order_by(Edition.id.asc())
            .limit(1)
            .one_or_none()
    )

def books(language_code: str = None, booknum: int = None) -> list[Book]:
    q = session.query(Book)
    if isinstance(language_code, str):
        q = (q
             .join(Edition, Edition.id == Book.edition_id)
             .join(Language, Language.code == Edition.language_code)
             .filter(Language.code == language_code)
        )
    if isinstance(booknum, int):
        q = q.filter(Book.number == booknum)
    return q.order_by(Book.number.asc()).all()

def book(language_code: str, booknum: int | str, edition_id: int | None = None) -> Book | None:
    q = (
        session.query(Book)
        .join(Edition, Edition.id == Book.edition_id)
        .join(Language, Language.code == Edition.language_code)
        .filter(
            Book.number == int(booknum),
            Language.code == language_code,
        )
    )
    if isinstance(edition_id, int):
        q = q.filter(Edition.id == edition_id)
    return q.one_or_none()

def chapter(chapternum: int, book: Book, checksum: str | None = None) -> Chapter | None:
    q = (session.query(Chapter)
         .join(Book, Book.id == Chapter.book_id)
         .join(Edition, Edition.id == Book.edition_id)
         .join(Language, Language.code == Edition.language_code)
         .filter(Language.code == book.edition.language.code,
                 Book.number == book.number,
                 Chapter.number == chapternum
        ).order_by(Chapter.id.desc()).limit(1)
    )
    if checksum:
        q.filter(Chapter.checksum == checksum)
    return q.one_or_none()

def chapters(book: Book) -> list[Chapter | None]:
    return (session.query(Chapter)
         .join(Book, Book.id == Chapter.book_id)
         .join(Edition, Edition.id == Book.edition_id)
         .join(Language, Language.code == Edition.language_code)
         .filter(Language.code == book.edition.language.code,
                 Book.number == book.number,
        ).order_by(Chapter.id.asc())
    ).all()

def videomarkers(chapter: Chapter, verses: list[int] | None = None) -> list[VideoMarker]:
    q = session.query(VideoMarker).filter(VideoMarker.chapter_id == chapter.id)
    if verses is None:
        return q.all()
    else:
        vms = q.filter(VideoMarker.versenum.in_(verses)).all()
        available_versenums = [vm.versenum for vm in vms]
        if set(verses) == set(available_versenums):
            return vms
        else:
            raise exc.IncompleteVideoMarkers(set(verses) == set(available_versenums))

def unavailable_verses(chapter: Chapter, verses: list[int]) -> list[int]:
    available = session.scalars(select(VideoMarker.versenum).filter(VideoMarker.chapter_id == chapter.id)).all()
    return list(set(verses) - set(available))

def file(chapter: Chapter,
         verses: list[int],
         overlay_language_code: str,
         checksum: str | None = None,
         ) -> File | None:
    q = session.query(File) \
        .join(Chapter, Chapter.id == File.chapter_id) \
        .join(Book, Book.id == Chapter.book_id) \
        .join(Edition, Edition.id == Book.edition_id) \
        .join(Language, Language.code == Edition.language_code) \
        .filter(Language.code == chapter.book.edition.language.code,
                Chapter.number == chapter.number,
                File.raw_verses == ' '.join(map(str, verses)),
                File.overlay_language.code == overlay_language_code)
    if checksum:
        q = q.filter(Chapter.checksum == checksum)
    return q.all()

def files(
        sign_language_code: str = None,
        booknum: int = None,
        chapternum: int = None,
        raw_verses: str = None,
        checksum: str = None,
        overlay_language_code: str | None = False,
        is_deprecated: bool = None
    ) -> list[File | None]:
    q = (
        session.query(File)
        .join(Chapter, Chapter.id == File.chapter_id)
        .join(Book, Book.id == Chapter.book_id)
        .join(Edition, Edition.id == Book.edition_id)
        .join(Language, Language.code == Edition.language_code)
    )
    if sign_language_code is not None:
        q = q.filter(Language.code == sign_language_code)
    if booknum is not None:
        q = q.filter(Book.number == booknum)
    if chapternum is not None:
        q = q.filter(Chapter.number == chapternum)
    if raw_verses:
        q = q.filter(File.raw_verses == raw_verses)
    if checksum is not None:
        q = q.filter(Chapter.checksum == checksum)
    if overlay_language_code is not False:
        q = q.filter(File.overlay_language_code == overlay_language_code)
    if is_deprecated is not None:
        q = q.filter(File.is_deprecated == is_deprecated)
    return q.all()

if __name__ == '__main__':
    my_markers = session.query(VideoMarker).filter(
        VideoMarker.chapter_id == 2,
        VideoMarker.versenum.in_([3, 4, 5])
    ).all()
    bk = book('csg', 40)
    chp = chapter(28, bk)

    print()


import requests
from bs4 import BeautifulSoup
from collections import Counter

def start(url):
    wordlist = []

    source_code = requests.get(url).text
    soup = BeautifulSoup(source_code, 'html.parser')

    # Extract all text from the webpage
    for text in soup.stripped_strings:
        words = text.lower().split()

        for each_word in words:
            wordlist.append(each_word)

    clean_wordlist(wordlist)

def clean_wordlist(wordlist):
    clean_list = []

    for word in wordlist:
        symbols = "!@#$%^&*()_-+={[}]|;:\"<>?/., "
        for symbol in symbols:
            word = word.replace(symbol, '')

        if len(word) > 0:
            clean_list.append(word)

    create_dictionary(clean_list)

def create_dictionary(clean_list):
    word_count = {}

    for word in clean_list:
        if word in word_count:
            word_count[word] += 1
        else:
            word_count[word] = 1

    c = Counter(word_count)
    top = c.most_common(10)
    print(top)

# Driver code
if __name__ == '__main__':
    # Use a different URL
    url = "https://www.amazon.in/"
    start(url)


#!/usr/bin/env python3
import asyncio
import json
import pickle

numCon = 0 # number of connections made with the server
conList = [] # list of connections made to the server (can use len(conList) to get former

HOST = '127.0.0.1'
PORT = 8888

class SnakeServerProtocol(asyncio.Protocol):

    def connection_made(self, transport):
        peername = transport.get_extra_info('peername')
        print('Connection from {}'.format(peername))
        self.transport = transport

        self.transport.write(pickle.dumps({"mode":"user","player": str(peername),"name":str(peername)}))

        global numCon
        numCon += 1

        global conList
        conList.append(self.transport)
        print('Number of Connections: {}'.format(numCon))

        self.send_all(pickle.dumps({"mode":"add","player":str(peername)}))

    def connection_lost(self, exc):
        print('Lost a connection from client...')
        global numCon
        numCon -= 1

        conList.remove(self.transport)
        print('Number of Connections: {}'.format(numCon))

    def data_received(self, data):
        message = pickle.loads(data)
        #print('Data received from {}:{!r}'.format(self.transport.get_extra_info('peername'),message),type(message))

        if isinstance(message,dict):
            if message["mode"] == "move":
                prepare =  {"player":str(self.transport.get_extra_info('peername')), "body": message["body"],"mode":"move"}
                packet = pickle.dumps(prepare) #  Arbritrary dictionary for snake game
                #print("Sending",prepare)
                self.send_all(packet)
            elif message["mode"] == "dead":
               print("Disconnecting player...")
               packet = {"mode":"dead","player":str(self.transport.get_extra_info('peername'))}
               self.send_all(pickle.dumps(packet))
        else:
            print(message)

    def send_all(self,data):
        global conList
        for con in conList:
            con.write(data)

async def main():
    loop = asyncio.get_running_loop()

    numCon = 0
    server = await loop.create_server(
            lambda: SnakeServerProtocol(),
            HOST, PORT)

    async with server:
        await server.serve_forever()

try:
    asyncio.run(main())
except KeyboardInterrupt:
    print("\n--> Closing server...")


import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from torch.autograd import Variable
import torchvision.transforms.functional as VF
from torchvision import transforms

import sys, argparse, os, copy, itertools, glob, datetime
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from itertools import cycle
from sklearn.utils import shuffle
from sklearn.metrics import roc_curve, roc_auc_score, auc, accuracy_score, f1_score, confusion_matrix, ConfusionMatrixDisplay, precision_recall_fscore_support
from scipy import interp
from sklearn.datasets import load_svmlight_file
from collections import OrderedDict

class AdeDataSet(Dataset):
    def __init__(self, csv_file):
        self.len = len(csv_file)
        self.wsi_feats = csv_file.iloc[:, 0]
        self.wsi_labels = csv_file.iloc[:, 1]

    def __getitem__(self, index):
        single_wsi_feats_path = self.wsi_feats[index]
        single_wsi_feats = pd.read_csv(single_wsi_feats_path)
        single_wsi_feats = shuffle(single_wsi_feats).reset_index(drop=True)
        feats = np.float32(single_wsi_feats.to_numpy())

        single_wsi_label = self.wsi_labels[index]
        label = np.float32(np.zeros(6))
        if int(single_wsi_label) <= (len(label) - 1):
            label[int(single_wsi_label)] = 1

        return feats, label

    def __len__(self):
        return self.len

def train(train_df, milnet, criterion, optimizer, args):
    milnet.train()
    csvs = shuffle(train_df).reset_index(drop=True)

    # 2.Calculating weight
    num_cls = args.num_classes
    weight_dict = {}
    for i in range(num_cls):
        weight_dict[i] = csvs.shape[0] / (csvs.loc[csvs['label'] == i].shape[0])
    # 3.Add weight column
    csvs['weight'] = csvs['label'].apply(lambda x: weight_dict[x])
    # 4.Define the WeightedRandomSampler sampler
    data_weight = torch.tensor(csvs['weight'].to_numpy(), dtype=torch.float)
    num_samples = csvs.shape[0]  # How many samples were taken? It can be set to the same amount as the dataset or set by yourself.
    # Define the sampler, pass in the prepared weight array, sample total, and select Put back sampling
    sampler = torch.utils.data.sampler.WeightedRandomSampler(data_weight, num_samples, replacement=True)
    # 5.Set up Dataset and Dataloader
    dataset = AdeDataSet(csvs)
    train_data_iter = torch.utils.data.DataLoader(dataset=dataset, sampler=sampler, batch_size=1)

    total_loss = 0
    bc = 0
    for i, data in enumerate(train_data_iter, 0):
        optimizer.zero_grad()
        bag_feats, bag_label = data
        bag_feats = bag_feats.cuda()
        bag_label = bag_label.cuda()
        bag_feats = bag_feats.view(-1, args.feats_size)
        ins_prediction, bag_prediction, _, _ = milnet(bag_feats)
        max_prediction, _ = torch.max(ins_prediction, 0)
        bag_loss = criterion(bag_prediction.view(1, -1), bag_label.view(1, -1))
        max_loss = criterion(max_prediction.view(1, -1), bag_label.view(1, -1))

        loss = 0.5 * max_loss + 0.5 * bag_loss
        loss.backward()
        optimizer.step()
        total_loss = total_loss + loss.item()
        sys.stdout.write('\r Training bag [%d/%d] bag loss: %.4f' % (i, len(train_df), loss.item()))
    return total_loss / len(train_df)

def test(epoch, test_df, milnet, criterion, optimizer, args):
    milnet.eval()
    csvs = shuffle(test_df).reset_index(drop=True)
    total_loss = 0
    test_labels = []
    test_predictions = []

    dataset = AdeDataSet(csvs)
    test_data_iter = DataLoader(dataset=dataset, batch_size=1)
    with torch.no_grad():
        for i, data in enumerate(test_data_iter, 0):
            bag_feats, bag_label = data
            bag_feats = bag_feats.cuda()
            bag_label = bag_label.cuda()
            label = bag_label.squeeze().cpu().numpy()
            bag_feats = bag_feats.view(-1, args.feats_size)
            ins_prediction, bag_prediction, _, _ = milnet(bag_feats)
            max_prediction, _ = torch.max(ins_prediction, 0)
            bag_loss = criterion(bag_prediction.view(1, -1), bag_label.view(1, -1))
            max_loss = criterion(max_prediction.view(1, -1), bag_label.view(1, -1))
            loss = 0.5 * max_loss + 0.5 * bag_loss
            total_loss = total_loss + loss.item()
            sys.stdout.write('\r Testing bag [%d/%d] bag loss: %.4f' % (i, len(test_df), loss.item()))
            test_labels.extend([label])
            test_predictions.extend(
                [(0.5 * torch.sigmoid(max_prediction) + 0.5 * torch.sigmoid(bag_prediction)).squeeze().cpu().numpy()])
    test_labels = np.array(test_labels)
    test_predictions = np.array(test_predictions)

    # compute accuracy and F1score
    F1score = dict()
    label_array = [np.argmax(test_label) for test_label in test_labels]
    prediction_array = [np.argmax(test_prediction) for test_prediction in test_predictions]

    acc_value = accuracy_score(label_array, prediction_array)

    F1score["macro"] = f1_score(label_array, prediction_array, average='macro')
    F1score["micro"] = f1_score(label_array, prediction_array, average='micro')

    auc_value, _, thresholds_optimal = multi_label_roc(epoch, test_labels, test_predictions, args.num_classes)
    if args.num_classes == 1:
        class_prediction_bag = copy.deepcopy(test_predictions)
        class_prediction_bag[test_predictions >= thresholds_optimal[0]] = 1
        class_prediction_bag[test_predictions < thresholds_optimal[0]] = 0
        test_predictions = class_prediction_bag
        test_labels = np.squeeze(test_labels)
    else:
        for i in range(args.num_classes):
            class_prediction_bag = copy.deepcopy(test_predictions[:, i])
            class_prediction_bag[test_predictions[:, i] >= thresholds_optimal[i]] = 1
            class_prediction_bag[test_predictions[:, i] < thresholds_optimal[i]] = 0
            test_predictions[:, i] = class_prediction_bag
    bag_score = 0
    for i in range(0, len(test_df)):
        bag_score = np.array_equal(test_labels[i], test_predictions[i]) + bag_score
    avg_score = bag_score / len(test_df)

    return acc_value, F1score, total_loss / len(test_df), avg_score, auc_value, thresholds_optimal

def multi_label_roc(epoch, labels, predictions, num_classes, pos_label=1):
    # fprs = []
    # tprs = []

    fprs = dict()
    tprs = dict()
    roc_auc = dict()

    thresholds = []
    thresholds_optimal = []
    aucs = []
    if len(predictions.shape) == 1:
        predictions = predictions[:, None]
    for c in range(0, num_classes):
        label = labels[:, c]
        prediction = predictions[:, c]
        # fpr, tpr, threshold = roc_curve(label, prediction, pos_label=1)
        fpr, tpr, threshold = roc_curve(label, prediction)

        fprs[c], tprs[c] = fpr, tpr
        roc_auc[c] = auc(fprs[c], tprs[c])

        fpr_optimal, tpr_optimal, threshold_optimal = optimal_thresh(fpr, tpr, threshold)
        c_auc = roc_auc_score(label, prediction)
        aucs.append(c_auc)
        thresholds.append(threshold)
        thresholds_optimal.append(threshold_optimal)

    # --------------------------------------------------------------------------------------------------------------#
    # Compute micro-average ROC curve and ROC area（方法二）
    fprs["micro"], tprs["micro"], _ = roc_curve(labels.ravel(), predictions.ravel())
    roc_auc["micro"] = auc(fprs["micro"], tprs["micro"])

    # Compute macro-average ROC curve and ROC area（方法一）
    # First aggregate all false positive rates
    all_fpr = np.unique(np.concatenate([fprs[i] for i in range(num_classes)]))
    # Then interpolate all ROC curves at this points
    mean_tpr = np.zeros_like(all_fpr)
    for i in range(num_classes):
        mean_tpr += interp(all_fpr, fprs[i], tprs[i])
    # Finally average it and compute AUC
    mean_tpr /= num_classes
    fprs["macro"] = all_fpr
    tprs["macro"] = mean_tpr
    roc_auc["macro"] = auc(fprs["macro"], tprs["macro"])

    return aucs, thresholds, thresholds_optimal

def optimal_thresh(fpr, tpr, thresholds, p=0):
    loss = (fpr - tpr) - p * tpr / (fpr + tpr + 1)
    idx = np.argmin(loss, axis=0)
    return fpr[idx], tpr[idx], thresholds[idx]

def main():
    parser = argparse.ArgumentParser(description='Train DSMIL on 20x patch features learned by SimCLR')
    parser.add_argument('--num_classes', default=6, type=int, help='Number of output classes [2]')
    parser.add_argument('--feats_size', default=768, type=int, help='Dimension of the feature size [512]')
    parser.add_argument('--lr', default=0.0002, type=float, help='Initial learning rate [0.0002]')
    parser.add_argument('--num_epochs', default=200, type=int, help='Number of total training epochs [40|200]')
    parser.add_argument('--gpu_index', type=int, nargs='+', default=(0,), help='GPU ID(s) [0]')
    parser.add_argument('--weight_decay', default=5e-3, type=float, help='Weight decay [5e-3]')
    parser.add_argument('--dataset_train', default='Ade', type=str, help='Dataset folder name')
    parser.add_argument('--dataset_val', default='Ade_val', type=str, help='Dataset folder name')
    parser.add_argument('--model', default='dsmil', type=str, help='MIL model [dsmil]')
    parser.add_argument('--dropout_patch', default=0, type=float, help='Patch dropout rate [0]')
    parser.add_argument('--dropout_node', default=0, type=float, help='Bag classifier dropout rate [0]')
    args = parser.parse_args()
    gpu_ids = tuple(args.gpu_index)
    os.environ['CUDA_VISIBLE_DEVICES'] = ','.join(str(x) for x in gpu_ids)

    if args.model == 'dsmil':
        import dsmil as mil
    elif args.model == 'abmil':
        import abmil as mil

    i_classifier = mil.FCLayer(in_size=args.feats_size, out_size=args.num_classes).cuda()
    b_classifier = mil.BClassifier(input_size=args.feats_size, output_class=args.num_classes,
                                   dropout_v=args.dropout_node).cuda()
    milnet = mil.MILNet(i_classifier, b_classifier).cuda()
    criterion = nn.BCEWithLogitsLoss()

    optimizer = torch.optim.Adam(milnet.parameters(), lr=args.lr, betas=(0.5, 0.9), weight_decay=args.weight_decay)
    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, args.num_epochs, 0.000005)

    bags_csv_train = os.path.join('datasets', args.dataset_train, args.dataset_train + '.csv')
    bags_csv_val = os.path.join('datasets', args.dataset_val, args.dataset_val + '.csv')

    train_path = pd.read_csv(bags_csv_train)
    test_path = pd.read_csv(bags_csv_val)

    best_accuracy = 0
    save_path = os.path.join('weights', datetime.date.today().strftime("%m%d%Y"))
    os.makedirs(save_path, exist_ok=True)
    run = len(glob.glob(os.path.join(save_path, '*.pth')))
    for epoch in range(1, args.num_epochs):
        train_path = shuffle(train_path).reset_index(drop=True)
        test_path = shuffle(test_path).reset_index(drop=True)
        train_loss_bag = train(train_path, milnet, criterion, optimizer, args)  # iterate all bags
        accuracy, F1score, test_loss_bag, avg_score, aucs, thresholds_optimal = test(epoch, test_path, milnet,
                                                                                criterion, optimizer, args)

        print(
            '\r Epoch [%d/%d] train loss: %.4f test loss: %.4f, accuracy: %.4f, F1score_macro: %.4f, F1score_micro: %.4f, average score: %.4f, AUC: ' %
            (epoch, args.num_epochs, train_loss_bag, test_loss_bag, accuracy, F1score["macro"], F1score["micro"],
             avg_score) + '|'.join('class-{}>>{}'.format(*k) for k in enumerate(aucs)))

        scheduler.step()
        if accuracy >= best_accuracy:
            best_accuracy = accuracy
            save_name = os.path.join(save_path, str(run + 1) + '.pth')
            torch.save(milnet.state_dict(), save_name)
            print('Best model saved at: ' + save_name)
            print('Best thresholds ===>>> ' + '|'.join(
                'class-{}>>{}'.format(*k) for k in enumerate(thresholds_optimal)))

if __name__ == '__main__':
    main()


import tensorflow as tf
import time

from params import Params
from utils import tf_round, Counter

class Logger(tf.Module):

    def __init__(self, log_dir):
        super().__init__(name="Logger")
        self.device = Params.DEVICE

        with tf.device(self.device), self.name_scope:

            self.episode_counter = Counter("episode_counter", start=-1, dtype=tf.int32)
            self.actor_steps_counter = Counter("actor_steps_counter", start=0, dtype=tf.int32)

            self.get_time = lambda: tf.reshape(tf.py_function(time.time, [], Tout=tf.float64), ()) * 1000
            self.actor_time = tf.Variable(self.get_time())
            self.learner_time = tf.Variable(self.get_time())

            self.learner_log_steps = tf.cast(Params.LEARNER_LOG_STEPS, tf.float64)

            # Init Tensorboard writer
            if Params.LOG_TENSORBOARD:
                self.log_dir = log_dir
                self.writer = tf.summary.create_file_writer(self.log_dir)
            else:
                self.writer = None

            # Log parameters
            self.log_params()

    def log_params(self):
        if Params.DO_LOGGING:
            print("retracing log_params")
            with tf.device(self.device):

                params = [f"{attr}: {getattr(Params, attr)}" for attr in dir(Params) if not attr.startswith("__")]
                params_string = "\n".join(params)

                if Params.LOG_CONSOLE:
                    tf.print("\n\n"+params_string+"\n\n")

                if Params.LOG_TENSORBOARD:
                    with self.writer.as_default():
                        tf.summary.text("Params", params_string, step=0)

    def log_ep_actor(self, n_episode, ep_steps, ep_avg_reward, ep_reward_sum_discounted,
                     noise_sigma, env_info, ep_replay_filename):
        if Params.DO_LOGGING:
            print("retracing log_ep_tensorboard")
            with tf.device(self.device):

                curr_time = self.get_time()
                actor_steps = tf.cast(self.actor_steps_counter.reset(), tf.float64)
                avg_step_time = (curr_time - self.actor_time.value()) / actor_steps
                self.actor_time.assign(curr_time)

                if Params.LOG_CONSOLE:
                    tf.print('E:', n_episode, '\tSteps:', ep_steps, '\t\tAvg. Reward:',
                             tf_round(ep_avg_reward, 3), '\tNoise variance:', tf_round(noise_sigma, 2), '\t', env_info)

                if Params.LOG_TENSORBOARD:
                    with self.writer.as_default():
                        step = tf.cast(n_episode, tf.int64)
                        tf.summary.scalar("Steps", ep_steps, step)
                        tf.summary.scalar("Average Reward", ep_avg_reward, step)
                        tf.summary.scalar("Reward Sum Discounted", ep_reward_sum_discounted, step)
                        # tf.cond(tf.not_equal(ep_replay_filename, ""),
                        #         lambda: tf.summary.text("Episode Replay", ep_replay_filename, step), lambda: False)
                        tf.summary.scalar("Avg step time", avg_step_time, step)
                        tf.summary.scalar("Noise variance", noise_sigma, step)
                        self.writer.flush()

    def log_step_learner(self, n_step, td_error, priority_beta):
        if Params.DO_LOGGING:
            print("retracing log_ep_tensorboard")
            with tf.device(self.device), tf.name_scope("Logger"):

                curr_time = self.get_time()
                avg_step_time = (curr_time - self.learner_time.value()) / self.learner_log_steps
                self.learner_time.assign(curr_time)

                if Params.LOG_CONSOLE:
                    tf.print("Train step:", n_step, "\t\tTD-Error:", td_error, "\t\tPriority Beta:", priority_beta)

                if Params.LOG_TENSORBOARD:
                    with self.writer.as_default():
                        step = tf.cast(n_step, tf.int64)
                        tf.summary.scalar("TD-Error", td_error, step)
                        tf.summary.scalar("Avg Step time", avg_step_time, step)
                        self.writer.flush()


from queue import PriorityQueue
from os import read
import networkx as nx
import matplotlib.pyplot as plt

class Graph:

    def __init__(self, num_of_vertices):
        self.v = num_of_vertices
        self.edges = [[-1 for i in range(num_of_vertices)] for j in range(num_of_vertices)]
        self.visited = []

    def add_edge(self, u, v, weight):
        self.edges[u][v] = weight
        self.edges[v][u] = weight

    def dijkstra(self, start_vertex, P):
        D = {v:float('inf') for v in range(self.v)}
        D[start_vertex] = 0

        pq = PriorityQueue()
        pq.put((0, start_vertex))

        while not pq.empty():
            (dist, current_vertex) = pq.get()
            self.visited.append(current_vertex)

            for neighbor in range(self.v):
                if self.edges[current_vertex][neighbor] != -1:
                    distance = self.edges[current_vertex][neighbor]
                    if neighbor not in self.visited:
                        old_cost = D[neighbor]
                        new_cost = D[current_vertex] + distance
                        if new_cost < old_cost:
                            pq.put((new_cost, neighbor))
                            D[neighbor] = new_cost

        for vertex in range(len(D)):
            P.add_edge(0, vertex, weight=D[vertex])
            print("Distance from vertex 0 to vertex", vertex, "is", D[vertex])

        pos = nx.get_node_attributes(P,'pos')
        labels = nx.get_edge_attributes(P,'weight')
        print(labels)
        nx.draw_networkx_edge_labels(P,pos,edge_labels=labels)
        plt.figure(2)
        nx.draw(P, pos, with_labels = True)

        print(P)


import random
import tkinter as tk
from screeninfo import get_monitors
import numpy as np

from const import COLORS

active_editor = None

class Tile(tk.Frame):
    """
    The class from which all tile types should inherit
    """
    def __init__(self, master, *args, **kwargs):
        super().__init__(master, *args, **kwargs)

        self.config(bg=random.choice(COLORS))

        self.bind('<Enter>', self.set_active_editor)

    @staticmethod
    def set_active_editor(event):
        """Ставит текущий эдитор активным"""
        global active_editor
        active_editor = event.widget

class TwoTiles(tk.PanedWindow):
    """
    Contains two tiles
    """
    def __init__(self, master, editor_type, ori):
        super().__init__(master)

        color = random.choice(COLORS)
        self.config(bd=0, sashwidth=2, sashpad=0, orient=ori)

        # First tile
        self.one = editor_type(self)
        self.add(self.one)

        # Second tile
        self.two = editor_type(self)
        self.add(self.two)

        self.pack(fill=tk.BOTH, expand=True)

        self.update()

    @staticmethod
    def sash_moved(event):
        print(event.widget.sash_coord(0))

    def split_editor(self, editor, orient=tk.HORIZONTAL):
        """
        Replaces one of the tiles with a new TwoTiles widget
        containing this type of tile
        """
        cls = type(self)

        # Remembering the position of the separator
        sash_x, sash_y = self.sash_coord(0)

        # Create a child TwoTiles and replace the editor with it
        new_split = cls(self, type(editor), orient)
        self.forget(editor)

        if editor == self.one:
            self.one = new_split
            self.add(self.one, before=self.two)
        else:
            self.two = new_split
            self.add(self.two,)

        # After replacing the widget, the separator has moved to the edge of the window,
        # - restore its position
        self.update()
        self.sash_place(0, sash_x, sash_y)

        # Now set the separator position in the new TwoTiles to the middle
        self.update()
        new_sash_x = new_split.winfo_width() // 2
        new_sash_y = new_split.winfo_height() // 2
        new_split.sash_place(0, new_sash_x, new_sash_y)

class MainWindow(tk.Tk):
    """
    Main Window (indeed)
    """
    def __init__(self, master=None):
        super().__init__(master)

        # ICON
        # icon = tk.PhotoImage(file='../icon.png')
        # self.iconphoto(False, icon)

        # App name
        self.title('Tiled UI')

        # The initial size of the window. We take the resolution of the smallest monitor
        # so that the window fits exactly into the screen
        resolution = np.min(
            [(mon.width, mon.height) for mon in get_monitors()],
            axis=0
        )
        self.geometry('{}x{}'.format(*resolution))
        self.minsize(300, 150)
        self.config(bg='gray')

        # Adding a Root TwoTiles
        root_tile = TwoTiles(self, Tile, tk.HORIZONTAL)
        root_tile.pack()

        root_tile.sash_place(0, self.winfo_width() // 2, self.winfo_height() // 2)

        # Key bindings
        self.bind("<z>", self.tile_split_handler)
        self.bind("<x>", self.tile_split_handler)

    @staticmethod
    def tile_split_handler(event):
        global active_editor

        if not active_editor: return

        # Call the split method on the PannedWindow containing the active editor,
        # vertical or horizontal depending on the event
        if event.char == 'z':
            active_editor.master.split_editor(active_editor)
        elif event.char == 'x':
            active_editor.master.split_editor(active_editor, orient='vertical')

if __name__ == '__main__':
    app = MainWindow()
    app.mainloop()


"""
This unit test looks at some specific particles within the simulation and
by hand checks that they really do end up at the distance to their nearest
neighbour as we say they do.

This is because of the currently very dubious claim presented in the
histogram plot produced by plot.find_distances_to_nearest_neighbours_plot.
"""

import numpy as np

import ltcaesar as lt
from test_halo_finder import test_load_data

def wrap_array(coordinates, boxsize):
    """
    Wraps according to periodic boundary conditions.
    """
    coordinates -= (coordinates > boxsize * 0.5) * boxsize
    coordinates += (coordinates <= -boxsize * 0.5) * boxsize

    return coordinates

def test_distance_to_data(test=None):
    """
    Tests if we are getting the correct distances to the data by hand
    for a few choice particles.

    You can supply a list of gas IDs in the above, but if none are supplied
    then we just check 10 randomly.
    """

    data = test_load_data()

    boxsize = data.snapshot_ini.header["BoxSize"]

    if test is None:
        # We need to choose the IDs to test specifically. These cannot be ones
        # that have formed stars for simplicity purposes.
        available_indicies = np.where(
            data.snapshot_end.baryonic_matter.gas_ids.astype(int)
            - max(data.snapshot_ini.baryonic_matter.gas_ids)
            < 0
        )

        # available_indicies is a tuple that contains the array
        test = np.sort(np.random.choice(available_indicies[0], 10))

    # Read the relevant data for these particles and do some ID matching
    particle_end_ids = data.snapshot_end.baryonic_matter.gas_ids[test]
    particle_end_coordinates = data.snapshot_end.baryonic_matter.gas_coordinates[test]

    # This is the hard part, doing the ID matching.
    particle_ini_indicies = np.searchsorted(
        data.snapshot_ini.baryonic_matter.gas_ids, particle_end_ids, side="left"
    )

    # Sort indicies by ID
    sorted_indicies_for_indicies = np.argsort(
        data.snapshot_ini.baryonic_matter.gas_ids[particle_ini_indicies]
    )
    particle_ini_indicies = particle_ini_indicies[sorted_indicies_for_indicies]

    particle_ini_ids = data.snapshot_ini.baryonic_matter.gas_ids[particle_ini_indicies]
    particle_ini_coordinates = data.snapshot_ini.baryonic_matter.gas_coordinates[
        particle_ini_indicies
    ]

    assert (particle_ini_ids == particle_end_ids).all()

    # Now we need to get the "real" data from the actual data function.

    r_ini, r_end, pair_indices = lt.plot.find_distances_to_nearest_neighbours_data(data)

    # We actually only care about 10 of these numbers...
    relevant_r_ini = r_ini[particle_ini_indicies]
    relevant_r_end = r_end[test]
    relevant_pair_ids = pair_indices[particle_ini_indicies]

    # This is the "brute-force" run.
    for (
        this_r_ini,
        this_r_end,
        this_pair_id,
        this_particle_ini_coordinate,
        this_particle_end_coordinate,
    ) in zip(
        relevant_r_ini,
        relevant_r_end,
        relevant_pair_ids,
        particle_ini_coordinates,
        particle_end_coordinates,
    ):

        # Brute force find
        distances_to_ini = (
            data.snapshot_ini.dark_matter.coordinates - this_particle_ini_coordinate
        )
        distances_to_ini = wrap_array(distances_to_ini, boxsize)

        radii_to_ini = np.sqrt(np.sum(distances_to_ini * distances_to_ini, axis=1))

        minimal_distance_ini = np.min(radii_to_ini)
        assert np.isclose(minimal_distance_ini, this_r_ini)

        # Now we can find the index of our friendly neighbourhood closest
        # particle!

        mask = radii_to_ini == minimal_distance_ini
        neighbour_end_coordinate = data.snapshot_end.dark_matter.coordinates[mask]

        distance_to_end = neighbour_end_coordinate - this_particle_end_coordinate
        distance_to_end = wrap_array(distance_to_end, boxsize)

        radii_to_end = np.sqrt(np.sum(distance_to_end * distance_to_end))

        assert np.isclose(this_r_end, radii_to_end)

    return


#!/usr/bin/env python3.8
WHITE, BLACK = " ", "#"

def create_chessboard(size: int = 8) -> None:
    """Create a chessboard with of the size passed in.
       Don't return anything, print the output to stdout"""
    for rows in range(size):
        row = f"{WHITE}{BLACK}" * int(size / 2)
        print(row if rows % 2 == 0 else row[::-1])

create_chessboard(3)


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

class Migration(migrations.Migration):

    dependencies = [
        ('region', '0001_initial'),
    ]

    operations = [
        migrations.CreateModel(
            name='Newsletter',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('created', models.DateTimeField(editable=False)),
                ('updated', models.DateTimeField(editable=False)),
                ('send', models.DateTimeField(verbose_name=b'Gesendet')),
                ('n', models.IntegerField(verbose_name=b'Anzahl Mails')),
            ],
            options={
                'verbose_name': 'Newsletter',
                'verbose_name_plural': 'Newsletter',
            },
        ),
        migrations.CreateModel(
            name='Subscriber',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('created', models.DateTimeField(editable=False)),
                ('updated', models.DateTimeField(editable=False)),
                ('email', models.EmailField(unique=True, max_length=254)),
                ('entities', models.ManyToManyField(related_name='subscribers', to='region.Entity')),
            ],
            options={
                'verbose_name': 'Abonnent',
                'verbose_name_plural': 'Abonnenten',
            },
        ),
        migrations.CreateModel(
            name='Validation',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('created', models.DateTimeField(editable=False)),
                ('updated', models.DateTimeField(editable=False)),
                ('email', models.EmailField(unique=True, max_length=254)),
                ('entities', models.CharField(max_length=256, null=True, blank=True)),
                ('code', models.SlugField(max_length=32)),
                ('action', models.CharField(max_length=16)),
            ],
            options={
                'verbose_name': 'Validierung',
                'verbose_name_plural': 'Validierung',
            },
        ),
    ]


from z3 import *

def lex_lesseq(x, y):
    # given arrays x and y, returns constraint imposing lex<= order between a and b
    # X1 ≤ Y1 ∧ (X1 = Y1 -> X2 ≤ Y2) ∧ (X1 = Y1 ∧ X2 = Y2 ... ∧ Xk-1 = Yk-1 -> Xk ≤ Yk)
    # note that in SAT ≤ is encoded as ->
    # more elegant but maybe less efficient:
    # And([Implies(And([x[i] == y[i] for i in range(k)]), Implies(x[k], y[k])) for k in range(len(x))])

    return And([Implies(x[0], y[0])] +
               [Implies(And([x[i] == y[i] for i in range(k)]), Implies(x[k], y[k]))
                for k in range(1, len(x))])

def lex(x, y):
    # given arrays x and y, returns constraint imposing lex<= order between a and b
    # X1 ≤ Y1 ∧
    # (X1 = Y1 -> X2 ≤ Y2) ∧
    # (X1 = Y1 ∧ X2 = Y2 -> X3 ≤ Y3) ...
    # (X1 = Y1 ∧ X2 = Y2 ... ∧ Xk-1 = Yk-1 -> Xk ≤ Yk)
    # more elegant but maybe less efficient:
    # And([Implies(And([x[i] == y[i] for i in range(k)]), x[k] <= y[k]) for k in range(len(x))])

    return And([x[0] <= y[0]] +
               [Implies(And([x[i] == y[i] for i in range(k)]), x[k] <= y[k]) for k in range(1, len(x))])

def flatten(matrix):
    return [cell for row in matrix for cell in row]

def base_model(instance, dual, rotation):
    vs = {}
    # define main problem variables and constraints
    w = IntVal(instance["w"])
    l = Int('length')
    circuits = list(range(instance['n']))
    vs['w'], vs['l'] = w, l
    # basic problem constraints
    constraints = [vs['l'] >= instance['minl'], vs['l'] <= instance['maxl']]

    for k in circuits:
        xhat_i, yhat_i = Ints(f'xhat_{k} yhat_{k}')
        vs[f'xhat_{k}'], vs[f'yhat_{k}'] = xhat_i, yhat_i

        if rotation:
            x_i, y_i = Ints(f'x_{k} y_{k}')
            rotation_i = Bool(f'rotation_{k}')
            vs[f'rotation_{k}'] = rotation_i
            constraints.append(If(rotation_i,
                                And(y_i == (instance['inputx'][k]), x_i == (instance['inputy'][k])),
                                And(y_i == (instance['inputy'][k]), x_i == (instance['inputx'][k]))))
        else:
            x_i = IntVal(instance['inputx'][k])
            y_i = IntVal(instance['inputy'][k])
        vs[f'x_{k}'], vs[f'y_{k}'] = x_i, y_i

        constraints.append(xhat_i >= 0)
        constraints.append(xhat_i < w)
        constraints.append(xhat_i + x_i <= w)
        constraints.append(yhat_i >= 0)
        constraints.append(yhat_i < l)
        constraints.append(yhat_i + y_i <= l)

    if dual:
        for c in range(instance["w"]):
            xproj_c = Bool(f'xproj_{c}')
            vs[f'xproj_{c}'] = xproj_c
            constraints.append(xproj_c == (l == Sum([If(vs[f'xhat_{i}'] == c, vs[f'y_{i}'], 0) for i in circuits])))

        for r in range(instance["maxl"]):
            yproj_r = Bool(f'yproj_{r}')
            vs[f'yproj_{r}'] = yproj_r
            constraints.append(yproj_r == (w == Sum([If(vs[f'yhat_{i}'] == r, vs[f'x_{i}'], 0) for i in circuits])))
            # constraints.append(yproj_r == (w == Sum([vs[f'x_{i}'] for i in circuits if vs[f'yhat_{i}'] == r])))

        xproj = [vs[f'xproj_{c}'] for c in range(instance["w"])]
        xproj_sym = [vs['xproj_0']] + [vs[f'xproj_{c}'] for c in range(instance["w"] - 1, 0, -1)]
        yproj = [vs[f'yproj_{r}'] for r in range(instance["maxl"])]
        yproj_sym = [vs['yproj_0']] + [vs[f'yproj_{r}'] for r in range(instance["maxl"] - 1, 0, -1)]
        constraints += [lex_lesseq(xproj, xproj_sym),
                        lex_lesseq(yproj, yproj_sym)]

    # implied constraints: for each horizontal (and vertical) line, the sum of traversed sides is bounded
    for col in range(instance['w']):
        constraints.append(Sum([If(And(vs[f'xhat_{i}'] < col, col <= vs[f'xhat_{i}'] + vs[f'x_{i}']), vs[f'y_{i}'], 0)
                                for i in circuits]) <= vs['l'])
    for row in range(instance['maxl']):
        constraints.append(Sum([If(And(vs[f'yhat_{i}'] < row, row <= vs[f'yhat_{i}'] + vs[f'y_{i}']), vs[f'x_{i}'], 0)
                                for i in circuits]) <= vs['w'])

    # no overlap constraints
    constraints += [
        Or(vs[f'xhat_{i}'] + vs[f'x_{i}'] <= vs[f'xhat_{j}'], vs[f'yhat_{i}'] + vs[f'y_{i}'] <= vs[f'yhat_{j}'],
           vs[f'xhat_{j}'] + vs[f'x_{j}'] <= vs[f'xhat_{i}'], vs[f'yhat_{j}'] + vs[f'y_{j}'] <= vs[f'yhat_{i}'])
        for i in circuits for j in circuits if i < j]

    # symmetry breaking
    # rows and columns symmetry
    constraints += [Implies(And(vs[f'xhat_{i}'] == vs[f'xhat_{j}'], vs[f'x_{i}'] == vs[f'x_{j}']),
                            vs[f'yhat_{i}'] <= vs[f'yhat_{j}'])
                    for i in circuits for j in circuits if i < j]

    constraints += [Implies(And(vs[f'yhat_{i}'] == vs[f'yhat_{j}'], vs[f'y_{i}'] == vs[f'y_{j}']),
                            vs[f'xhat_{i}'] <= vs[f'xhat_{j}'])
                    for i in circuits for j in circuits if i < j]
    # three block symmetry
    constraints += [Implies(And(vs[f'xhat_{i}'] == vs[f'xhat_{j}'], vs[f'x_{i}'] == vs[f'x_{j}'],
                                vs[f'yhat_{i}'] == vs[f'yhat_{k}'], vs[f'y_{i}'] + vs[f'y_{j}'] == vs[f'y_{k}']),
                            vs[f'xhat_{k}'] <= vs[f'xhat_{i}'])
                    for i in circuits for j in circuits for k in circuits if i < j < k]

    constraints += [Implies(And(vs[f'yhat_{i}'] == vs[f'yhat_{j}'], vs[f'y_{i}'] == vs[f'y_{j}'],
                                vs[f'xhat_{i}'] == vs[f'xhat_{k}'], vs[f'x_{i}'] + vs[f'x_{j}'] == vs[f'x_{k}']),
                            vs[f'yhat_{k}'] <= vs[f'yhat_{i}'])
                    for i in circuits for j in circuits for k in circuits if i < j < k]

    # force the biggest block to be always to the bottom left of the second biggest
    constraints.append(And(vs['xhat_0'] <= vs['xhat_1'], vs['yhat_0'] <= vs['yhat_1']))

    return constraints, vs


import random

# File Path
ALL_WORDS_PATH = "C:/Users/CHENTH/Documents/Python/Wordle/Wordle/word-bank/all_words.txt"
TARGET_WORDS_PATH = "C:/Users/CHENTH/Documents/Python/Wordle/Wordle/word-bank/target_words.txt"
# ALL_WORDS_PATH = "/Users/thomas/Documents/GitHub/Wordle/Wordle/word-bank/all_words.txt"
# TARGET_WORDS_PATH = "/Users/thomas/Documents/GitHub/Wordle/Wordle/word-bank/target_words.txt"

# Play the game
def play():
    '''
    Combines functions and starts the game
    '''
    attempts = 0
    target_word = get_target_word()
    if cheats == True:
        print("The target word is: " + target_word)
    while attempts < 6:
        guess_word = guess_input()
        target_letter = list(target_word)
        while True:
            if check_valid_word(guess_word) == True:
                break
            else:
                guess_word = guess_input()
        guess_letter = list(guess_word)
        attempts = attempts + 1
        is_correct(guess_word, target_word, attempts)
        score = score_guess(guess_word, target_word)
        read_score(score, target_letter, guess_letter, display)
        attempts_left(attempts)
    else:
        print("You've failed, the word was: " + Colours.GREEN + target_word + Colours.END)
    play_again()

# Choosing Random Target Word
def get_target_word():
    '''
    Opens target_words.txt and begins choosing a random word to be the target word of the game

    Returns:
        str: target_word
    '''
    fh = open(TARGET_WORDS_PATH, "r")
    words_list = []
    for words in fh:
        words = words.strip()
        words_list.append(words)
    target_word = random.choice(words_list)
    return target_word

# Ask for guess
def guess_input():
    '''
    Ask for user to input guess

    Returns:
        str: The user input/guess
    '''
    return (input("Enter guess: "))

# Check for a valid word
def check_valid_word(guess_word):
    '''
    Checks user input and compares it to all_words.txt, checking if user input is a valid 5 letter word in the english dictionary

    Args:
        guess_word (str): takes in user's guess word/input

    Returns:
        bool: Returns true for valid word and false for invalid word

    '''
    fh = open(ALL_WORDS_PATH, "r")
    valid_words = []
    for words in fh:
        words = words.strip()
        valid_words.append(words)
    if guess_word not in valid_words:
        print("This isnt a word")
        return False
    else:
        return True

# Check for correct guess
def is_correct(guess_word, target_word, attempts):
    '''
    Skips scoring process and checks if guess word directly matches the target word

    Args:
        guess_word (str): User input/guess
        target_word (str): Target word for the game
        attempts (int): Attempts left in the game

    '''
    if guess_word == target_word:
        if attempts == 1:
            print("Correct word is: " + Colours.GREEN + target_word + Colours.END + "\nCongratul---... Wait! You got it in one attempt??")
            play_again()
        elif attempts == 2:
            print("Correct word is: " + Colours.GREEN + target_word + Colours.END + "\nNot bad, figuring out the word in 2 attempts.")
            play_again()
        elif attempts >= 3:
            print("Correct word is: " + Colours.GREEN + target_word + Colours.END + "\nCongratulations!! You did it in " + str(attempts) + " attemps!")
            play_again()
    else:
        return

# Scores guess
def score_guess(guess_word, target_word):
    '''
    Scores the user's guess with
    2: correct letter and position
    1: correct letter, wrong position
    0: wrong letter and position

    Args:
        guess_word (str): user input/guess
        target_word (str): The target word of the game

    Returns:
        list: list format with numbered score in each position, default score [0, 0, 0, 0, 0]
    '''
    position = [0, 0, 0, 0, 0]
    guess_letter = list(guess_word)
    target_letter = list(target_word)

    for i in range(0, 5):
        if guess_letter[i] == target_letter[i]:
            position[i] = 2
        if position[i] == 0:
            if guess_letter[i] in target_word:
                position[i] = 1
    return position

# Change scores to be readable
def read_score(score, target_letter, guess_letter, display):
    '''
    Reads the score returned by score_guess() and processes it to become readable to the user

    Args:
        score (list:'int'): Scoring in list form returned by score_guess()
        target_letter (list:'str'): Letter corresponding to index in the target word
        guess_letter (list: 'str'): Letter corresponding to index in the guess word
        display (list: 'str'): Changes display to display correctly guessed letters
    '''
    for i in score:
        if i == 2:
            colour = Colours.GREEN
            index = score.index(i)
            score[index] = colour + target_letter[index] + Colours.END
            keyboard(colour, guess_letter[index])
            display[index] = Colours.BOLD + Colours.GREEN + Colours.UNDERLINE + target_letter[index] + Colours.END
        if i == 1:
            colour = Colours.YELLOW
            index = score.index(i)
            score[index] = colour + guess_letter[index] + Colours.END
            keyboard(colour, guess_letter[index])
        if i == 0:
            colour = Colours.RED
            index = score.index(i)
            score[index] = colour + guess_letter[index] + Colours.END
            keyboard(colour, guess_letter[index])
    score = ' '.join(score)
    print(score + "\n")
    list_guess(score)
    print("\t" + ' '.join(display) + "\n")
    print_keyboard()

# Class holding colors
class Colours:
    '''
    Class containing strings to modify colour in terminal
    '''
    GREEN = '\033[92m'
    YELLOW = '\033[93m'
    RED = '\033[91m'
    END = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'

# Print keyboard
def print_keyboard():
    '''
    Prints modified keyboard to look aesthetically spaced in terminal
    '''
    print("   " + "  ".join(keys1))
    print("     " + "  ".join(keys2))
    print("\t" + "  ".join(keys3))

# Colour keyboard
def keyboard(colour, letter):
    '''
    Modifies keyboard display to show correctly guessed letters with corresponding colours
    '''
    letter = letter.upper()
    while True:
        if letter in keys1:
            index = keys1.index(letter)
            if keys1[index].startswith(Colours.GREEN):
                return
            else:
                keys1[index] = colour + letter + Colours.END
        elif letter in keys2:
            index = keys2.index(letter)
            if keys2[index].startswith(Colours.GREEN):
                return
            else:
                keys2[index] = colour + letter + Colours.END
        elif letter in keys3:
            index = keys3.index(letter)
            if keys3[index].startswith(Colours.GREEN):
                return
            else:
                keys3[index] = colour + letter + Colours.END
        else:
            return

# Print out attempts left
def attempts_left(attempts):
    '''
    Calculates the attempts left by the user

    Args:
        attempts (int): The attempts used by the user
    '''
    attempts_left_over = MAX_ATTEMPTS - attempts
    if attempts_left_over >= 3:
        print("You have " + Colours.GREEN + str(attempts_left_over) + Colours.END + " attempts left")
    if attempts_left_over == 2:
        print("You have " + Colours.YELLOW + str(attempts_left_over) + Colours.END + " attempts left")
    if attempts_left_over == 1:
        print("You have " + Colours.RED + str(attempts_left_over) + Colours.END + " attempt left")

# Start asking for command input
def start():
    '''
    Starts the start-up interface for the game and sets the global variables to be used in game
    '''
    global MAX_ATTEMPTS
    MAX_ATTEMPTS = 6
    global cheats
    cheats = False
    # Global lists holding keyboard letters and display
    global keys1, keys2, keys3, display, guess_list
    keys1 = list("QWERTYUIOP")
    keys2 = list("ASDFGHJKL")
    keys3 = list("ZXCVBNM")
    display = list("_____")
    guess_list = ["\tGUESSES"]
    while True:
        command_input = input("Would you lke to play Wordle? Y/N: ")
        if command_input.upper() == "Y":
            play()
        if command_input.upper() == "N":
            command_input = input("What would you like to do?\n1 - Change Max Attempts\n2 - Help\n3 - Toggle Answer Reveal: " + str(cheats) + "\n4 - Back\n5 - Exit\nPlease enter number corresponding to command: ")
            if command_input == "1":
                MAX_ATTEMPTS = int(input("The current Max Attempts is: " + str(MAX_ATTEMPTS) + ". What would you like it to be: "))
                continue
            elif command_input == "2":
                wordle_help()
                continue
            elif command_input == "3":
                if cheats == True:
                    cheats = False
                elif cheats == False:
                    cheats = True
                continue
            elif command_input == "4":
                continue
            elif command_input == "5":
                exit()
            else:
                print("The entered input is invalid or out of scope")
                continue

# User input to play again after finished game
def play_again():
    '''
    Prompts user input to choose to play again after the game finishes
    '''
    user_input = input("Would you like to start again?: Y/N \n")
    if user_input.upper() == "Y":
        start()
    else:
        exit()

def list_guess(score):
    '''
    Display all guesses by the user in the current game with appropriate colouring

    Args:
        score (list'str'): Score for the users guess/input
    '''
    guess_list.append(score)
    print("\n\t".join(guess_list) + "\n")
def wordle_help():
    print("You are given " + Colours.GREEN + str(MAX_ATTEMPTS) + Colours.END + " chances to guess the correct 5 letter word, with some hints that will help you along the way\n1) Any correctly guessed letter, but in the wrong position will be coloured " + Colours.YELLOW + "YELLOW" + Colours.END + "\n2) Any correctly guessed letters in the right position will be coloured " + Colours.GREEN + "GREEN" + Colours.END + "\n3) Any incorrectly guessed letters will be coloured " + Colours.RED + "RED" + Colours.END + "\nFinally a keyboard with the corresponding coloured letters will be displayed for your convenience")

start()