Beispiel #1
0
def get_people(terminal: Terminal):
    people = []
    known_folders = os.listdir(OUTPUT_CSVS_FRIENDS_DIR)
    try:
        progress_bar = ProgressBar(
            'Loading knowns from .csv files', max=len(known_folders))
        for folder in known_folders:
            knowns_file = '%s/%s/known_people.csv' % (
                OUTPUT_CSVS_FRIENDS_DIR, folder)
            if os.path.exists(knowns_file):
                with open(knowns_file, 'r') as f:
                    reader = csv.DictReader(f)
                    for individual in reader:
                        people.append(individual)
                    f.close()
            progress_bar.next()
        print()
        with open('%s/known_people.csv' % OUTPUT_CSVS_DIR, 'r') as f:
            reader = csv.DictReader(f)
            progress_bar_knowns = ProgressBar(
                'Loading knowns from .csv files', max=sum(1 for row in reader) - 1)
            for individual in reader:
                people.append(individual)
                progress_bar_knowns.next()
            f.close()
    except FileNotFoundError as ex:
        terminal.error(str(ex))
    return uniquify(people)
Beispiel #2
0
class Player:
    def __init__(self, name, board=None, color=None, wait_time=None):
        self.term = Terminal()
        self.name = f'{name} ({self.__class__.__name__})'
        self.board = board
        self.color = color

        self.wins = 0
        self.losses = 0
        self.ties = 0

    def _take_turn(self):
        if self.board is None:
            raise NoGameInProgress('No board has been assigned to this player')

        self.term.move(*Location(19, 0))
        print(f"{self.color or ''}{self.name}{self.term.normal}'s turn")
        cup = self.take_turn()

        if cup is None:
            raise Exception('A cup must be returned by the user')
        return cup

    def take_turn(self):
        raise NotImplementedError(
            'take_turn must be implemented by subclasses')

    def assign_board(self, board):
        self.board = board

    def game_over(self, result):
        if result == Result.Win:
            self.wins += 1
        elif result == Result.Loss:
            self.losses += 1
        elif result == Result.Tie:
            self.ties += 1
        else:
            raise ValueError('Result not recognized. Got {result}')

        self.board = None

    @property
    def games_played(self):
        return self.wins + self.losses + self.ties

    @property
    def is_player1(self):
        return self.board.player1 == self

    @property
    def is_player2(self):
        return self.board.player2 == self

    @property
    def cup_index(self):
        return (self.board.player_1_cup_index
                if self.is_player1 else self.board.player_2_cup_index)
Beispiel #3
0
    def __init__(self, name, board=None, color=None, wait_time=None):
        self.term = Terminal()
        self.name = f'{name} ({self.__class__.__name__})'
        self.board = board
        self.color = color

        self.wins = 0
        self.losses = 0
        self.ties = 0
Beispiel #4
0
    def __init__(self) -> None:
        # basic init for pygame: window/screen, blackground, clock and fonts
        # fonts need to be initialized extra
        pygame.init()
        pygame.font.init()
        pygame.display.set_caption(GAME_NAME)

        self.window = pygame.display.set_mode(
            (WINDOW_SIZE_X + TERMINAL_WIDTH, WINDOW_SIZE_Y))
        self.background = pygame.Surface(
            (TILE_HEIGHT * ROWS, TILE_WIDTH * COLS))
        self.clock = pygame.time.Clock()
        self.terminal_font = pygame.font.SysFont(FONT_TYPE, TERMINAL_FONT_SIZE)
        self.description_font = pygame.font.SysFont(FONT_TYPE,
                                                    DESCRIPTION_FONT_SIZE)
        self.game_over_font = pygame.font.SysFont(FONT_TYPE,
                                                  GAME_OVER_FONT_SIZE)

        # init chessboard
        #   an array of meeples is created that reflects the default setup
        #   sprites are loaded from board list which are then added to the sprite group, because it is easier to draw them
        self.board = Chessboard()
        self.sprites = self.board.loadSprites()
        self.chess_sprite_list = pygame.sprite.Group()
        self.chess_sprite_list.add(self.sprites)

        # tile representation of the chessboard
        # used to determine tile/meeple on click events
        self.chessboard_tiles = self.board.loadTiles(self.background)

        # init terminal
        #   clickable button without UX -> no highlighting are click reaction
        #   terminal window that logs the moves performed by the players
        #   notations for each player are saved to different arrays; if notations exceed 28 lines -> list.pop is used / no scrolling possible
        #   instance variables for a white meeple that a player has selected and the possbile moves for the according meeple
        self.terminal = Terminal(self.terminal_font)

        # init black player
        self.bot = Bot()

        # draw terminal on the right side of the game window including a "new game" button
        drawTerminal(self)

        # conditions for the game loop
        self.gameRunning = True  # condition for gameloop
        self.playerTurn = True  # white starts playing
        self.selectedField = False  # player has selected a field
        self.check_white = False
        self.check_black = False
        self.draw = False
        self.check_mate_white = False
        self.check_mate_black = False

        # init first possible moves for white player
        # saves the next possibles for each side
        self.number_of_moves = self.board.getAllMoves("w")
Beispiel #5
0
    def __init__(
        self,
        prompt,
        choices=None,
    ):
        self.term = Terminal()
        self.prompt = prompt
        self.choices = list(choices) if choices else None

        self._inital_location = INITIAL_MENU_LOCATION
Beispiel #6
0
    def __init__(
        self,
        *,
        player1,
        player2,
        side_length=6,
        initial_seeds=None,
        player_1_color=None,
        player_2_color=None,
        animation_wait=0.1,
    ):
        self.term = Terminal()

        if player_1_color is None:
            player_1_color = self.term.bold + self.term.red

        if player_2_color is None:
            player_2_color = self.term.bold + self.term.blue

        seed_color = self.term.green
        index_color = self.term.yellow

        self.term.clear()
        self.term.move(Location(5, 5))

        self.board = Board(
            side_length,
            seed_color=seed_color,
            index_color=index_color,
            animation_wait=animation_wait,
        )

        self.player1 = player1
        self.player2 = player2

        self.board.assign_player(self.player1)
        self.board.assign_player(self.player2)

        self.current_player = self.player1
        self._players = (self.player1, self.player2)

        seeds = initial_seeds or self._get_initial_seeds()
        self.board.initialize_cups(seeds)
        self.board.clear_board()
        self.board.display_cups()
Beispiel #7
0
def get_connections(terminal: Terminal):
    people_file = '%s/people.csv' % OUTPUT_CSVS_DIR
    connections = []
    people = []
    persons = []
    if not os.path.exists(people_file):
        terminal.info('Generating people file')
        people = get_people(terminal)
        save_list_of_dicts(people_file, people, ['id', 'name'])
    with open(people_file, 'r') as f:
        terminal.info('Reading file: %s' % people_file)
        reader = csv.DictReader(f)
        for row in reader:
            person = Person(id=row['id'], name=row['name'])
            persons.append(person)

    for person in persons:
        person.get_knowns(0)
        for con in get_known_connections(person):
            connections.append(con)
Beispiel #8
0
class GetUserInput:
    def __init__(
        self,
        prompt,
        choices=None,
    ):
        self.term = Terminal()
        self.prompt = prompt
        self.choices = list(choices) if choices else None

        self._inital_location = INITIAL_MENU_LOCATION

    def get_response(self):
        self.term.clear()
        self.term.move(self._inital_location)

        if self.choices:
            print(self.prompt)
            for idx, choice in enumerate(self.choices):
                print(f'     ({idx + 1})  {str(choice)}')

        done = False
        while not done:
            if self.choices:
                user_resp = input(f'Enter [1-{len(self.choices)}]: ')
            else:
                user_resp = input(self.prompt)

            try:
                val = int(user_resp)

                if self.choices:
                    choice = self.choices[val - 1]
                else:
                    choice = val

                done = True
            except ValueError:
                continue

        return choice
Beispiel #9
0
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi(self)
        self.setAttribute(Qt.WA_QuitOnClose)

        self._connection_scanner = ConnectionScanner()
        self._connection = None
        self._root_dir = Settings.root_dir
        self._mcu_files_model = None
        self._terminal = Terminal()
        self._terminal_dialog = None
        self._code_editor = None
        self._flash_dialog = None

        self.actionNavigate.triggered.connect(self.navigate_directory)
        self.actionTerminal.triggered.connect(self.open_terminal)
        self.actionCode_Editor.triggered.connect(self.open_code_editor)
        self.actionUpload.triggered.connect(self.upload_transfer_scripts)
        self.actionFlash.triggered.connect(self.open_flash_dialog)

        self.connectionComboBox.currentIndexChanged.connect(
            self.connection_changed)
        self.refreshButton.clicked.connect(self.refresh_ports)

        # Populate baud speed combo box and select default
        self.baudComboBox.clear()
        for speed in BaudOptions.speeds:
            self.baudComboBox.addItem(str(speed))
        self.baudComboBox.setCurrentIndex(BaudOptions.speeds.index(115200))

        self.presetButton.clicked.connect(self.show_presets)
        self.connectButton.clicked.connect(self.connect_pressed)

        self.update_file_tree()

        self.listButton.clicked.connect(self.list_mcu_files)
        self.listView.clicked.connect(self.mcu_file_selection_changed)
        self.listView.doubleClicked.connect(self.read_mcu_file)
        self.executeButton.clicked.connect(self.execute_mcu_code)
        self.removeButton.clicked.connect(self.remove_file)
        self.localPathEdit.setText(self._root_dir)

        self.treeView.clicked.connect(self.local_file_selection_changed)
        self.treeView.doubleClicked.connect(self.open_local_file)

        self.transferToMcuButton.clicked.connect(self.transfer_to_mcu)
        self.transferToPcButton.clicked.connect(self.transfer_to_pc)

        self.disconnected()
Beispiel #10
0
def find_knowns_of(terminal: Terminal, params: dict):
    profile_id = params.get('id', None)
    if not profile_id:
        terminal.error('No profile id provided')
        terminal.info('Try again using -profile-id=<profile id>')
        return
    else:
        terminal.info('Getting knowns of %s' % profile_id)
        try:
            knowns_of = facebook_api.get_knowns_of(profile_id)
            if len(knowns_of) > 0:
                known_directory = '%s/%s' % (
                    OUTPUT_CSVS_FRIENDS_DIR, profile_id)
                file_name = '%s/known_people.csv' % known_directory
                try:
                    os.mkdir(known_directory)
                except FileExistsError:
                    pass
                save_list_of_dicts(file_name, knowns_of, ['id', 'name'])
        except GraphAPIError as ex:
            terminal.error(str(ex))
Beispiel #11
0
    def __init__(
        self,
        initial_seeds=3,
        number_of_games=10,
        animation_wait=0.001,
        player1=None,
        player2=None,
    ):
        self.term = Terminal()

        if number_of_games is None:
            number_of_games = GetUserInput(
                'Enter number of games: ').get_response()

        self.number_of_games = number_of_games
        self.initial_seeds = initial_seeds
        self.animation_wait = animation_wait

        player_1_color = self.term.bold + self.term.red
        player_2_color = self.term.bold + self.term.blue

        if not player1:
            player_class = GetUserInput(
                'Enter player type for Player 1:',
                PlayerFactory.all_classes()).get_response()
            self.player1 = player_class('Player 1',
                                        color=player_1_color,
                                        wait_time=animation_wait)
        else:
            self.player1 = player1

        if not player2:
            player_class = GetUserInput(
                'Enter player type for Player 2:',
                PlayerFactory.all_classes()).get_response()
            self.player2 = player_class('Player 2',
                                        color=player_2_color,
                                        wait_time=animation_wait)
        else:
            self.player2 = player2
Beispiel #12
0
class Game:
    def __init__(self) -> None:
        # basic init for pygame: window/screen, blackground, clock and fonts
        # fonts need to be initialized extra
        pygame.init()
        pygame.font.init()
        pygame.display.set_caption(GAME_NAME)

        self.window = pygame.display.set_mode(
            (WINDOW_SIZE_X + TERMINAL_WIDTH, WINDOW_SIZE_Y))
        self.background = pygame.Surface(
            (TILE_HEIGHT * ROWS, TILE_WIDTH * COLS))
        self.clock = pygame.time.Clock()
        self.terminal_font = pygame.font.SysFont(FONT_TYPE, TERMINAL_FONT_SIZE)
        self.description_font = pygame.font.SysFont(FONT_TYPE,
                                                    DESCRIPTION_FONT_SIZE)
        self.game_over_font = pygame.font.SysFont(FONT_TYPE,
                                                  GAME_OVER_FONT_SIZE)

        # init chessboard
        #   an array of meeples is created that reflects the default setup
        #   sprites are loaded from board list which are then added to the sprite group, because it is easier to draw them
        self.board = Chessboard()
        self.sprites = self.board.loadSprites()
        self.chess_sprite_list = pygame.sprite.Group()
        self.chess_sprite_list.add(self.sprites)

        # tile representation of the chessboard
        # used to determine tile/meeple on click events
        self.chessboard_tiles = self.board.loadTiles(self.background)

        # init terminal
        #   clickable button without UX -> no highlighting are click reaction
        #   terminal window that logs the moves performed by the players
        #   notations for each player are saved to different arrays; if notations exceed 28 lines -> list.pop is used / no scrolling possible
        #   instance variables for a white meeple that a player has selected and the possbile moves for the according meeple
        self.terminal = Terminal(self.terminal_font)

        # init black player
        self.bot = Bot()

        # draw terminal on the right side of the game window including a "new game" button
        drawTerminal(self)

        # conditions for the game loop
        self.gameRunning = True  # condition for gameloop
        self.playerTurn = True  # white starts playing
        self.selectedField = False  # player has selected a field
        self.check_white = False
        self.check_black = False
        self.draw = False
        self.check_mate_white = False
        self.check_mate_black = False

        # init first possible moves for white player
        # saves the next possibles for each side
        self.number_of_moves = self.board.getAllMoves("w")

    def run(self):
        while self.gameRunning:
            if pygame.event.get(pygame.QUIT):
                sys.exit()

            # white player's turn
            if self.playerTurn:

                if pygame.event.get(pygame.MOUSEBUTTONDOWN):
                    mouse_x, mouse_y = pygame.mouse.get_pos()

                    # reset the game when clicking on the "new game" button by calling __init__ from Game
                    if self.terminal.button.collidepoint((mouse_x, mouse_y)):
                        self.__init__()
                        self.board.getAllMoves("w")

                    if self.number_of_moves > 0:
                        self.check_white = False
                        # finds selected tiles on the chessboard
                        selected_tile = [
                            tile for tile in self.chessboard_tiles
                            if tile.collidepoint((mouse_x, mouse_y))
                        ]

                        # if a position outside the chessboard was selected, jump to the next iteration of the game loop
                        # prevents out of bound exception
                        if len(selected_tile) != 1:
                            continue

                        tile_x, tile_y = convert(selected_tile[0].x,
                                                 selected_tile[0].y)

                        # in this game status the white player has to select a meeple to see possible moves
                        # a move can only be performed when a highlighted possible move is clicked
                        if not self.selectedField:
                            meeple = self.board.highlightMeeple(
                                tile_x, tile_y)  # choose from available moves

                            # if the clicked meeple is a white meeple -> the according tiles are highlighted
                            if meeple != None and meeple.colour == "w":
                                moves = meeple.possible_moves
                                self.board.highlightMoves(moves)
                                self.selectedField = True
                        else:
                            # while possible moves (yellow tiles) are displayed, clicking on the highlighted meeple (green) or on any
                            # other tile on the chessboard, desselects the possible moves and the highlighted meeple and returns to previous
                            # if statement where the player has to select a meeple
                            if not (tile_x,
                                    tile_y) in self.board.highlightedMoveTiles:
                                self.board.highlightMoves([])
                                self.board.highlightedMeeple = None
                                self.selectedField = False

                            else:
                                # if a hightlighted possible move is clicked (yellow tiles), the move to this tile is performed
                                # sprites group is updated, check status calculated and move rendered on terminal
                                self.board.highlightMoves([])
                                result = self.board.moveMeeple(
                                    (tile_x, tile_y))

                                self.updateSprites()
                                self.check_black = self.board.king_b.isCheck(
                                    self.board)
                                self.number_of_moves = self.board.getAllMoves(
                                    "b")
                                self.displayNotation(result)
                                self.playerTurn = False

            # black player's turn represented by a stupid computer player
            else:
                self.playerTurn = True
                self.selectedField = False

                # directly skip to end game screen when black is check mate; if possibles moves is > 0 check can be dissolved because
                # there are only moves calculated that "free" the king of check
                if self.number_of_moves > 0:
                    # delay black side move a little bit to see that the black king was checked
                    pygame.time.delay(400)
                    self.check_black = False

                    meeple = self.bot.run(self.board)

                    meeple = self.board.highlightMeeple(meeple.x, meeple.y)
                    result = self.board.moveMeeple(
                        random.choice(meeple.possible_moves))

                    self.updateSprites()
                    self.check_white = self.board.king_w.isCheck(self.board)
                    self.number_of_moves = self.board.getAllMoves("w")
                    self.displayNotation(result, "b")

            #draw GUI - end of loop
            self.drawChessboard()

            if self.draw or self.check_mate_black or self.check_mate_white:
                showGameOver(self)

            pygame.display.flip()
            self.clock.tick(30)

    # when a meeple is moved, the function is used to update the sprites group which is rendered thereafter
    def updateSprites(self):
        self.chess_sprite_list.empty()
        meeple_sprites = self.board.loadSprites()
        self.chess_sprite_list.add(meeple_sprites)

    def displayNotation(self, result: List, side: str = "w") -> None:
        if side == "w":
            if self.number_of_moves > 0 and self.check_black:
                result[6].append("check")
                self.check_black = True

            if self.number_of_moves < 1 and self.check_black:
                result[6].append("check_mate")
                self.check_mate_black = True

            if self.number_of_moves < 1 and not self.check_black:
                result[6].append("draw")
                self.draw = True
        else:
            if self.number_of_moves > 0 and self.check_white:
                result[6].append("check")
                self.check_white = True

            if self.number_of_moves < 1 and self.check_white:
                result[6].append("check_mate")
                self.check_mate_white = True

            if self.number_of_moves < 1 and not self.check_white:
                result[6].append("draw")
                self.draw = True

        self.terminal.addNotation(result, side)

    # important that all drawings on the chessboard are in one function so the layering can be controlled
    def drawChessboard(self) -> None:
        self.window.blit(self.background, (0, 0))
        pygame.draw.rect(self.window, COLOUR_WHITE, self.terminal.button)
        pygame.draw.rect(self.window, COLOUR_WHITE,
                         self.terminal.terminal_background)
        self.window.blit(self.terminal.button_text,
                         (BUTTON_TEXT_X, BUTTON_TEXT_Y))

        # indicator for the selected meeple and the resulting possible moves
        # selected player meeple is highlighted with a green rec while possible moves are indicated with yellow recs
        highlights = self.board.drawHighlightedMeeple(
        ) + self.board.drawHighlightedMoves()
        board_description = self.board.loadChessboardDescription(
            self.description_font)

        for highlight_info in highlights:
            if highlight_info != None:
                self.window.blit(highlight_info[0],
                                 (highlight_info[1], highlight_info[2]))

        # board descrption in red color that indicate the row and col
        for letter_info in board_description[0]:
            if letter_info != None:
                self.window.blit(letter_info[0],
                                 (letter_info[1], letter_info[2]))

        for number_info in board_description[1]:
            if number_info != None:
                self.window.blit(number_info[0],
                                 (number_info[1], number_info[2]))

        # draws text from terminal arrays to terminal box with a column for white (left side) including the numbered round and black (right side)
        for index, text in enumerate(self.terminal.terminal_white_notation):
            self.window.blit(text, (TERMINAL_TEXT_X_WHITE, TERMINAL_TEXT_Y +
                                    index * TERMINAL_TEXT_Y_OFFSET))

        for index, text in enumerate(self.terminal.terminal_black_notation):
            self.window.blit(text, (TERMINAL_TEXT_X_BLACK, TERMINAL_TEXT_Y +
                                    index * TERMINAL_TEXT_Y_OFFSET))

        # draws a red rectangle under the kings if they are check
        if self.check_black:
            surface = pygame.Surface((TILE_WIDTH, TILE_HEIGHT))
            surface.set_alpha(COLOUR_ALPHA)
            surface.fill(COLOUR_RED)
            self.window.blit(surface, (self.board.king_b.x * TILE_HEIGHT,
                                       self.board.king_b.y * TILE_WIDTH))

        if self.check_white:
            surface = pygame.Surface((TILE_WIDTH, TILE_HEIGHT))
            surface.set_alpha(COLOUR_ALPHA)
            surface.fill(COLOUR_RED)
            self.window.blit(surface, (self.board.king_w.x * TILE_HEIGHT,
                                       self.board.king_w.y * TILE_WIDTH))

        #draw sprites at last so they are on top of everything
        self.chess_sprite_list.draw(self.window)
Beispiel #13
0
class Game:
    def __init__(
        self,
        *,
        player1,
        player2,
        side_length=6,
        initial_seeds=None,
        player_1_color=None,
        player_2_color=None,
        animation_wait=0.1,
    ):
        self.term = Terminal()

        if player_1_color is None:
            player_1_color = self.term.bold + self.term.red

        if player_2_color is None:
            player_2_color = self.term.bold + self.term.blue

        seed_color = self.term.green
        index_color = self.term.yellow

        self.term.clear()
        self.term.move(Location(5, 5))

        self.board = Board(
            side_length,
            seed_color=seed_color,
            index_color=index_color,
            animation_wait=animation_wait,
        )

        self.player1 = player1
        self.player2 = player2

        self.board.assign_player(self.player1)
        self.board.assign_player(self.player2)

        self.current_player = self.player1
        self._players = (self.player1, self.player2)

        seeds = initial_seeds or self._get_initial_seeds()
        self.board.initialize_cups(seeds)
        self.board.clear_board()
        self.board.display_cups()

    def _get_initial_seeds(self):
        seeds = input('Enter the initial number of seeds per cup: ')
        return seeds

    def clear_screen(self):
        self.term.clear()

    def run(self):
        self.clear_screen()
        while not self.board.done():
            self.board.clear_board()
            self.board.display_cups()

            try:
                cup = self.current_player._take_turn()
                last_cup = self.board.sow(cup, color=self.current_player.color)
            except (EmptyCup, InvalidCup):
                continue

            self._determine_next_player(last_cup)
            self.clear_screen()
        self.board.display_cups()

        print()
        print()
        if self.board.player_1_cup > self.board.player_2_cup:
            print(f'{self.player1.name} Wins!')
            self.board.player1.game_over(Result.Win)
            self.board.player2.game_over(Result.Loss)
        elif self.board.player_1_cup < self.board.player_2_cup:
            print(f'{self.player2.name} Wins!')
            self.board.player1.game_over(Result.Loss)
            self.board.player2.game_over(Result.Win)
        else:
            print('Tie game!')
            self.board.player1.game_over(Result.Tie)
            self.board.player2.game_over(Result.Tie)

    def _determine_next_player(self, last_cup):
        if self.current_player == self.player1:
            if last_cup != self.board.player_1_cup_index:
                self.current_player = self.player2
        elif self.current_player == self.player2:
            if last_cup != self.board.player_2_cup_index:
                self.current_player = self.player1
Beispiel #14
0
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi(self)
        self.setAttribute(Qt.WA_QuitOnClose)

        geometry = Settings().retrieve_geometry("main")
        if geometry:
            self.restoreGeometry(geometry)

        self._connection_scanner = ConnectionScanner()
        self._connection = None
        self._root_dir = Settings().root_dir
        self._mcu_files_model = None
        self._terminal = Terminal()
        self._terminal_dialog = None
        self._code_editor = None
        self._flash_dialog = None
        self._settings_dialog = None
        self._preset_password = None

        self.actionNavigate.triggered.connect(self.navigate_directory)
        self.actionTerminal.triggered.connect(self.open_terminal)
        self.actionCode_Editor.triggered.connect(self.open_code_editor)
        self.actionUpload.triggered.connect(self.upload_transfer_scripts)
        self.actionFlash.triggered.connect(self.open_flash_dialog)
        self.actionSettings.triggered.connect(self.open_settings_dialog)

        self.connectionComboBox.currentIndexChanged.connect(self.connection_changed)
        self.refreshButton.clicked.connect(self.refresh_ports)

        # Populate baud speed combo box and select default
        self.baudComboBox.clear()
        for speed in BaudOptions.speeds:
            self.baudComboBox.addItem(str(speed))
        self.baudComboBox.setCurrentIndex(BaudOptions.speeds.index(115200))

        self.presetButton.clicked.connect(self.show_presets)
        self.connectButton.clicked.connect(self.connect_pressed)

        self.update_file_tree()

        self.listButton.clicked.connect(self.list_mcu_files)
        self.mcuFilesListView.clicked.connect(self.mcu_file_selection_changed)
        self.mcuFilesListView.doubleClicked.connect(self.read_mcu_file)
        self.executeButton.clicked.connect(self.execute_mcu_code)
        self.removeButton.clicked.connect(self.remove_file)
        self.localPathEdit.setText(self._root_dir)

        local_selection_model = self.localFilesTreeView.selectionModel()
        local_selection_model.selectionChanged.connect(self.local_file_selection_changed)
        self.localFilesTreeView.doubleClicked.connect(self.open_local_file)

        # Set the "Name" column to always fit the content
        self.localFilesTreeView.header().setSectionResizeMode(0, QHeaderView.ResizeToContents)

        self.compileButton.clicked.connect(self.compile_files)
        self.update_compile_button()
        self.autoTransferCheckBox.setChecked(Settings().auto_transfer)

        self.transferToMcuButton.clicked.connect(self.transfer_to_mcu)
        self.transferToPcButton.clicked.connect(self.transfer_to_pc)

        self.disconnected()
Beispiel #15
0
class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi(self)
        self.setAttribute(Qt.WA_QuitOnClose)

        geometry = Settings().retrieve_geometry("main")
        if geometry:
            self.restoreGeometry(geometry)

        self._connection_scanner = ConnectionScanner()
        self._connection = None
        self._root_dir = Settings().root_dir
        self._mcu_files_model = None
        self._terminal = Terminal()
        self._terminal_dialog = None
        self._code_editor = None
        self._flash_dialog = None
        self._settings_dialog = None
        self._preset_password = None

        self.actionNavigate.triggered.connect(self.navigate_directory)
        self.actionTerminal.triggered.connect(self.open_terminal)
        self.actionCode_Editor.triggered.connect(self.open_code_editor)
        self.actionUpload.triggered.connect(self.upload_transfer_scripts)
        self.actionFlash.triggered.connect(self.open_flash_dialog)
        self.actionSettings.triggered.connect(self.open_settings_dialog)

        self.connectionComboBox.currentIndexChanged.connect(self.connection_changed)
        self.refreshButton.clicked.connect(self.refresh_ports)

        # Populate baud speed combo box and select default
        self.baudComboBox.clear()
        for speed in BaudOptions.speeds:
            self.baudComboBox.addItem(str(speed))
        self.baudComboBox.setCurrentIndex(BaudOptions.speeds.index(115200))

        self.presetButton.clicked.connect(self.show_presets)
        self.connectButton.clicked.connect(self.connect_pressed)

        self.update_file_tree()

        self.listButton.clicked.connect(self.list_mcu_files)
        self.mcuFilesListView.clicked.connect(self.mcu_file_selection_changed)
        self.mcuFilesListView.doubleClicked.connect(self.read_mcu_file)
        self.executeButton.clicked.connect(self.execute_mcu_code)
        self.removeButton.clicked.connect(self.remove_file)
        self.localPathEdit.setText(self._root_dir)

        local_selection_model = self.localFilesTreeView.selectionModel()
        local_selection_model.selectionChanged.connect(self.local_file_selection_changed)
        self.localFilesTreeView.doubleClicked.connect(self.open_local_file)

        # Set the "Name" column to always fit the content
        self.localFilesTreeView.header().setSectionResizeMode(0, QHeaderView.ResizeToContents)

        self.compileButton.clicked.connect(self.compile_files)
        self.update_compile_button()
        self.autoTransferCheckBox.setChecked(Settings().auto_transfer)

        self.transferToMcuButton.clicked.connect(self.transfer_to_mcu)
        self.transferToPcButton.clicked.connect(self.transfer_to_pc)

        self.disconnected()

    def closeEvent(self, event):
        Settings().root_dir = self._root_dir
        Settings().auto_transfer = self.autoTransferCheckBox.isChecked()
        Settings().update_geometry("main", self.saveGeometry())
        Settings().save()
        if self._connection is not None and self._connection.is_connected():
            self.end_connection()
        if self._terminal_dialog:
            self._terminal_dialog.close()
        if self._code_editor:
            self._code_editor.close()
        event.accept()

    def connection_changed(self):
        connection = self._connection_scanner.port_list[self.connectionComboBox.currentIndex()]
        self.connectionStackedWidget.setCurrentIndex(1 if connection == "wifi" else 0)

    def refresh_ports(self):
        self._connection_scanner.scan_connections(with_wifi=True)
        # Populate port combo box and select default
        self.connectionComboBox.clear()

        if self._connection_scanner.port_list:
            for port in self._connection_scanner.port_list:
                self.connectionComboBox.addItem(port)
            prefPort = str(Settings().preferred_port)
            prefPort = prefPort.upper()
            prefPort = prefPort.join(prefPort.split())
            if self.connectionComboBox.findText(prefPort) >= 0:
                self.connectionComboBox.setCurrentIndex(self.connectionComboBox.findText(prefPort))
            else:
                self.connectionComboBox.setCurrentIndex(0)
            self.connectButton.setEnabled(True)
        else:
            self.connectButton.setEnabled(False)

    def set_status(self, status):
        if status == "Connected":
            self.statusLabel.setStyleSheet("QLabel { background-color : none; color : green; font : bold;}")
        elif status == "Disconnected":
            self.statusLabel.setStyleSheet("QLabel { background-color : none; color : red; }")
        elif status == "Connecting...":
            self.statusLabel.setStyleSheet("QLabel { background-color : none; color : blue; }")
        elif status == "Error":
            self.statusLabel.setStyleSheet("QLabel { background-color : red; color : white; }")
        elif status == "Password":
            self.statusLabel.setStyleSheet("QLabel { background-color : red; color : white; }")
            status = "Wrong Password"
        else:
            self.statusLabel.setStyleSheet("QLabel { background-color : red; color : white; }")
        self.statusLabel.setText(status)
        QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

    def update_compile_button(self):
        self.compileButton.setEnabled(bool(Settings().mpy_cross_path) and
                                      len(self.get_local_file_selection()) > 0)

    def disconnected(self):
        self.connectButton.setText("Connect")
        self.set_status("Disconnected")
        self.listButton.setEnabled(False)
        self.connectionComboBox.setEnabled(True)
        self.baudComboBox.setEnabled(True)
        self.refreshButton.setEnabled(True)
        self.mcuFilesListView.setEnabled(False)
        self.executeButton.setEnabled(False)
        self.removeButton.setEnabled(False)
        self.actionTerminal.setEnabled(False)
        self.actionUpload.setEnabled(False)
        self.transferToMcuButton.setEnabled(False)
        self.transferToPcButton.setEnabled(False)
        # Clear terminal on disconnect
        self._terminal.clear()
        if self._terminal_dialog:
            self._terminal_dialog.close()
        if self._code_editor:
            self._code_editor.disconnected()
        self.refresh_ports()

    def connected(self):
        self.connectButton.setText("Disconnect")
        self.set_status("Connected")
        self.listButton.setEnabled(True)
        self.connectionComboBox.setEnabled(False)
        self.baudComboBox.setEnabled(False)
        self.refreshButton.setEnabled(False)
        self.mcuFilesListView.setEnabled(True)
        self.actionTerminal.setEnabled(True)
        if isinstance(self._connection, SerialConnection):
            self.actionUpload.setEnabled(True)
        self.transferToMcuButton.setEnabled(True)
        if self._code_editor:
            self._code_editor.connected(self._connection)
        self.list_mcu_files()

    def navigate_directory(self):
        dialog = QFileDialog()
        dialog.setDirectory(self._root_dir)
        dialog.setFileMode(QFileDialog.Directory)
        dialog.setOption(QFileDialog.ShowDirsOnly)
        dialog.exec()
        path = dialog.selectedFiles()
        if path and path[0]:
            self._root_dir = path[0]
            self.localPathEdit.setText(self._root_dir)
            self.update_file_tree()

    def update_file_tree(self):
        model = QFileSystemModel()
        model.setRootPath(self._root_dir)
        self.localFilesTreeView.setModel(model)
        local_selection_model = self.localFilesTreeView.selectionModel()
        local_selection_model.selectionChanged.connect(self.local_file_selection_changed)
        self.localFilesTreeView.setRootIndex(model.index(self._root_dir))

    def serial_mcu_connection_valid(self):
        file_list = []
        try:
            file_list = self._connection.list_files()
            return True
        except OperationError:
            file_list = None
            return False

    def list_mcu_files(self):
        file_list = []
        try:
            file_list = self._connection.list_files()
        except OperationError:
            QMessageBox().critical(self, "Operation failed", "Could not list files.", QMessageBox.Ok)
            return

        self._mcu_files_model = QStringListModel()

        for file in file_list:
            idx = self._mcu_files_model.rowCount()
            self._mcu_files_model.insertRow(idx)
            self._mcu_files_model.setData(self._mcu_files_model.index(idx), file)

        self.mcuFilesListView.setModel(self._mcu_files_model)
        self.mcu_file_selection_changed()

    def execute_mcu_code(self):
        idx = self.mcuFilesListView.currentIndex()
        assert isinstance(idx, QModelIndex)
        model = self.mcuFilesListView.model()
        assert isinstance(model, QStringListModel)
        file_name = model.data(idx, Qt.EditRole)
        self._connection.run_file(file_name)

    def remove_file(self):
        idx = self.mcuFilesListView.currentIndex()
        assert isinstance(idx, QModelIndex)
        model = self.mcuFilesListView.model()
        assert isinstance(model, QStringListModel)
        file_name = model.data(idx, Qt.EditRole)
        try:
            self._connection.remove_file(file_name)
        except OperationError:
            QMessageBox().critical(self, "Operation failed", "Could not remove the file.", QMessageBox.Ok)
            return
        self.list_mcu_files()

    def ask_for_password(self, title, label="Password"):
        if self._preset_password is not None:
            return self._preset_password

        input_dlg = QInputDialog(parent=self, flags=Qt.Dialog)
        input_dlg.setTextEchoMode(QLineEdit.Password)
        input_dlg.setWindowTitle(title)
        input_dlg.setLabelText(label)
        input_dlg.resize(500, 100)
        input_dlg.exec()
        return input_dlg.textValue()

    def start_connection(self):
        self.set_status("Connecting...")

        connection = self._connection_scanner.port_list[self.connectionComboBox.currentIndex()]

        if connection == "wifi":
            ip_address = self.ipLineEdit.text()
            port = self.portSpinBox.value()
            if not IpHelper.is_valid_ipv4(ip_address):
                QMessageBox().warning(self, "Invalid IP", "The IP address has invalid format", QMessageBox.Ok)
                return

            try:
                self._connection = WifiConnection(ip_address, port, self._terminal, self.ask_for_password)
            except PasswordException:
                self.set_status("Password")
                return
            except NewPasswordException:
                QMessageBox().information(self, "Password set",
                                          "WebREPL password was not previously configured, so it was set to "
                                          "\"passw\" (without quotes). "
                                          "You can change it in port_config.py (will require reboot to take effect). "
                                          "Caution: Passwords longer than 9 characters will be truncated.\n\n"
                                          "Continue by connecting again.", QMessageBox.Ok)
                return
        else:
            baud_rate = BaudOptions.speeds[self.baudComboBox.currentIndex()]
            self._connection = SerialConnection(connection, baud_rate, self._terminal,
                                                self.serialResetCheckBox.isChecked())
            if self._connection.is_connected():
               if not self.serial_mcu_connection_valid():
                  self._connection.disconnect()
                  self._connection = None
            else:
               # serial connection didn't work, so likely the unplugged the serial device and COM value is stale
               self.refresh_ports()

        if self._connection is not None and self._connection.is_connected():
            self.connected()
            if isinstance(self._connection, SerialConnection):
                if Settings().use_transfer_scripts and not self._connection.check_transfer_scripts_version():
                    QMessageBox.warning(self,
                                        "Transfer scripts problem",
                                        "Transfer scripts for UART are either"
                                        " missing or have wrong version.\nPlease use 'File->Init transfer files' to"
                                        " fix this issue.")
        else:
            self._connection = None
            self.set_status("Error")
            self.refresh_ports()

    def end_connection(self):
        self._connection.disconnect()
        self._connection = None

        self.disconnected()

    def show_presets(self):
        dialog = WiFiPresetDialog()
        dialog.accepted.connect(lambda: self.use_preset(dialog.selected_ip,
                                                        dialog.selected_port,
                                                        dialog.selected_password))
        dialog.exec()

    def use_preset(self, ip, port, password):
        self.ipLineEdit.setText(ip)
        self.portSpinBox.setValue(port)
        self._preset_password = password

    def connect_pressed(self):
        if self._connection is not None and self._connection.is_connected():
            self.end_connection()
        else:
            self.start_connection()

    def run_file(self):
        content = self.codeEdit.toPlainText()
        self._connection.send_block(content)

    def open_local_file(self, idx):
        assert isinstance(idx, QModelIndex)
        model = self.localFilesTreeView.model()
        assert isinstance(model, QFileSystemModel)

        if model.isDir(idx):
            return

        local_path = model.filePath(idx)
        remote_path = local_path.rsplit("/", 1)[1]
        if local_path.endswith(".py"):
            if Settings().external_editor_path:
                self.open_external_editor(local_path)
            else:
                with open(local_path) as f:
                    text = "".join(f.readlines())
                    self.open_code_editor()
                    self._code_editor.set_code(local_path, remote_path, text)
        else:
            QMessageBox.information(self, "Unknown file", "Files without .py ending won't open"
                                                          " in editor, but can still be transferred.")

    def mcu_file_selection_changed(self):
        idx = self.mcuFilesListView.currentIndex()
        assert isinstance(idx, QModelIndex)
        if idx.row() >= 0:
            self.executeButton.setEnabled(True)
            self.removeButton.setEnabled(True)
            self.transferToPcButton.setEnabled(True)
        else:
            self.executeButton.setEnabled(False)
            self.removeButton.setEnabled(False)
            self.transferToPcButton.setEnabled(False)

    def get_local_file_selection(self):
        """Returns absolute paths for selected local files"""
        indices = self.localFilesTreeView.selectedIndexes()
        model = self.localFilesTreeView.model()
        assert isinstance(model, QFileSystemModel)

        def filter_indices(x):
            return x.column() == 0 and not model.isDir(x)

        # Filter out all but first column (file name) and
        # don't include directories
        indices = [x for x in indices if filter_indices(x)]

        # Return absolute paths
        return [model.filePath(idx) for idx in indices]

    def local_file_selection_changed(self):
        self.update_compile_button()
        local_file_paths = self.get_local_file_selection()
        if len(local_file_paths) == 1:
            self.remoteNameEdit.setText(local_file_paths[0].rsplit("/", 1)[1])
        else:
            self.remoteNameEdit.setText("")

    def compile_files(self):
        local_file_paths = self.get_local_file_selection()
        compiled_file_paths = []

        for local_path in local_file_paths:
            split = os.path.splitext(local_path)
            if split[1] == ".mpy":
                title = "COMPILE WARNING!! " + os.path.basename(local_path)
                QMessageBox.warning(self, title, "Can't compile .mpy files, already bytecode")
                continue
            mpy_path = split[0]+".mpy"

            try:
                os.remove(mpy_path)
                # Force view to update itself so that it sees file removed
                self.localFilesTreeView.repaint()
                QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
            except OSError:
                pass

            try:
                with subprocess.Popen([Settings().mpy_cross_path, os.path.basename(local_path)],
                                      cwd=os.path.dirname(local_path),
                                      stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
                    proc.wait()  # Wait for process to finish
                    out = proc.stderr.read()
                    if out:
                        QMessageBox.warning(self, "Compilation error", out.decode("utf-8"))
                        continue

            except OSError:
                QMessageBox.warning(self, "Compilation error", "Failed to run mpy-cross")
                continue

            compiled_file_paths += [mpy_path]

        # Force view to update so that it sees compiled files added
        self.localFilesTreeView.repaint()
        QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

        # Force view to update last time so that it resorts the content
        # Without this, the new file will be placed at the bottom of the view no matter what
        header = self.localFilesTreeView.header()
        column = header.sortIndicatorSection()
        order = header.sortIndicatorOrder()
        # This is necessary so that view actually sorts anything again
        self.localFilesTreeView.sortByColumn(-1, Qt.AscendingOrder)
        QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
        self.localFilesTreeView.sortByColumn(column, order)

        selection_model = self.localFilesTreeView.selectionModel()
        if compiled_file_paths:
            assert isinstance(selection_model, QItemSelectionModel)
            selection_model.clearSelection()

        for mpy_path in compiled_file_paths:
            idx = self.localFilesTreeView.model().index(mpy_path)
            selection_model.select(idx, QItemSelectionModel.Select | QItemSelectionModel.Rows)

        if (self.autoTransferCheckBox.isChecked() and self._connection and self._connection.is_connected()
            and compiled_file_paths):
            self.transfer_to_mcu()

    def finished_read_mcu_file(self, file_name, transfer):
        assert isinstance(transfer, FileTransfer)
        result = transfer.read_result
        text = result.binary_data.decode("utf-8",
                                         errors="replace") if result.binary_data is not None else "!Failed to read file!"
        self.open_code_editor()
        self._code_editor.set_code(None, file_name, text)

    def read_mcu_file(self, idx):
        assert isinstance(idx, QModelIndex)
        model = self.mcuFilesListView.model()
        assert isinstance(model, QStringListModel)
        file_name = model.data(idx, Qt.EditRole)
        if not file_name.endswith(".py"):
            QMessageBox.information(self, "Unknown file", "Files without .py ending won't open"
                                                          " in editor, but can still be transferred.")
            return

        progress_dlg = FileTransferDialog(FileTransferDialog.DOWNLOAD)
        progress_dlg.finished.connect(lambda: self.finished_read_mcu_file(file_name, progress_dlg.transfer))
        progress_dlg.show()
        self._connection.read_file(file_name, progress_dlg.transfer)

    def upload_transfer_scripts(self):
        progress_dlg = FileTransferDialog(FileTransferDialog.UPLOAD)
        progress_dlg.finished.connect(self.list_mcu_files)
        progress_dlg.show()
        self._connection.upload_transfer_files(progress_dlg.transfer)

    def transfer_to_mcu(self):
        local_file_paths = self.get_local_file_selection()

        progress_dlg = FileTransferDialog(FileTransferDialog.UPLOAD)
        progress_dlg.finished.connect(self.list_mcu_files)
        progress_dlg.show()

        # Handle single file transfer
        if len(local_file_paths) == 1:
            local_path = local_file_paths[0]
            remote_path = self.remoteNameEdit.text()
            with open(local_path, "rb") as f:
                content = f.read()
            self._connection.write_file(remote_path, content, progress_dlg.transfer)
            return

        # Batch file transfer
        progress_dlg.enable_cancel()
        progress_dlg.transfer.set_file_count(len(local_file_paths))
        self._connection.write_files(local_file_paths, progress_dlg.transfer)

    def finished_transfer_to_pc(self, file_path, transfer):
        if not transfer.read_result.binary_data:
            return

        try:
            with open(file_path, "wb") as file:
                file.write(transfer.read_result.binary_data)
        except IOError:
            QMessageBox.critical(self, "Save operation failed", "Couldn't save the file. Check path and permissions.")

    def transfer_to_pc(self):
        idx = self.mcuFilesListView.currentIndex()
        assert isinstance(idx, QModelIndex)
        model = self.mcuFilesListView.model()
        assert isinstance(model, QStringListModel)
        remote_path = model.data(idx, Qt.EditRole)
        local_path = self.localPathEdit.text() + "/" + remote_path

        progress_dlg = FileTransferDialog(FileTransferDialog.DOWNLOAD)
        progress_dlg.finished.connect(lambda: self.finished_transfer_to_pc(local_path, progress_dlg.transfer))
        progress_dlg.show()
        self._connection.read_file(remote_path, progress_dlg.transfer)

    def open_terminal(self):
        if self._terminal_dialog is not None:
            return
        self._terminal_dialog = TerminalDialog(self, self._connection, self._terminal)
        self._terminal_dialog.finished.connect(self.close_terminal)
        self._terminal_dialog.show()

    def close_terminal(self):
        self._terminal_dialog = None

    def open_external_editor(self, file_path):
        ext_path = Settings().external_editor_path
        ext_args = []
        if Settings().external_editor_args:
            def wildcard_replace(s):
                s = s.replace("%f", file_path)
                return s

            ext_args = [wildcard_replace(x.strip()) for x in Settings().external_editor_args.split(";")]

        subprocess.Popen([ext_path] + ext_args)

    def open_code_editor(self):
        if self._code_editor is not None:
            return

        self._code_editor = CodeEditDialog(self, self._connection)
        self._code_editor.mcu_file_saved.connect(self.list_mcu_files)
        self._code_editor.finished.connect(self.close_code_editor)
        self._code_editor.show()

    def close_code_editor(self):
        self._code_editor = None

    def open_flash_dialog(self):
        if self._connection is not None and self._connection.is_connected():
            self.end_connection()

        self._flash_dialog = FlashDialog(self)
        self._flash_dialog.finished.connect(self.close_flash_dialog)
        self._flash_dialog.show()

    def close_flash_dialog(self):
        self._flash_dialog = None

    def open_settings_dialog(self):
        if self._settings_dialog is not None:
            return
        self._settings_dialog = SettingsDialog(self)
        self._settings_dialog.finished.connect(self.close_settings_dialog)
        self._settings_dialog.show()

    def close_settings_dialog(self):
        self._settings_dialog = None
        # Update compile button as mpy-cross path might have been set
        self.update_compile_button()
Beispiel #16
0
from src.terminal import Terminal

if sys.version_info[0] < 3:
    raise Exception("Python 3 or a more recent version is required.")

user_request_exit = False

ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
OUTPUT_DIR = join_path(ROOT_DIR, 'output')
OUTPUT_REQUESTS_DIR = join_path(OUTPUT_DIR, 'requests')
OUTPUT_CSVS_DIR = join_path(OUTPUT_DIR, 'csv')
OUTPUT_CSVS_FRIENDS_DIR = join_path(OUTPUT_CSVS_DIR, 'known_people')
OUTPUT_PHOTOS_DIR = join_path(ROOT_DIR, 'output/photos')
OUTPUT_FRIENDS = join_path(OUTPUT_CSVS_DIR, 'known_people')
COOKIES_DIR = join_path(ROOT_DIR, 'cookie')
CONFIGURATION_DIR = join_path(ROOT_DIR, 'configuration')


def define_directories():
    # directories
    DIRS = [
        OUTPUT_DIR, COOKIES_DIR, CONFIGURATION_DIR, OUTPUT_REQUESTS_DIR,
        OUTPUT_CSVS_DIR, OUTPUT_CSVS_FRIENDS_DIR, OUTPUT_PHOTOS_DIR
    ]
    for dir_path in DIRS:
        write_directory(dir_path)


terminal = Terminal()
terminal.logo('Open Source Social Information')
Beispiel #17
0
class Board:
    def __init__(
        self,
        side_length,
        animation_wait=1,
        seed_color=None,
        index_color=None,
        player1=None,
        player2=None,
    ):
        """
        Indexes and their associated letters:

            a   b   c   d   e   f   g
            1   2   3   4   5   6   7
        0                               8
            15  14  13  12  11  10  9
            h   i   j   k   l   m   n
        """
        self.term = Terminal()
        self.side_length = side_length
        self.total_number_of_cups = self.side_length * 2 + 2
        self.cups = [0] * self.total_number_of_cups

        self._SEED_COLOR = seed_color
        self._INDEX_COLOR = index_color

        self._ANIMATION_WAIT = animation_wait

        self._INITIAL_LOCATION = Location(5, 10)
        self._HORIZONTAL_SPACER = Location(0, 4)

        self._TOP_ROW_INDICATOR_LOCATION = (self._INITIAL_LOCATION +
                                            self._HORIZONTAL_SPACER)
        self._TOP_ROW_INDICES_LOCATION = self._TOP_ROW_INDICATOR_LOCATION + Location(
            1, 0)
        self._TOP_ROW_LOCATION = self._TOP_ROW_INDICES_LOCATION + Location(
            1, 0)

        self._PLAYER_1_LOCATION = Location(self._TOP_ROW_LOCATION.row + 1,
                                           self._INITIAL_LOCATION.column)
        self._PLAYER_2_LOCATION = self._PLAYER_1_LOCATION + Location(
            0, self._HORIZONTAL_SPACER.column * (self.side_length + 1))

        self._BOTTOM_ROW_LOCATION = (self._PLAYER_1_LOCATION +
                                     self._HORIZONTAL_SPACER + Location(1, 0))
        self._BOTTOM_ROW_INDICES_LOCATION = self._BOTTOM_ROW_LOCATION + Location(
            1, 0)
        self._BOTTOM_ROW_INDICATOR_LOCATION = (
            self._BOTTOM_ROW_INDICES_LOCATION + Location(1, 0))

        self._PLAYER_1_INDICATOR = self._PLAYER_1_LOCATION - self._HORIZONTAL_SPACER
        self._PLAYER_2_INDICATOR = self._PLAYER_2_LOCATION + self._HORIZONTAL_SPACER

        self._build_index_dicts()

        self.players = []

        if player1:
            self.assign_player(player1)

        if player2:
            self.assign_player(player2)

    @property
    def ready_to_play(self):
        return len(self.players) == 2

    def assign_player(self, player):
        if len(self.players) == 2:
            raise TooManyPlayers('Cannot add any more players to this board')

        if not self.players:
            self.player1 = player
            self.player1.assign_board(self)
        elif len(self.players) == 1:
            self.player2 = player
            self.player2.assign_board(self)
        self.players.append(player)

    @property
    def max_row(self):
        return self._BOTTOM_ROW_INDICATOR_LOCATION.row

    @property
    def max_column(self):
        return self._PLAYER_2_INDICATOR.column

    @property
    def min_row(self):
        return self._INITIAL_LOCATION.row

    @property
    def min_column(self):
        return self._INITIAL_LOCATION.column

    @property
    def top_row(self):
        return self.cups[1:self._midpoint]

    @property
    def top_row_indices(self):
        return [i for i in range(1, self._midpoint)]

    @property
    def top_row_cups(self):
        return sorted([self.index_to_cup[idx] for idx in self.top_row_indices])

    @property
    def bottom_row(self):
        return self.cups[self._midpoint + 1:]

    @property
    def bottom_row_indices(self):
        return [i for i in range(self._midpoint + 1, len(self.cups))]

    @property
    def bottom_row_cups(self):
        return sorted(
            [self.index_to_cup[idx] for idx in self.bottom_row_indices])

    @property
    def player_1_cup_index(self):
        return 0

    @property
    def player_2_cup_index(self):
        return self._midpoint

    @property
    def player_1_cup(self):
        return self.cups[self.player_1_cup_index]

    @property
    def player_2_cup(self):
        return self.cups[self.player_2_cup_index]

    @property
    def _midpoint(self):
        return len(self.cups) // 2

    def cup_seeds(self, cup):
        return self.cups[self.cup_to_index[cup]]

    def cup_seeds_by_index(self, index):
        return self.cups[index]

    @property
    def cup_to_index(self):
        return self._cup_to_index

    @property
    def index_to_cup(self):
        return self._index_to_cup

    def _build_index_dicts(self):
        self._cup_to_index = dict()
        self._index_to_cup_indicator = dict()

        letter_sequence = generate_sequence(len(self.cups) - 2)

        for i in range(1, self._midpoint):
            letter = letter_sequence.pop(0)
            self._cup_to_index[letter] = i

        for i in range(len(self.cups) - 1, self._midpoint, -1):
            letter = letter_sequence.pop(0)
            self._cup_to_index[letter] = i

        self._index_to_cup = {v: k for k, v in self._cup_to_index.items()}

        for i in range(len(self.cups)):
            if i == 0:  # Player1's cup
                self._index_to_cup_indicator[i] = Indicator(
                    self._PLAYER_1_INDICATOR, '>')
            elif i < self._midpoint:  # Top row
                self._index_to_cup_indicator[i] = Indicator(
                    (i - 1) * self._HORIZONTAL_SPACER +
                    self._TOP_ROW_INDICATOR_LOCATION,
                    'v',
                )
            elif i == self._midpoint:  # Player2's cup
                self._index_to_cup_indicator[i] = Indicator(
                    self._PLAYER_2_INDICATOR, '<')
            else:  # Bottom row cups
                self._index_to_cup_indicator[i] = Indicator(
                    (len(self.cups) - i - 1) * self._HORIZONTAL_SPACER +
                    self._BOTTOM_ROW_INDICATOR_LOCATION,
                    '^',
                )

    def clear_indicators(self):
        for idx, indicator in self._index_to_cup_indicator.items():
            self.term.move(indicator.location)
            self.term.display(' ')

    def done(self):
        return all(val == 0
                   for val in itertools.chain(self.top_row, self.bottom_row))

    def initialize_cups(self, seeds):
        for i in range(len(self.cups)):
            if i == 0 or i == self._midpoint:
                continue

            self.cups[i] = int(seeds)

    def sow(self, cup, color=None):
        if cup not in self.cup_to_index:
            raise InvalidCup(f'Invalid cup. Got {cup}.')

        self.clear_indicators()
        index = self.cup_to_index[cup]
        seeds = self.cups[index]

        if seeds == 0:
            raise EmptyCup(f"Cup '{cup}' is empty.")

        self._draw_indicator(index, color=color)
        print()
        self.cups[index] = 0
        self.display_cups()
        time.sleep(self._ANIMATION_WAIT)

        while seeds:
            index += 1
            self.cups[index % len(self.cups)] += 1
            seeds -= 1
            self.clear_board()
            self.display_cups()
            self._draw_indicator(index % len(self.cups), color=color)

            print()
            time.sleep(self._ANIMATION_WAIT)

        return index % len(self.cups)

    def _draw_indicator(self, index, color=None):
        indicator = self._index_to_cup_indicator[index]
        self.term.move(indicator.location)
        self.term.display(indicator.symbol, color=color)

    def clear_board(self):
        for row in range(self.min_row, self.max_row):
            # Iterate to max_columns + 1 in case player2's cup has 10 or more seeds
            for column in range(self.min_column, self.max_column + 1):
                self.term.move(row, column)
                self.term.display(' ')

        self.clear_indicators()

    def display_cups(self):
        self.term.move(self._PLAYER_1_LOCATION)
        self.term.display(self.player_1_cup, color=self.player1.color)

        self.term.move(self._PLAYER_2_LOCATION)
        self.term.display(self.player_2_cup, color=self.player2.color)

        # Draw the top row
        # Draw cup indices
        current_location = self._TOP_ROW_INDICES_LOCATION

        for key in self.top_row_cups:
            self.term.move(current_location)
            self.term.display(key, color=self._INDEX_COLOR)
            current_location += self._HORIZONTAL_SPACER

        current_location = self._TOP_ROW_LOCATION

        for val in self.top_row:
            self.term.move(current_location)
            self.term.display(val, color=self._SEED_COLOR)
            current_location += self._HORIZONTAL_SPACER

        # Draw player cups
        # Draw bottom row
        current_location = self._BOTTOM_ROW_LOCATION

        for val in reversed(self.bottom_row):
            self.term.move(current_location)
            self.term.display(val, color=self._SEED_COLOR)
            current_location += self._HORIZONTAL_SPACER

        current_location = self._BOTTOM_ROW_INDICES_LOCATION

        for key in self.bottom_row_cups:
            self.term.move(current_location)
            self.term.display(key, color=self._INDEX_COLOR)
            current_location += self._HORIZONTAL_SPACER
Beispiel #18
0
    def __init__(
        self,
        side_length,
        animation_wait=1,
        seed_color=None,
        index_color=None,
        player1=None,
        player2=None,
    ):
        """
        Indexes and their associated letters:

            a   b   c   d   e   f   g
            1   2   3   4   5   6   7
        0                               8
            15  14  13  12  11  10  9
            h   i   j   k   l   m   n
        """
        self.term = Terminal()
        self.side_length = side_length
        self.total_number_of_cups = self.side_length * 2 + 2
        self.cups = [0] * self.total_number_of_cups

        self._SEED_COLOR = seed_color
        self._INDEX_COLOR = index_color

        self._ANIMATION_WAIT = animation_wait

        self._INITIAL_LOCATION = Location(5, 10)
        self._HORIZONTAL_SPACER = Location(0, 4)

        self._TOP_ROW_INDICATOR_LOCATION = (self._INITIAL_LOCATION +
                                            self._HORIZONTAL_SPACER)
        self._TOP_ROW_INDICES_LOCATION = self._TOP_ROW_INDICATOR_LOCATION + Location(
            1, 0)
        self._TOP_ROW_LOCATION = self._TOP_ROW_INDICES_LOCATION + Location(
            1, 0)

        self._PLAYER_1_LOCATION = Location(self._TOP_ROW_LOCATION.row + 1,
                                           self._INITIAL_LOCATION.column)
        self._PLAYER_2_LOCATION = self._PLAYER_1_LOCATION + Location(
            0, self._HORIZONTAL_SPACER.column * (self.side_length + 1))

        self._BOTTOM_ROW_LOCATION = (self._PLAYER_1_LOCATION +
                                     self._HORIZONTAL_SPACER + Location(1, 0))
        self._BOTTOM_ROW_INDICES_LOCATION = self._BOTTOM_ROW_LOCATION + Location(
            1, 0)
        self._BOTTOM_ROW_INDICATOR_LOCATION = (
            self._BOTTOM_ROW_INDICES_LOCATION + Location(1, 0))

        self._PLAYER_1_INDICATOR = self._PLAYER_1_LOCATION - self._HORIZONTAL_SPACER
        self._PLAYER_2_INDICATOR = self._PLAYER_2_LOCATION + self._HORIZONTAL_SPACER

        self._build_index_dicts()

        self.players = []

        if player1:
            self.assign_player(player1)

        if player2:
            self.assign_player(player2)
Beispiel #19
0
def find_knowns_of_my_knowns(terminal: Terminal):
    terminal.info('Getting knowns of all')
    knowns = facebook_api.get_knowns()
    for known in knowns:
        find_knowns_of(terminal, {'id': known['id']})