CSC108-Fall-2022-A1 / mm_functions.py
mm_functions.py
Raw
"""CSC108/CSCA08: Fall 2022 -- Assignment 1: Mystery Message Game

This code is provided solely for the personal and private use of
students taking the CSC108/CSCA08 course at the University of
Toronto. Copying for purposes other than this use is expressly
prohibited. All forms of distribution of this code, whether as given
or with any changes, are expressly prohibited.

All of the files in this directory and all subdirectories are:
Copyright (c) 2022 Mario Badr, Jennifer Campbell, Tom Fairgrieve, Diane
Horton, Michael Liut, Jacqueline Smith, Anya Tafliovich and Michelle Craig.
"""

from constants import (CONSONANT_POINTS, VOWEL_COST, CONSONANT_BONUS,
                       PLAYER_ONE, PLAYER_TWO, CONSONANT, VOWEL,
                       SOLVE, QUIT, HUMAN, HUMAN_HUMAN,
                       HUMAN_COMPUTER, EASY, HARD, ALL_CONSONANTS,
                       ALL_VOWELS, PRIORITY_CONSONANTS, HIDDEN)


# We provide this function as an example.
def is_win(view: str, message: str) -> bool:
    """Return True if and only if message and view are a winning
    combination. That is, if and only if message and view are the same.
    >>> is_win('banana', 'banana')
    True
    >>> is_win('a^^le', 'apple')
    False
    >>> is_win('app', 'apple')
    False
    """
    return message == view


# We provide this function as an example of using a function as a helper.
def is_game_over(view: str, message: str, move: str) -> bool:
    """Return True if and only if message and view are a winning
    combination or move is QUIT.
    >>> is_game_over('a^^le', 'apple', 'V')
    False
    >>> is_game_over('a^^le', 'apple', 'Q')
    True
    >>> is_game_over('apple', 'apple', 'S')
    True
    """
    return move == QUIT or is_win(view, message)


# We provide the header and docstring of this function as an example
# of where and how to use constants in the docstring.
def is_human(current_player: str, game_type: str) -> bool:
    """Return True if and only if current_player represents a human in a
    game of type game_type.
    current_player is PLAYER_ONE or PLAYER_TWO.
    game_type is HUMAN, HUMAN_HUMAN, or HUMAN_COMPUTER.
    In a HUMAN game or a HUMAN_HUMAN game, a player is always
    human. In a HUMAN_COMPUTER game, PLAYER_ONE is human and
    PLAYER_TWO is computer.
    >>> is_human('Player One', 'P1')
    True
    >>> is_human('Player One', 'PVP')
    True
    >>> is_human('Player Two', 'PVP')
    True
    >>> is_human('Player One', 'PVE')
    True
    >>> is_human('Player Two', 'PVE')
    False
    """
    not_human = current_player == PLAYER_TWO and game_type == HUMAN_COMPUTER
    return not not_human


def half_revealed(view: str) -> bool:
    """Return True if and only if at least half of the alphabetic
    characters in view are revealed.
    >>> half_revealed('')
    True
    >>> half_revealed('x')
    True
    >>> half_revealed('^')
    False
    >>> half_revealed('a^,^c!')
    True
    >>> half_revealed('a^b^^e ^c^d^^d')
    False
    """
    num_hidden = view.count(HIDDEN)
    num_alphabetic = 0
    for char in view:
        if char.isalpha():
            num_alphabetic += 1
    return num_alphabetic >= num_hidden


def is_one_player_game(game_type: str) -> bool:
    """Return True if and only if game_type represents a one-player game.
    game_type is HUMAN, HUMAN_HUMAN, or HUMAN_COMPUTER.
    In a HUMAN game, there is only one player.
    >>> is_one_player_game(HUMAN)
    True
    >>> is_one_player_game(HUMAN_COMPUTER)
    False
    >>> is_one_player_game(HUMAN_HUMAN)
    False
    """
    return game_type == HUMAN


def current_player_score(player_one_score: int, player_two_score: int,
                         current_player: str) -> int:
    """Return score of current_player, current_player is PLAYER_ONE or\
    PLAYER_TWO
    >>> current_player_score( 5, 6, PLAYER_ONE)
    5
    >>> current_player_score( 5, 6, PLAYER_TWO)
    6
    """
    if current_player == PLAYER_ONE:
        return player_one_score
    else:
        return player_two_score


def is_bonus_letter(view: str, letter: str, message: str) -> bool:
    """Return True if and only if letter is a bonus letter, that is, it is a \
    hidden consonant in view.
    ALL_CONSONANTS is a string of all consonants in the alphabet.
    >>> is_bonus_letter('a^^le', 'p', 'apple')
    True
    >>> is_bonus_letter('b^n^n^', 'a', 'banana')
    False
    >>> is_bonus_letter('bi^^er', 'b', 'bitter')
    False
    """
    is_hidden = letter in message and letter not in view
    is_consonant = letter in ALL_CONSONANTS
    return is_hidden and is_consonant


def get_updated_char_view(view: str, message: str, character_index: int,
                          guess: str) -> str:
    """Return a single character string of an updated view of a character at \
    character_index of message.
    If the guess is correct, updated view is the revealed character, otherwise,\
    return the unchanged character from the view before the function was called.
    >>> get_updated_char_view('a^^le', 'apple', 1, 'p')
    'p'
    >>> get_updated_char_view('b^tter', 'bitter', 1, 'u')
    '^'
    """
    if guess == message[character_index]:
        updated_view = message[character_index]
        return updated_view
    else:
        updated_view = view[character_index]
        return updated_view


def calculate_score(player_score: int, num_occurrences: int, move: str) -> int:
    """Return an updated score player_score calculated according to number of \
    occurrences num_occurrences of moves, which are either CONSONANT or VOWEL.
    >>> calculate_score(10, 2, VOWEL)
    9
    >>> calculate_score(8, 4, CONSONANT)
    12
    """
    if move == CONSONANT:
        player_score = player_score + num_occurrences * CONSONANT_POINTS
        return player_score
    else:
        player_score = player_score - VOWEL_COST
        return player_score\



def next_player(current_player: str,
                num_occurrences: int, game_type: str) -> str:
    """Return the player current_player who will play in the next turn,\
    current_player can be either PLAYER_ONE or PLAYER_TWO.
    >>> next_player(PLAYER_ONE, 3, HUMAN_HUMAN)
    'Player One'
    >>> next_player(PLAYER_TWO, 0, HUMAN_COMPUTER)
    'Player One'
    >>> next_player(PLAYER_ONE, 0, HUMAN)
    'Player One'
    >>> next_player(PLAYER_ONE, 0, HUMAN_HUMAN)
    'Player Two'
    >>> next_player(PLAYER_ONE, 0, HUMAN_COMPUTER)
    'Player Two'
    """
    if game_type == HUMAN:
        return current_player
    elif game_type != HUMAN and num_occurrences >= 1:
        return current_player
    else:
        if current_player == PLAYER_ONE:
            return PLAYER_TWO
        else:
            return PLAYER_ONE


def is_fully_hidden(view: str, character_index: int, message: str) -> bool:
    """Return True if and only if the character at character_index of message\
    is not revealed anywhere in view.
    >>> is_fully_hidden('a^e^', 1 , 'axed')
    True
    >>> is_fully_hidden('^ana^a', 4 , 'banana')
    False
    """
    result = message[character_index] in view
    return not result


def computer_chooses_solve(view: str, difficulty: str,
                           consonants_left: str) -> bool:
    """Return True if and only if computer decides to solve the mystery. This\
    happens when:
    a) Difficulty is HARD and at least half of the letters have been revealed or
    \there are no more consonants to guess
    b) Difficulty is EASY and there are no more consonants to choose from
    >>> computer_chooses_solve("l^t's g^t it st^rt^d", HARD, '')
    True
    >>> computer_chooses_solve('c^n c^n', HARD, 'bdgh')
    False
    >>> computer_chooses_solve('^anana', EASY, '')
    True
    >>> computer_chooses_solve('a^b^^e ^c^d^^d', HARD, 'fghjk')
    False
    >>> computer_chooses_solve('app^e', EASY, 'bcdgh')
    False
    """
    view_sorted = sorted(view)
    consonants_left_sorted = sorted(consonants_left)
    comparable_string_1 = ''.join(list(dict.fromkeys(
        view_sorted + consonants_left_sorted)))
    comparable_string_2 = ''.join(filter(str.isalnum, comparable_string_1))
    no_more_consonants_guessable = comparable_string_2 in ALL_CONSONANTS

    if difficulty == HARD:
        return half_revealed(view) or no_more_consonants_guessable
    elif difficulty == EASY:
        return len(consonants_left) == 0
    else:
        return False


def erase(string_of_letters: str, character_index: int) -> str:
    """Return new string of letters, where letter at character_index in \
    string_of_letters is removed, if character_index is between 0 and last \
    character in string_of_letters. Otherwise, return string_of_letters\
    unchanged.
    >>> erase('abcdefg', 2)
    'abdefg'
    >>> erase('banana', 0)
    'banana'
    >>> erase('apple', 4)
    "apple"
    """
    if 0 < character_index < len(string_of_letters) - 1:
        new_string = string_of_letters[0: character_index] + \
            string_of_letters[character_index + 1: len(string_of_letters)]
        return new_string
    else:
        return string_of_letters