├── requirements.txt ├── chapter-3 ├── global_statement.py ├── zero_divide.py ├── same_name_local_global.py ├── functions.py ├── collatz.py └── zigzag.py ├── chapter-6 ├── catnapping.py ├── bullet_point_adder.py ├── picnic_table.py ├── validate_input.py ├── table_printer.py ├── mclip.py └── pig_latin.py ├── chapter-4 ├── passing_reference.py ├── my_pets.py ├── magic_8_ball.py ├── comma_code.py ├── all_my_cats.py ├── character_picture_grid.py ├── coin_flip_streaks.py ├── lists.py └── conway.py ├── chapter-2 ├── exit_example.py ├── loops.py ├── guess_the_number.py └── rps_game.py ├── chapter-7 ├── multiple_groups.py └── is_phone_number.py ├── chapter-5 ├── character_count.py ├── birthdays.py ├── tic_tac_toe.py ├── guest_picnic.py ├── fantasy_game_inventory.py └── chess_dictionary_validator.py ├── README.md ├── chapter-1 └── hello_world.py └── .gitignore /requirements.txt: -------------------------------------------------------------------------------- 1 | inflect==4.1.0 2 | pyfiglet==0.8.post1 3 | pyperclip==1.8.0 4 | regex==2020.4.4 -------------------------------------------------------------------------------- /chapter-3/global_statement.py: -------------------------------------------------------------------------------- 1 | def spam(): 2 | global eggs 3 | eggs = 'spam' 4 | 5 | eggs = 'global' 6 | spam() 7 | print(eggs) 8 | -------------------------------------------------------------------------------- /chapter-6/catnapping.py: -------------------------------------------------------------------------------- 1 | print('''Dear Alice, 2 | 3 | Eve's cat has been arrested for catnapping, cat burglary, and extortion. 4 | 5 | Sincerely, 6 | Bob''') 7 | -------------------------------------------------------------------------------- /chapter-4/passing_reference.py: -------------------------------------------------------------------------------- 1 | def eggs(some_parameter): 2 | some_parameter.append('Hello') 3 | 4 | 5 | spam = [1, 2, 3] 6 | 7 | eggs(spam) 8 | print(spam) 9 | 10 | -------------------------------------------------------------------------------- /chapter-2/exit_example.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | while True: 4 | print('Type exit to exit.') 5 | response = input() 6 | if response == 'exit': 7 | sys.exit() 8 | print (f'You typed {response}.') -------------------------------------------------------------------------------- /chapter-7/multiple_groups.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | hero_regex = re.compile('rBatman|Tina Fey') 4 | mo1 = hero_regex.search('Batman and Tina Fey') 5 | print(mo1.group()) 6 | 7 | mo2 = hero_regex.search('Tina Fey and Batman') 8 | print(mo2.group()) 9 | -------------------------------------------------------------------------------- /chapter-4/my_pets.py: -------------------------------------------------------------------------------- 1 | my_pets = ['Zophie', 'Pooka', 'Fat-tail'] 2 | print('Enter a pet name:') 3 | name = input() 4 | 5 | if name not in my_pets: 6 | print(f'I do not have a pet named {name}') 7 | else: 8 | print(f'{name} is my pet.') 9 | 10 | 11 | -------------------------------------------------------------------------------- /chapter-3/zero_divide.py: -------------------------------------------------------------------------------- 1 | def spam(divisor): 2 | try: 3 | return 42 / divisor 4 | except ZeroDivisionError: 5 | print('Error: Invalid argument.') 6 | 7 | 8 | print(spam(2)) 9 | print(spam(12)) 10 | print(spam(0)) 11 | print(spam(1)) 12 | -------------------------------------------------------------------------------- /chapter-3/same_name_local_global.py: -------------------------------------------------------------------------------- 1 | def spam(): 2 | global eggs 3 | eggs = 'spam' # global variable 4 | 5 | def bacon(): 6 | eggs = 'bacon' # local variable 7 | 8 | def ham(): 9 | print(eggs) 10 | 11 | eggs = 42 12 | spam() 13 | bacon() 14 | print(eggs) -------------------------------------------------------------------------------- /chapter-5/character_count.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | 3 | message = 'It was a bright cold day in April, and the clocks were striking thirteen.' 4 | count = {} 5 | 6 | for character in message: 7 | count.setdefault(character, 0) 8 | count[character] += 1 9 | 10 | pprint(count) -------------------------------------------------------------------------------- /chapter-3/functions.py: -------------------------------------------------------------------------------- 1 | def hello(): 2 | print('Howdy!') 3 | print('Howdy!!!') 4 | print('Hello there.') 5 | 6 | 7 | hello() 8 | hello() 9 | hello() 10 | 11 | 12 | def hello(name): 13 | print(f'Hello, {name}') 14 | 15 | 16 | hello('Cor') 17 | 18 | 19 | def square(x): 20 | return x ** 2 21 | 22 | 23 | print(square(4)) 24 | -------------------------------------------------------------------------------- /chapter-7/is_phone_number.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | phone_number_regex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)') 4 | 5 | mo = phone_number_regex.search('My phone number is 415-555-4242') 6 | 7 | print(f'Phone number found: {mo.group()}') 8 | 9 | area_code, main_number = mo.groups() 10 | 11 | print(f'Area: {area_code}, Main number: {main_number}') 12 | 13 | -------------------------------------------------------------------------------- /chapter-6/bullet_point_adder.py: -------------------------------------------------------------------------------- 1 | #! python3 2 | # bulletPointAdder.py - Adds Wikipedia bullet points to the start 3 | # of each line of text on the clipboard. 4 | 5 | import pyperclip 6 | 7 | lines = pyperclip.paste().split('\n') 8 | lines_with_bullets = [f'* {line}' for line in lines] 9 | result = '\n'.join(lines_with_bullets) 10 | 11 | pyperclip.copy(result) 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # automate-the-boring-stuff 2 | [Automate the Boring Stuff with Python](https://automatetheboringstuff.com) exercises. 3 | Includes the given examples (which have been modified to be PEP-8 compliant, DRYer, and more modern), 4 | as well as my own solutions to the practice projects. 5 | 6 | Code is far from optimal as I'm learning Python while coming up with these answers. -------------------------------------------------------------------------------- /chapter-1/hello_world.py: -------------------------------------------------------------------------------- 1 | # Basic 'Hello world' type of program that asks for name and age 2 | print('Hello, world!') 3 | 4 | print('What is your name?') 5 | my_name = input() 6 | print(f'It is good to meet you, {my_name} ({len(my_name)})') 7 | 8 | print() 9 | 10 | print('What is your age?') 11 | my_age = input() 12 | print(f'You will be {str(int(my_age) + 1)} in a year.') 13 | 14 | -------------------------------------------------------------------------------- /chapter-4/magic_8_ball.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | messages = ['It is certain', 4 | 'It is decidedly so', 5 | 'Yes definitely', 6 | 'Reply hazy try again', 7 | 'Ask again later', 8 | 'Concentrate and ask again', 9 | 'My reply is no', 10 | 'Outlook not so good', 11 | 'Very doubtful'] 12 | 13 | print(random.choice(messages)) -------------------------------------------------------------------------------- /chapter-6/picnic_table.py: -------------------------------------------------------------------------------- 1 | def print_picnic(items, left_width, right_width): 2 | print('PICNIC ITEMS'.center(left_width + right_width, '-')) 3 | for k, v in items.items(): 4 | print(k.ljust(left_width, '.') + str(v).rjust(right_width)) 5 | 6 | 7 | picnic_items = {'sandwiches': 4, 'apples': 12, 'cups': 4, 'cookies': 8000} 8 | print_picnic(picnic_items, 12, 5) 9 | print_picnic(picnic_items, 20, 6) 10 | -------------------------------------------------------------------------------- /chapter-6/validate_input.py: -------------------------------------------------------------------------------- 1 | while True: 2 | print('Enter your age:') 3 | age = input() 4 | if age.isdecimal(): 5 | break 6 | print('Please enter a number for your age.') 7 | 8 | while True: 9 | print('Select a new password (letters and numbers only):') 10 | password = input() 11 | if password.isalnum(): 12 | break 13 | print('Passwords can only have letters and numbers.') -------------------------------------------------------------------------------- /chapter-4/comma_code.py: -------------------------------------------------------------------------------- 1 | def comma_code(list): 2 | if not list: 3 | return '' 4 | elif len(list) == 1: 5 | return list[0] 6 | elif len(list) == 2: 7 | return f'{list[0]} and {list[1]}' 8 | else: 9 | return ', '.join(list[:-1]) + f', and {list[-1]}' 10 | 11 | 12 | print(comma_code(['Apples'])) 13 | print(comma_code(['Apples', 'Bananas'])) 14 | print(comma_code(['Apples', 'Bananas', 'Carrots'])) -------------------------------------------------------------------------------- /chapter-2/loops.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | name = '' 4 | 5 | while name != 'your name': 6 | print('Please type your name.') 7 | name = input() 8 | print('Thank you!') 9 | 10 | 11 | for i in range(5): 12 | print(i) 13 | 14 | total = 0 15 | for num in range(101): 16 | total += num 17 | print(total) 18 | 19 | for i in range(12, 16): 20 | print(i) 21 | 22 | for i in range (0, 10, 2): 23 | print(i) 24 | 25 | for i in range (5, -1, -1): 26 | print(i * random.randit(1, 10)) -------------------------------------------------------------------------------- /chapter-4/all_my_cats.py: -------------------------------------------------------------------------------- 1 | import inflect # Used for converting numbers to word-based ordinals 2 | p = inflect.engine() 3 | 4 | cat_names = [] 5 | 6 | while True: 7 | ordinal = p.number_to_words(p.ordinal(len(cat_names) + 1)) 8 | print(f'Enter the name of the {ordinal} cat (Or enter nothing to stop.):') 9 | name = input() 10 | if name == '': 11 | break 12 | cat_names += [name] 13 | 14 | print('The cat names are:') 15 | for name in cat_names: 16 | print(name) 17 | 18 | -------------------------------------------------------------------------------- /chapter-5/birthdays.py: -------------------------------------------------------------------------------- 1 | birthdays = {'Alice': 'Apr 1', 'Bob': 'Dec 12', 'Carol': 'Mar 4'} 2 | 3 | while True: 4 | print('Enter a name: (blank to quit)') 5 | name = input() 6 | if name == '': 7 | break 8 | 9 | if name in birthdays: 10 | print(f'{birthdays[name]} is the birthday of {name}') 11 | else: 12 | print(f'I do not have birthday indormation for {name}') 13 | print('What is their birthday?') 14 | birthdays[name] = input() 15 | print('Birthday database updated.') -------------------------------------------------------------------------------- /chapter-6/table_printer.py: -------------------------------------------------------------------------------- 1 | def print_table(table): 2 | column_widths = [max([len(item) for item in column]) for column in table] 3 | 4 | for row in range(len(table[0])): 5 | for column in range(len(table)): 6 | cell_content = table[column][row] 7 | column_width = column_widths[column] 8 | print(cell_content.rjust(column_width), end=' ') 9 | print() 10 | 11 | 12 | table_data = [['apples', 'oranges', 'cherries', 'banana'], 13 | ['Alice', 'Bob', 'Carol', 'David'], 14 | ['dogs', 'cats', 'moose', 'goose']] 15 | 16 | print_table(table_data) 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /chapter-2/guess_the_number.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | secret_number = random.randint(1, 20) 4 | print('I am thinking of a number between 1 and 20') 5 | 6 | for guesses_taken in range(1, 7): 7 | print('Take a guess!') 8 | guess = int(input()) 9 | 10 | if guess < secret_number: 11 | print('Your guess is too low.') 12 | elif guess > secret_number: 13 | print('Your guess is too high.') 14 | else: 15 | break # The guess is correct! 16 | 17 | 18 | if guess == secret_number: 19 | print(f'Good job! You guessed my number in {guesses_taken} guesses!') 20 | else: 21 | print(f'Nope. The number I was thinking of was {secret_number}') -------------------------------------------------------------------------------- /chapter-5/tic_tac_toe.py: -------------------------------------------------------------------------------- 1 | the_board = {'top-L': ' ', 'top-M': ' ', 'top-R': ' ', 2 | 'mid-L': ' ', 'mid-M': ' ', 'mid-R': ' ', 3 | 'low-L': ' ', 'low-M': ' ', 'low-R': ' '} 4 | 5 | 6 | def print_board(board): 7 | print(board['top-L'] + '|' + board['top-M'] + '|' + board['top-R']) 8 | print('-+-+-') 9 | print(board['mid-L'] + '|' + board['mid-M'] + '|' + board['mid-R']) 10 | print('-+-+-') 11 | print(board['low-L'] + '|' + board['low-M'] + '|' + board['low-R']) 12 | 13 | 14 | for i in range(9): 15 | turn = 'O' if i % 2 else 'X' 16 | print_board(the_board) 17 | print(f'Turn for {turn}. Move on which space?') 18 | move = input() 19 | the_board[move] = turn 20 | -------------------------------------------------------------------------------- /chapter-6/mclip.py: -------------------------------------------------------------------------------- 1 | #! python3 2 | # mclip.py - A multi-clipboard program 3 | import sys 4 | import pyperclip 5 | 6 | TEXT = {'agree': """Yes, I agree. That sounds fine to me.""", 7 | 'busy': """Sorry, can we do this later this week or next week?""", 8 | 'upsell': """Would you consider making this a monthly donation?"""} 9 | 10 | if len(sys.argv) < 2: 11 | print('Usage: python mclip.py [keyphrase] - copy phrase text') 12 | sys.exit() 13 | 14 | keyphrase = sys.argv[1] # first command line arg is the keyphrase 15 | 16 | if keyphrase in TEXT: 17 | pyperclip.copy(TEXT[keyphrase]) 18 | print(f'Text for {keyphrase} copied to clipboard') 19 | else: 20 | print(f'There is no text for {keyphrase}') 21 | -------------------------------------------------------------------------------- /chapter-3/collatz.py: -------------------------------------------------------------------------------- 1 | def collatz(number): 2 | if number % 2 == 0: 3 | return number // 2 4 | else: 5 | return 3 * number + 1 6 | 7 | 8 | def positive_int_input(): 9 | number = None 10 | while number is None: 11 | try: 12 | number = int(input()) 13 | except ValueError: 14 | print("ERROR: Please enter a positive number.") 15 | continue 16 | if number < 0: 17 | print("ERROR: Please enter a POSITIVE number.") 18 | continue 19 | return number 20 | 21 | 22 | print("Enter a positive number to apply the Collatz sequence on:") 23 | n = positive_int_input() 24 | 25 | while n != 1: 26 | n = collatz(n) 27 | print(n) 28 | -------------------------------------------------------------------------------- /chapter-5/guest_picnic.py: -------------------------------------------------------------------------------- 1 | all_guests = {'Alice': {'apples': 5, 'pretzels': 12}, 2 | 'Bob': {'ham sandwiches': 3, 'apples': 2}, 3 | 'Carol': {'cups': 3, 'apple pies': 1}} 4 | 5 | 6 | def total_brought(guests, item): 7 | count = 0 8 | for k, v in guests.items(): 9 | count += v.get(item, 0) 10 | return count 11 | 12 | 13 | print(f' - Apples {total_brought(all_guests, "apples")}') 14 | print(f' - Cups {total_brought(all_guests, "cups")}') 15 | print(f' - Cakes {total_brought(all_guests, "cakes")}') 16 | print(f' - Ham Sandwiches {total_brought(all_guests, "ham sandwiches")}') 17 | print(f' - Apple Pies {total_brought(all_guests, "apple pies")}') 18 | -------------------------------------------------------------------------------- /chapter-5/fantasy_game_inventory.py: -------------------------------------------------------------------------------- 1 | my_inventory = {'gold coin': 42, 'rope': 1} 2 | 3 | 4 | def display_inventory(inventory): 5 | """Display an inventory""" 6 | print('Inventory:') 7 | for k, v in inventory.items(): 8 | print(f'{v} {k}') 9 | print(f'Total number of items: {sum(inventory.values())}') 10 | 11 | 12 | def add_to_inventory(inventory, added_items): 13 | """Add items to an inventory. Modifies the inventory parameter.""" 14 | for item in added_items: 15 | inventory.setdefault(item, 0) 16 | inventory[item] += 1 17 | 18 | 19 | display_inventory(my_inventory) 20 | add_to_inventory(my_inventory, ['gold coin', 'dagger', 'gold coin', 'gold coin', 'ruby']) 21 | display_inventory(my_inventory) 22 | -------------------------------------------------------------------------------- /chapter-3/zigzag.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | 4 | # CONFIGURATION 5 | WIDTH = 30 6 | INDENT_STEP = 2 7 | SPEED = 50 8 | 9 | # Current State 10 | indent = 0 # How many spaces to indent 11 | indent_increasing = True 12 | 13 | try: 14 | while True: 15 | print(' ' * indent, end='') 16 | print('********') 17 | time.sleep(1 / SPEED) 18 | 19 | if indent_increasing: 20 | indent += INDENT_STEP 21 | if indent == WIDTH: 22 | indent_increasing = False # Change direction 23 | 24 | else: 25 | indent -= INDENT_STEP 26 | if indent == 0: 27 | indent_increasing = True # Change direction 28 | 29 | except KeyboardInterrupt: 30 | sys.exit() 31 | 32 | -------------------------------------------------------------------------------- /chapter-4/character_picture_grid.py: -------------------------------------------------------------------------------- 1 | grid = [['.', '.', '.', '.', '.', '.'], 2 | ['.', 'O', 'O', '.', '.', '.'], 3 | ['O', 'O', 'O', 'O', '.', '.'], 4 | ['O', 'O', 'O', 'O', 'O', '.'], 5 | ['.', 'O', 'O', 'O', 'O', 'O'], 6 | ['O', 'O', 'O', 'O', 'O', '.'], 7 | ['O', 'O', 'O', 'O', '.', '.'], 8 | ['.', 'O', 'O', '.', '.', '.'], 9 | ['.', '.', '.', '.', '.', '.']] 10 | 11 | # Simple double for loop implementation. 12 | for y in range(0, len(grid[0])): 13 | for x in range(0, len(grid)): 14 | print(grid[x][y], end='') 15 | print() 16 | 17 | print() # separate the two implementations 18 | 19 | # zip-based grid rotation: https://stackoverflow.com/questions/8421337/rotating-a-two-dimensional-array-in-python 20 | for x in zip(*grid[::-1]): 21 | print(''.join(x)) 22 | -------------------------------------------------------------------------------- /chapter-4/coin_flip_streaks.py: -------------------------------------------------------------------------------- 1 | import random 2 | from statistics import mean 3 | 4 | 5 | # For more implementations of this problem, including a test runner that check which one is the fastest, see: 6 | # https://github.com/cor/drudgery 7 | # for an implementation of this problem in Rust, see: 8 | # https://github.com/cor/coin-flip-streaks-rs 9 | 10 | 11 | # Generate a sequence of 100 'H' (Heads) and 'T' (Tails) throws 12 | # and return the amount of 6-long chains of the same value 13 | def experiment(): 14 | flip_results = "".join(random.choices(('H', 'T'), k=100)) 15 | streak_count = 0 16 | for i in range(len(flip_results) - 6): 17 | streak_count += (flip_results[i:i + 6] == flip_results[i] * 6) 18 | return streak_count 19 | 20 | 21 | results = [] 22 | for _ in range(10_000): 23 | results.append(experiment()) 24 | 25 | print(mean(results)) 26 | -------------------------------------------------------------------------------- /chapter-4/lists.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | spam = [['cat', 'bat'], [10, 20, 30, 42, 50]] 4 | test = ['cat', 'bat', 'rat', 'elephant'] 5 | 6 | 7 | print(spam[-1][3]) 8 | print(spam[1][0:2]) 9 | print(test[0:-1]) 10 | print(test[:2]) 11 | print(len(test)) 12 | 13 | test[1] = 'aardvark' 14 | print(test) 15 | 16 | a = [1, 2, 3] + ['A', 'B', 'C'] * 3 17 | 18 | print(a) 19 | 20 | del a[3] 21 | 22 | print(a) 23 | 24 | print('howdy' in ['hello', 'hi', 'howdy', 'heyas']) 25 | 26 | 27 | pets = ['Dog', 'Cat', 'Moose'] 28 | print(random.choice(pets)) 29 | print(random.choice(pets)) 30 | print(random.choice(pets)) 31 | 32 | print(pets) 33 | random.shuffle(pets) 34 | print(pets) 35 | 36 | print(['hello', 'hi', 'howdy', 'heyas'].index('hi')) 37 | 38 | pets.append('Fox') 39 | pets.insert(1, 'Chicken') 40 | 41 | print(pets) 42 | 43 | pets.remove('Chicken') 44 | print(pets) 45 | 46 | pets.sort(reverse=True) 47 | 48 | print(pets) 49 | 50 | characters = ['a', 'B', 'z', 'D'] 51 | characters.sort(key=str.lower, reverse=True) 52 | print(characters) 53 | 54 | 55 | for c in 'Breath of the Wild': 56 | print(f'***<- {c} ->***') 57 | 58 | -------------------------------------------------------------------------------- /chapter-2/rps_game.py: -------------------------------------------------------------------------------- 1 | import random, sys 2 | 3 | print('ROCK, PAPER, SCISSORS') 4 | 5 | # These variables keep track of the number of wins, losses, and ties. 6 | wins = 0 7 | losses = 0 8 | ties = 0 9 | 10 | # The main game loop. 11 | while True: 12 | print(f'{wins} Wins, {losses} Losses, {ties} Ties') 13 | 14 | # The player input loop. 15 | while True: 16 | print('Enter your move: (r)ock (p)aper (s)cissors or (q)uit') 17 | player_move = input() 18 | 19 | if player_move == 'q': 20 | sys.exit() # Quit the program. 21 | if player_move in ('r', 'p', 's'): 22 | break # Break out of the player input loop. 23 | 24 | # Invalid input 25 | print('Type one of r, p, s, or q.') 26 | 27 | # Display what the player chose: 28 | if player_move == 'r': 29 | print('ROCK versus...') 30 | elif player_move == 'p': 31 | print('PAPER versus...') 32 | elif player_move == 's': 33 | print('SCISSORS versus...') 34 | 35 | # Display what the computer chose: 36 | computer_input = random.choice((('r', 'ROCK'), ('p', 'PAPER'), ('s', 'SCISSORS'))) 37 | computer_move = computer_input[0] 38 | print(computer_input[1]) 39 | 40 | # Display and record the win/loss/tie: 41 | if player_move == computer_move: 42 | print('It is a tie!') 43 | ties += 1 44 | elif (player_move, computer_move) in (('r', 's'), ('p', 'r'), ('s', 'p')): 45 | print('You win!') 46 | wins += 1 47 | else: 48 | print('You lose!') 49 | losses += 1 50 | -------------------------------------------------------------------------------- /chapter-5/chess_dictionary_validator.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | 3 | board_a = {'1h': 'bking', '6c': 'wqueen', '2g': 'bbishop', '5h': 'bqueen', '3e': 'wking'} 4 | board_b = {'1h': 'bking', '2h': 'bking', '6c': 'wqueen', '2g': 'bbishop', '5h': 'bqueen', '3e': 'wking'} 5 | board_c = {'6c': 'wqueen', '2g': 'bbishop', '5h': 'bqueen', '3e': 'wking'} 6 | board_d = {'6c': 'wqueen', '6x': 'wbking', '2g': 'bbishop', '5h': 'bqueen', '3e': 'wking'} 7 | 8 | 9 | def is_valid_chess_board(board): 10 | pieces = list(board.values()) 11 | spaces = list(board.keys()) 12 | 13 | # Piece validation 14 | pieces_are_valid = {'bpawn', 'bbishop', 'brook', 'bqueen', 'bking', 15 | 'wpawn', 'wbishop', 'wrook', 'wqueen', 'wking'}.issuperset(pieces) 16 | 17 | knights_present = pieces.count('bking') == 1 and \ 18 | pieces.count('wking') == 1 19 | 20 | piece_count = [p[0] for p in pieces].count('b') <= 16 and \ 21 | [p[0] for p in pieces].count('w') <= 16 22 | 23 | pawn_count = pieces.count('bpawn') <= 8 and \ 24 | pieces.count('wpawn') <= 8 25 | 26 | # Space validation 27 | def is_valid_space(s): 28 | return int(s[0]) in range(8) and 'a' <= s[1] <= 'h' 29 | 30 | spaces_are_valid = all(is_valid_space(s) for s in spaces) 31 | 32 | return pieces_are_valid and knights_present and piece_count and pawn_count and spaces_are_valid 33 | 34 | 35 | print(is_valid_chess_board(board_a)) 36 | print(is_valid_chess_board(board_b)) 37 | print(is_valid_chess_board(board_c)) 38 | print(is_valid_chess_board(board_d)) 39 | -------------------------------------------------------------------------------- /chapter-6/pig_latin.py: -------------------------------------------------------------------------------- 1 | #! python3 2 | # pig_latin.py - Translates text into pig latin. 3 | 4 | print('Enter the English message to translate into Pig Latin:') 5 | message = input() 6 | pig_latin = [] 7 | 8 | for word in message.split(): 9 | # Separate the non-letters at the start of this word: 10 | prefix_non_letters = '' 11 | while len(word) > 0 and not word[0].isalpha(): 12 | prefix_non_letters += word[0] 13 | word = word[1:] 14 | 15 | # If the word does not contain letters, add it to pig latin: 16 | if len(word) == 0: 17 | pig_latin.append(prefix_non_letters) 18 | continue 19 | 20 | # Separate the non-letters at the end of this word: 21 | suffix_non_letters = '' 22 | while not word[-1].isalpha(): 23 | suffix_non_letters += word[-1] 24 | word = word[:-1] 25 | 26 | # Remember if the word was in uppercase or title case. 27 | was_upper = word.isupper() 28 | was_title = word.istitle() 29 | 30 | word = word.lower() # Make the word lowercase for translation. 31 | 32 | # Separate the consonants at the start of this word: 33 | prefix_constants = '' 34 | while len(word) > 0 and not word[0] in 'aeiouy': 35 | prefix_constants += word[0] 36 | word = word[1:] 37 | 38 | # Add the Pig Latin ending to the word: 39 | word_ending = 'ay' if prefix_non_letters != '' else 'yay' 40 | word += word_ending 41 | 42 | # Set the word back to uppercase or title case: 43 | if was_upper: 44 | word = word.upper() 45 | if was_title: 46 | word = word.title() 47 | 48 | # Add the non-letters back to the start or end of the word. 49 | pig_latin.append(prefix_constants + word + suffix_non_letters) 50 | 51 | # Join all the words back together into a single string: 52 | print(' '.join(pig_latin)) 53 | -------------------------------------------------------------------------------- /chapter-4/conway.py: -------------------------------------------------------------------------------- 1 | import random 2 | import time 3 | import copy 4 | 5 | WIDTH = 200 6 | HEIGHT = 20 7 | INTERVAL = 0.1 8 | 9 | next_cells = [] 10 | 11 | # Initialize the world with random cells 12 | for x in range(WIDTH): 13 | column = [] # Create a new column 14 | for y in range(HEIGHT): 15 | if bool(random.getrandbits(1)): 16 | column.append('#') 17 | else: 18 | column.append(' ') 19 | next_cells.append(column) 20 | 21 | 22 | while True: # Main program loop. 23 | print('\n\n\n\n\n') # Separate each step with newlines. 24 | current_cells = copy.deepcopy(next_cells) 25 | 26 | # Print current_cells on the screen: 27 | for y in range(HEIGHT): 28 | for x in range(WIDTH): 29 | print(current_cells[x][y], end='') # Print the # or space. 30 | print() # Print a newline at the end of the row. 31 | 32 | # Calculate the next step's cells based on current step's cells: 33 | for x in range(WIDTH): 34 | for y in range(HEIGHT): 35 | # Get neighboring coordinates: 36 | # `% WIDTH` ensures leftCoord is always between 0 and WIDTH - 1 37 | left_coord = (x - 1) % WIDTH 38 | right_coord = (x + 1) % WIDTH 39 | top_coord = (y - 1) % HEIGHT 40 | bottom_coord = (y + 1) % HEIGHT 41 | 42 | neighbor_coords = ((left_coord, top_coord), (x, top_coord), (right_coord, top_coord), # top row 43 | (left_coord, y), (right_coord, y), # middle row 44 | (left_coord, bottom_coord), (x, bottom_coord), (right_coord, bottom_coord)) # bottom row 45 | 46 | # Count number of living neighbors: 47 | neighbor_count = 0 48 | for coord in neighbor_coords: 49 | if current_cells[coord[0]][coord[1]] == '#': 50 | neighbor_count += 1 51 | 52 | # Set cell based on Conway's Game of Life rules: 53 | if current_cells[x][y] == '#' and (neighbor_count == 2 or neighbor_count == 3): 54 | # Living cells with 2 or 3 neighbors stay alive: 55 | next_cells[x][y] = '#' 56 | elif current_cells[x][y] == ' ' and neighbor_count == 3: 57 | # Dead cells with 3 neighbors become alive: 58 | next_cells[x][y] = '#' 59 | else: 60 | # Everything else dies or stays dead: 61 | next_cells[x][y] = ' ' 62 | 63 | time.sleep(INTERVAL) # Add a 1-second pause to reduce flickering. 64 | 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 132 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 133 | 134 | # User-specific stuff 135 | .idea/**/workspace.xml 136 | .idea/**/tasks.xml 137 | .idea/**/usage.statistics.xml 138 | .idea/**/dictionaries 139 | .idea/**/shelf 140 | 141 | # Generated files 142 | .idea/**/contentModel.xml 143 | 144 | # Sensitive or high-churn files 145 | .idea/**/dataSources/ 146 | .idea/**/dataSources.ids 147 | .idea/**/dataSources.local.xml 148 | .idea/**/sqlDataSources.xml 149 | .idea/**/dynamic.xml 150 | .idea/**/uiDesigner.xml 151 | .idea/**/dbnavigator.xml 152 | 153 | # Gradle 154 | .idea/**/gradle.xml 155 | .idea/**/libraries 156 | 157 | # Gradle and Maven with auto-import 158 | # When using Gradle or Maven with auto-import, you should exclude module files, 159 | # since they will be recreated, and may cause churn. Uncomment if using 160 | # auto-import. 161 | # .idea/artifacts 162 | # .idea/compiler.xml 163 | # .idea/jarRepositories.xml 164 | # .idea/modules.xml 165 | # .idea/*.iml 166 | # .idea/modules 167 | # *.iml 168 | # *.ipr 169 | 170 | # CMake 171 | cmake-build-*/ 172 | 173 | # Mongo Explorer plugin 174 | .idea/**/mongoSettings.xml 175 | 176 | # File-based project format 177 | *.iws 178 | 179 | # IntelliJ 180 | out/ 181 | 182 | # mpeltonen/sbt-idea plugin 183 | .idea_modules/ 184 | 185 | # JIRA plugin 186 | atlassian-ide-plugin.xml 187 | 188 | # Cursive Clojure plugin 189 | .idea/replstate.xml 190 | 191 | # Crashlytics plugin (for Android Studio and IntelliJ) 192 | com_crashlytics_export_strings.xml 193 | crashlytics.properties 194 | crashlytics-build.properties 195 | fabric.properties 196 | 197 | # Editor-based Rest Client 198 | .idea/httpRequests 199 | 200 | # Android studio 3.1+ serialized cache file 201 | .idea/caches/build_file_checksums.ser 202 | --------------------------------------------------------------------------------