#14-1###################################################
"""Write a class called Investment with fields called
principal and interest. The constructor should set the
values of those fields. There should be a method called
value_after that returns the value of the investment
after n years. The formula for this is p(1 + i)n, where
p is the principal, and i is the interest rate. It should
also use the special method __str__ so that printing the
object will result in something like below:
Principal - $1000.00, Interest rate - 5.12%"""


##class Investment:
##      def __init__(self,principal,interest,n):
##          self.principal = principal
##          self.interest = interest
##          self.n = n
##      def value_after(self):
##          return_investment = self.principal*(1+self.interest)**self.n
##          return return_investment
##      def __str__(self):
##          return ("Principal --$", self.principal,"Interest rate---", self.interest,"%","Interst $",a.value_after())
##a = Investment(1000,5.12,3)
##print((a.__str__()))


#14-2####################################################
"""Write a class called Product. The class should have
fields called name, amount, and price, holding the
product’s name, the number of items of that product in
stock, and the regular price of the product. There should
be a method get_price that receives the number of items
to be bought and returns a the cost of buying that many
items, where the regular price is charged for orders of
less than 10 items, a 10% discount is applied for orders
of between 10 and 99 items, and a 20% discount is applied
for orders of 100 or more items. There should also be a
method called make_purchase that receives the number of
items to be bought and decreases amount by that much.

"""

##class Product:
##    def __init__(self,name,amount ,price):
##        self.name =name
##        self.amount =amount
##        self.price = price
##    def get_price(self):
##        return self.price * self.amount
##    def make_purchase(self):
##        if self.amount < 10:
##            return "Discount Price", self.amount * self.price
##        elif self.amount in range(9,100):
##            return "Discount Price",self.amount * self.price-(self.price * 0.01)
##        elif self.amount >= 100:
##            return "Discount Price",self.amount * self.price - (self.price * 0.02)
##shop =Product("Henry",15,99)
##print(shop.make_purchase())


#14-3######################################################
""""Write a class called Password_manager. The class should
have a list called old_passwords that holds all of the user’s
past passwords. The last item of the list is the user’s
current password. There should be a method called get_password
that returns the current password and a method called
set_password that sets the user’s password. The set_password
method should only change the password if the attempted
password is different from all the user’s past passwords.
Finally, create a method called is_correct that receives a
string and returns a boolean True or False depending on
whether the string is equal to the current password or not."""

##class Password_manager:
##    def __init__(self, old_passwords):
##        self.old_passwords = paswords
##    def get_password(self):
##        return paswords[-1]
##    def set_password(self):
##        new_pass =(input("Enter Your New Pass: " ))
##        if new_pass in paswords:
##            return "Password found in list,Choose new pass"
##        else:
##            paswords.append(new_pass)
##            return "New Password is Set",paswords[-1]
##    def is_correct(self,string):
##        if string == self.old_passwords[-1]:
##            return True
##        else:
##            return False
##paswords= ["Henry","Gaga","Nelson"]
##a  = Password_manager(paswords)
###print(a.get_password())
###print(a.set_password())
##print(a.is_correct("Nelson"))


#14-4######################################################
""""Write a class called Time whose only field is a time in
seconds. It should have a method called convert_to_minutes
that returns a string of minutes and seconds formatted as
in the following example: if seconds is 230, the method
should return '5:50'. It should also have a method called
convert_to_hours that returns a string of hours, minutes,
and seconds formatted analogously to the previous method."""


###NoteThe question has a slight mistake the Input of 230s should give an output of 3:50s and not 5:50s
##class Time:
##    def __init__(self,seconds):
##        self.seconds = seconds
##    def convert_to_minutes(self):
##        if self.seconds < 60:
##           return self.seconds
##        else:
##           minute = int(self.seconds/60)
##           sec = self.seconds% 60
##           return "{} minutes :{} seconds".format(minute,sec)
##    def convert_to_hours(self):
##        if self.seconds < 60:
##            hour =0
##            minute = 0
##            sec = self.seconds
##            return "{} Hour :{} minutes :{} seconds".format(hour,minute,sec)
##        elif self.seconds > 60 and self.seconds< 3600:
##            hour = 0
##            minute = int(self.seconds/60)
##            sec = self.seconds % 60
##            return "{} Hour :{} minutes :{} seconds".format(hour, minute, sec)
##        elif self.seconds >= 3600:
##            hour = int(self.seconds/3600)
##            if self.seconds - 3600 < 60:
##               minute =0
##            else:
##                minute = int((self.seconds - 3600) / 60)
##            sec = int(self.seconds-3600)%60
##            return "{} Hour :{} minutes :{} seconds".format(hour, minute, sec)
##a =Time(5000)
##print(a.convert_to_minutes())
##print(a.convert_to_hours())


#14-5######################################################
""""Write a class called Wordplay. It should have a field
that holds a list of words. The user of the class should
pass the list of words they want to use to the class. There
should be the following methods:
• words_with_length(length)— returns a list of all the words
of length length
• starts_with(s)— returns a list of all the words that start
with s
• ends_with(s)— returns a list of all the words that end
with s
• palindromes()— returns a list of all the palindromes in the
list
• only(L)— returns a list of the words that contain only those
letters in L
• avoids(L)— returns a list of the words that contain none of
the letters in L"""


##class Wordplay:
##    def __init__(self):
##        self.words_list = []
##    def words_with_length(self,length):
##        wordlist = []
##        for w in self.words_list:
##            if len(w) == length:
##                wordlist.append(w)
##        return  "Words with length {} {}".format(length,wordlist)
##    def start_with(self, s):
##        wordlist = []
##        for w in self.words_list:
##            if (w[0] == s):
##                wordlist.append(w)
##        return "Words Start with letter {} {}".format(s,wordlist)
##    def ends_with(self,s):
##        wordlist =[]
##        for w in self.words_list:
##            if w[-1] == s:
##                wordlist.append(w)
##        return "Words Ending with letter {} {}".format(s,wordlist)
##    def palindrome(self):
##        wordlist =[]
##        for w in self.words_list:
##            if w[::1] == w[::-1]:
##                wordlist.append(w)
##        return "Palindromes {}".format(wordlist)
##    def only_L(self):
##        wordlist =[]
##        for w in self.words_list:
##            if "l" in w:
##                wordlist.append(w)
##        return "Words containing L {}".format(wordlist)
##    def Not_L(self):
##        wordlist = []
##        for w in self.words_list:
##            if "l" not in w:
##                wordlist.append(w)
##        return "Words Not containing L {}".format(wordlist)
##a = Wordplay()
##a.words_list=["solos","Marcus","Aurelius","Henryi","tirla","samantha","pocahontas","kayak"]
##print(a.words_with_length(5))
##print(a.start_with("M"))
##print(a.ends_with("s"))
##print(a.palindrome())
##print(a.ends_with("s"))
##print(a.only_L())
##print(a.Not_L())


#14-6######################################################
"""Write a class called Converter. The user will pass a
length and a unit when declaring an object from the class—
for example, c = Converter(9,'inches'). The possible units
are inches, feet, yards, miles, kilometers, meters,
centimeters, and millimeters. For each of these units there
should be a method that returns the length converted into
those units. For example, using the Converter object created
above, the user could call c.feet() and should get 0.75 as
the result."""

##class Converter:
##    def __init__(self, length, unit):
##        self.length = length
##        self.unit = unit
##    def convert(self, unit_convert):
##        unit_list = ["inches", "feet", 'yards', 'miles', 'kilometers', 'meters', 'centimeters']
##        value_on_foot = [12, 1, 1 / 3, 1 / 5280, 1 / 3281, 1 / 3, 30]
##        if unit_convert in unit_list:
##           length_convert = round(
##                self.length * value_on_foot[unit_list.index(unit_convert)] / value_on_foot[unit_list.index(self.unit)],
##                9)
##        return "{1} {0}".format(length_convert, unit_convert)
##    def inches(self):
##        return self.convert('inches')
##    def feet(self):
##        return self.convert('feet')
##    def yards(self):
##        return self.convert('yards')
##    def miles(self):
##        return self.convert('miles')
##    def kilometers(self):
##        return self.convert('kilometers')
##    def meters(self):
##        return self.convert('meters')
##    def centimeters(self):
##        return self.convert('centimeters')
##c = Converter(9, "inches")
##print(c.yards())
##print(c.miles())


#14-7######################################################
"""Use the Standard_deck class of this section to create a
simplified version of the game War. In this game, there are
two players. Each starts with half of a deck. The players
each deal the top card from their decks and whoever has the
higher card wins the other player’s cards and adds them to
the bottom of his deck. If there is a tie, the two cards
are eliminated from play (this differs from the actual game,
but is simpler to program). The game ends when one player
runs out of cards."""

##from random import randint
##class Card:
##    def __init__(self, value, suit):
##        self.value = value
##        self.suit = suit
##    def __str__(self):
##        names = ['Jack', 'Queen', 'King', 'Ace']
##        if self.value <= 10:
##            return '{} of {}'.format(self.value, self.suit)
##        else:
##            return '{} of {}'.format(names[self.value - 11], self.suit)
##import random
##class Card_group:
##    def __init__(self, cards=[]):
##        self.cards = cards
##    def nextCard(self):
##        return self.cards.pop(0)
##    def hasCard(self):
##        return len(self.cards) > 0
##    def size(self):
##        return len(self.cards)
##    def shuffleCard(self):
##        return random.shuffle(self.cards)
##class Standard_deck(Card_group):
##    def __init__(self):
##        self.cards = []
##        for s in ['Hearts', 'Diamonds', 'Clubs', 'Spades']:
##            for v in range(2, 15):
##                self.cards.append(Card(v, s))  # hỏi chỗ này
##class War(Standard_deck):
##    def __init__(self):
##        Standard_deck.__init__(self)
##        self.shuffleCard()
##        self.player1 = []
##        self.player2 = []
##        self.__splitCardToPlayer()
##    def __splitCardToPlayer(self):
##        cardLength = len(self.cards)
##        for i in range(0, cardLength, 2):
##            self.player1.append(self.nextCard())
##            self.player2.append(self.nextCard())
##    def __popCard(self, playerNumber):
##        if playerNumber == 1:
##            return self.player1.pop()
##        if playerNumber == 2:
##            return self.player2.pop()
##    def play(self):
##        print('Playing card')
##        while len(self.player1) > 0 and len(self.player2) > 0:
##            card1 = self.__popCard(1)
##            card2 = self.__popCard(2)
##            if card1.value > card2.value:
##                self.player1.append(card1)
##            elif card2.value > card1.value:
##                self.player2.append(card2)
##        if len(self.player1) > 0:
##            print("player 1 wins")
##        elif len(self.player2) > 0:
##            print("player 2 wins")
##        else:
##            print("noone win")
##deck = War()
##deck.play()


#14-10######################################################
'''
(a) Write a class called Connect4 that implements the logic
of a Connect4 game. Use the Tic_tac_toe class from this
chapter as a starting point. (b) Use the Connect4 class to
create a simple text-based version of the game.
'''

##import pprint as pp
##class ConnectFour:
##    def __init__(self):
##        self.Board = [[' ' for i in range(7)] for i in range(6)]
##    def get_diag_and_row(self):
##        ''' to get rows,columns and diagonals'''
##        # To print the board from a negative axis
##        x1 = [[i[j] for j in range(-1, -(len(i)+1), -1)]for i in self.Board]        
##        def diag(d):
##            ''' To get diagonals for the positive and negative slope '''
##            neg = [[d[i][j] for i in range(k+1) for j in range(k, -1, -1) if i+j == k-1]
##                    for k in range(1, 7)]
##            pos = [[d[i][j] for i in range(5, -1, -1) for j in range(-1, -8, -1) if i+j == k]
##                    for k in range(6, -2, -1)]
##            c = [i for i in (neg+pos) if len(i) >= 4]
##            return c
##        check_list = (diag(x1) + diag(self.Board) + self.Board)
##        return check_list
##    def get_column(self, col_index):
##        """
##        Returns a value at that index on every row 
##
##        """
##        return [i[col_index] for i in self.Board]
##    def check_win(self):
##        """
##        checks for four_in_a_row in the positive and Negative diagonals 
##        Returns
##        -------
##        the True if there is four in a row
##
##        """
##        #check diagonals and rows
##        four_in_row = [['1' for i in range(4)], ['2' for i in range(4)]]
##        for check in self.get_diag_and_row():
##            for j in range(len(check)):
##                if check[j:j+4] in four_in_row and j < len(check) - 3:
##                    #print ('yes')
##                    return True
##        #Check columns
##        for i in range(7):
##            for j in range(6 - 3):
##                if self.get_column(i)[j:j + 4] in four_in_row:
##                    return True
##        else:
##            return False
##    def make_move(self, col, player):
##        """
##        Parameters
##        ----------
##        col : int.
##        player : player 1 and 2.
##        
##        Returns
##        -------
##          The Board with the players move registered.
##        """
##        u = -1
##        while self.Board[u][col] != ' ' and u != -6:
##            u -= 1
##
##        self.Board[u][col] = player
##        pp.pprint(self.Board)
##        return self.Board
##    def play(self):
##        """
##        To play the Connect4 game  
##        -------
##        Returns
##        The winner of the game after each player makes their moves.
##        """
##        pp.pprint(self.Board)
##        for i in range(42):
##            #pp.pprint(self.Board)
##            
##            col = int(input('Enter column: '))-1
##            self.make_move(col, '1')
##            if self.check_win() is True:
##                return 'Player 1 Wins\nTHANKS FOR PLAYING'
##                break
##            col = int(input('Enter column: '))-1
##            self.make_move(col, '2')
##            if self.check_win() is True:
##                return 'Player 2 Wins\nTHANKS FOR PLAYING'
##                break
##game = ConnectFour()
##print(game.play())

#14-11######################################################
'''
Write a class called Poker_hand that has a field that is a
list of Card objects. There should be the following
self-explanatory methods: has_royal_flush, has_straight_flush,
has_four_of_a_kind, has_full_house, has_flush, has_straight,
has_three_of_a_kind, has_two_pair, has_pair. There should also
be a method called best that returns a string indicating what
the best hand is that can be made from those cards.
'''

##import random
##class Card:
##    def __init__(self, value, suit):
##        self.value = value
##        self.suit = suit
##    def __str__(self):
##        names = ['Jack', 'Queen', 'King', 'Ace']
##        if self.value <= 10:
##            return '{} of {}'.format(self.value, self.suit)
##        else:
##            return '{} of {}'.format(names[self.value - 11], self.suit)
##class PokerHand:
##    def __init__(self):
##        self.deck = [{'value': i, 'suit': c}for c in ['spades', 'clubs',
##                                                      'hearts', 'diamonds']for i in range(2, 15)]
##        self.deals = random.sample(self.deck, 5)
##        self.suits = [(self.deals[i])['suit'] for i in range(len(self.deals))]
##        self.values = [((self.deals[i])['value'])
##                       for i in range(len(self.deals))]
##        self.cards = [Card(self.values[i], self.suits[i]).__str__()
##                      for i in range(len(self.deals))]
##        self.values.sort()
##        self.deck_count = {i: self.values.count(i) for i in self.values}
##        self.card_count = [self.deck_count[i] for i in self.deck_count]
##        self.card_count.sort()
##    def straigth(self):
##        return self.values == [self.values[0]+i for i in range(5)] or self.values == [2, 3, 4, 5, 14]
##    def flush(self):
##        return self.suits.count(self.suits[0]) == 5
##    def straigth_flush(self):
##        return self.straigth() and self.flush()
##    def four_of_a_kind(self):
##        return self.card_count == [1, 4]
##    def three_of_a_kind(self):
##        return self.card_count == [1, 1, 3]
##    def two_pairs(self):
##        return self.card_count == [1, 2, 2]
##    def full_house(self):
##        return self.card_count == [2, 3]
##    def royal_flush(self):
##        return self.values == [10, 11, 12, 13, 14] and self.flush()
##    def one_pair(self):
##        return self.card_count == [1, 1, 1, 2]
##    def best_hand(self):
##        for i, card in enumerate(self.cards):
##            print(f'card {i+1}.', card)
##        if self.royal_flush():
##            return ("BEST HAND IS ROYAL FLUSH  ")
##        elif self.straigth_flush():
##            return("BEST HAND IS STRAIGHT FLUSH")
##        elif self.four_of_a_kind():
##            return("BEST HAND IS FOUR OF A KIND ")
##        elif self.full_house():
##            return("BEST HAND IS  FULL HOUSE")
##        elif self.flush():
##            return("BEST HAND IS FLUSH ")
##        elif self.straigth():
##            return("BEST HAND IS STRAIGHT ")
##        elif self.three_of_a_kind():
##            return("BEST HAND IS THREE OF A KIND")
##        elif self.two_pairs():
##            return("BEST HAND IS TWO PAIRS")
##        elif self.one_pair():
##            return("BEST HAND IS ONE PAIR")
##        else:
##            return "YOU HAVE HIGH CARDS"          
##poker = PokerHand()
##print(poker.best_hand())

