diff --git a/.gitignore b/.gitignore index d0f4d7c..d8f641b 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,4 @@ misc.xml .idea/workspace.xml .idea/inspectionProfiles/profiles_settings.xml .idea/inspectionProfiles/Project_Default.xml +mkennedy.xml diff --git a/solutions/ch-04-writing-your-first-lines-of-code/step_1.md b/solutions/ch-04-writing-your-first-lines-of-code/step_1.md new file mode 100644 index 0000000..a329d47 --- /dev/null +++ b/solutions/ch-04-writing-your-first-lines-of-code/step_1.md @@ -0,0 +1,27 @@ +## Step 1: Run the Python REPL and verify you have Python 3.6 or higher. + +### On macOS and Linux + +```bash +$ python3 + +Python 3.8.3 (default, May 27 2020, 20:54:22) +[Clang 11.0.3 (clang-1103.0.32.59)] on darwin +Type "help", "copyright", "credits" or "license" for more information. +>>> +``` + +### On Windows + +```commandline +# Ensure the right version of Python +# Should be 3.6 or higher + +C:\project\> python -V +Python 3.8.3 + +C:\project\> python +Python 3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)] on win32 +Type "help", "copyright", "credits" or "license" for more information. +>>> +``` \ No newline at end of file diff --git a/solutions/ch-04-writing-your-first-lines-of-code/step_2.md b/solutions/ch-04-writing-your-first-lines-of-code/step_2.md new file mode 100644 index 0000000..67a5ae5 --- /dev/null +++ b/solutions/ch-04-writing-your-first-lines-of-code/step_2.md @@ -0,0 +1,17 @@ +## Step 2: Create a variable which is a whole number, compute the square and cube of it + +``` +>>> num = 4 +>>> sqr = num * num +>>> sqr +16 +>>> cube = num * num * num +>>> cube +64 +>>> # A better way to do this +>>> num ** 2 +16 +>>> num ** 3 +64 +>>> +``` diff --git a/solutions/ch-04-writing-your-first-lines-of-code/step_3.md b/solutions/ch-04-writing-your-first-lines-of-code/step_3.md new file mode 100644 index 0000000..ad27313 --- /dev/null +++ b/solutions/ch-04-writing-your-first-lines-of-code/step_3.md @@ -0,0 +1,15 @@ +## Step 3: Ask a user for their name and age. + +Write code to tell them how many years you are older than them (negative numbers for younger is fine at this point instead of older / younger with only positive numbers). + +``` +>>> my_age = 47 +>>> response = input("How old are you? ") +How old are you? 53 +>>> their_age = int(response) +>>> diff = their_age - my_age +>>> print(f'Great age! You are {diff} years older than me.') +Great age! You are 6 years older than me. +``` + + diff --git a/solutions/ch-04-writing-your-first-lines-of-code/step_4.md b/solutions/ch-04-writing-your-first-lines-of-code/step_4.md new file mode 100644 index 0000000..ddc00b0 --- /dev/null +++ b/solutions/ch-04-writing-your-first-lines-of-code/step_4.md @@ -0,0 +1,16 @@ +## Step 4: Use the built-in library datetime + +Use the built-in library datetime and the function `datetime.datetime.now()` +to determine the current year and print that to REPL using an f-string. + + +``` +>>> import datetime +>>> right_now = datetime.datetime.now() +>>> # What is this thing? +>>> right_now +datetime.datetime(2020, 7, 10, 14, 41, 33, 790353) +>>> print(f"What a crazy year {right_now.year} has been, eh?") +What a crazy year 2020 has been, eh? +>>> +``` diff --git a/solutions/ch-04-writing-your-first-lines-of-code/step_5.png b/solutions/ch-04-writing-your-first-lines-of-code/step_5.png new file mode 100644 index 0000000..14fec64 Binary files /dev/null and b/solutions/ch-04-writing-your-first-lines-of-code/step_5.png differ diff --git a/solutions/ch-05-interactive-code/hello_world.py b/solutions/ch-05-interactive-code/hello_world.py new file mode 100644 index 0000000..0e03bbb --- /dev/null +++ b/solutions/ch-05-interactive-code/hello_world.py @@ -0,0 +1,2 @@ +# If you can run this, thins are mostly working well in your setup. +print("Hello world.") diff --git a/solutions/ch-05-interactive-code/step_1.md b/solutions/ch-05-interactive-code/step_1.md new file mode 100644 index 0000000..b570804 --- /dev/null +++ b/solutions/ch-05-interactive-code/step_1.md @@ -0,0 +1,3 @@ +## Step 1: Create a hello_world.py file and execute it with Python + +See [hello_world.py](./hello_world.py) diff --git a/solutions/ch-05-interactive-code/step_2_even_odd.py b/solutions/ch-05-interactive-code/step_2_even_odd.py new file mode 100644 index 0000000..d3f2cdb --- /dev/null +++ b/solutions/ch-05-interactive-code/step_2_even_odd.py @@ -0,0 +1,14 @@ +print("Let's talk about numbers!") +print() + +num_text = input("What's your favorite whole number? ") +num = int(num_text) + +if num % 2 == 0: + print(f"What a sweet number, {num:,} is even!") +else: + print(f"That's odd. Yeah, I mean {num:,} is an odd number mathematically.") + +# Note: +# {num:,} does digit grouping: 10101010 -> 10,101,010 +# {num} would just repeat it as 10101010. diff --git a/solutions/ch-05-interactive-code/step_3_even_odd_a_lot.py b/solutions/ch-05-interactive-code/step_3_even_odd_a_lot.py new file mode 100644 index 0000000..335ee50 --- /dev/null +++ b/solutions/ch-05-interactive-code/step_3_even_odd_a_lot.py @@ -0,0 +1,23 @@ +print("Let's talk about numbers!") +print() + +question_text = "Give me a whole number [0 to cancel]? " + +num_text = input(question_text) +num = int(num_text) + +while num != 0: + + if num % 2 == 0: + print(f"What a sweet number, {num:,} is even!") + else: + print(f"That's odd. Yeah, I mean {num:,} is an odd number mathematically.") + + # Note: + # {num:,} does digit grouping: 10101010 -> 10,101,010 + # {num} would just repeat it as 10101010. + + num_text = input(question_text) + num = int(num_text) + +print("kthxbye!") diff --git a/solutions/ch-05-interactive-code/step_4.png b/solutions/ch-05-interactive-code/step_4.png new file mode 100644 index 0000000..dbcf6e9 Binary files /dev/null and b/solutions/ch-05-interactive-code/step_4.png differ diff --git a/solutions/ch-06-organizing-code-with-functions/guessinggame.py b/solutions/ch-06-organizing-code-with-functions/guessinggame.py new file mode 100644 index 0000000..9cbee52 --- /dev/null +++ b/solutions/ch-06-organizing-code-with-functions/guessinggame.py @@ -0,0 +1,52 @@ +import random + + +def main(): + show_header() + play_game() + + +def show_header(): + print("------------------------------") + print(" M&M guessing game!") + print("------------------------------") + + print("Guess the number of M&Ms and you get lunch on the house!") + print() + + +def get_guess_from_user(): + guess_text = input("How many M&Ms are in the jar? ") + guess = int(guess_text) + return guess + + +def evaluate_guess(guess, actual_count): + if actual_count == guess: + print(f"You got a free lunch! It was {guess}.") + elif guess < actual_count: + print("Sorry, that's too LOW!") + else: + print("That's too HIGH!") + + return actual_count == guess + + +def play_game(): + mm_count = random.randint(1, 100) + attempt_limit = 5 + attempts = 0 + + while attempts < attempt_limit: + guess = get_guess_from_user() + attempts += 1 + + won = evaluate_guess(guess, mm_count) + if won: + break + + print(f"Bye, you're done in {attempts} attempts!") + + +if __name__ == '__main__': + main() diff --git a/solutions/ch-07-data-structures/dictionary_program.py b/solutions/ch-07-data-structures/dictionary_program.py new file mode 100644 index 0000000..3a9143f --- /dev/null +++ b/solutions/ch-07-data-structures/dictionary_program.py @@ -0,0 +1,31 @@ +##################################################################### +# +# The essence of this practice is to create this data structure here: +# +d = { + 'Sam': 7, + 'rolls': ['rock', 'paper', 'scissors'], + 'done': True +} + +##################################################################### +# +# This is unchanged from the instructions, +# sans formatting and display of output of expected. +# \t means tab in Python strings. +# + +print(d["Sam"], "\t\t\t\t\t\t\t\t# <- outputs 7?") +print(d['rolls'], "\t# <- outputs ['rock', 'paper', 'scissors']?") +print(d.get('Sarah'), "\t\t\t\t\t\t\t# <- outputs None?") +print(d.get('Jeff', -1), "\t\t\t\t\t\t\t\t# <- outputs -1?") +print(d['done'], "\t\t\t\t\t\t\t# <- outputs True?") + +#################################################################### +# Output when running this: +# +# 7 # <- outputs 7? +# ['rock', 'paper', 'scissors'] # <- outputs ['rock', 'paper', 'scissors']? +# None # <- outputs None? +# -1 # <- outputs -1? +# True # <- outputs True? diff --git a/solutions/ch-08-problem-solving/connect4.py b/solutions/ch-08-problem-solving/connect4.py new file mode 100644 index 0000000..7a39f02 --- /dev/null +++ b/solutions/ch-08-problem-solving/connect4.py @@ -0,0 +1,212 @@ +import random +from typing import List, Optional + + +def main(): + print() + print("Welcome to Connect 4 from TALK PYTHON") + print() + + # CREATE THE BOARD: + # Board is a list of rows + # Rows are a list of cells + board = [ + # 6 rows + [None, None, None, None, None, None, None], # 7 columns per row + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + ] + + # CHOOSE INITIAL PLAYER + # We could use X and O, but let's liven it + # up with some emoji from: https://emojipedia.org/baseball/ + symbols = ["🏀", "🥎"] + + active_player_index = 0 + player_name = input("What is your name player 1? ") + players = [player_name.capitalize(), "Computer"] + print(f"Welcome {players[0]}") + print(f"Your symbol will be {symbols[0]}.") + print(f"{players[1]}, will be {symbols[1]}.") + player = players[active_player_index] + symbol = symbols[active_player_index] + + # UNTIL SOMEONE WINS + while not find_winner(board): + # SHOW THE BOARD + player = players[active_player_index] + symbol = symbols[active_player_index] + + announce_turn(player) + show_board(board) + if not choose_location(board, symbol, active_player_index == 1): + print("That isn't an option, try again.") + continue + + # TOGGLE ACTIVE PLAYER + active_player_index = (active_player_index + 1) % len(players) + + print() + print(f"GAME OVER! {player} ({symbol}) has won with the board: ") + show_board(board) + print() + + +def choose_location(board, symbol, is_computer): + if not is_computer: + column = int(input("Choose which column: ")) + else: + column = random.randint(1, len(board[0])) + print(f"Computer chooses column {column}") + + column -= 1 + if column < 0 or column >= len(board[0]): + return False + + row = find_bottom_row(board, column) + if row is None: + return False + + cell = board[row][column] + if cell is not None: + return False + + board[row][column] = symbol + return True + + +def find_bottom_row(board: List[List[str]], column: int) -> Optional[int]: + col_cells = [ + board[n][column] + for n in range(0, len(board)) + ] + + last_empty = None + for idx, cell in enumerate(col_cells): + if cell is None: + last_empty = idx + + return last_empty + + +def show_board(board): + for row_idx, row in enumerate(board, start=1): + print("| ", end='') + for col_idx, cell in enumerate(row, start=1): + empty_text = f"({row_idx}, {col_idx})" + symbol = f' {cell} ' if cell is not None else empty_text + print(symbol, end=" | ") + print() + + +def announce_turn(player): + print() + print(f"It's {player}'s turn. Here's the board:") + print() + + +def find_winner(board): + sequences = get_winning_sequences(board) + + for cells in sequences: + symbol1 = cells[0] + if symbol1 and all(symbol1 == cell for cell in cells): + return True + + return False + + +def get_winning_sequences(board): + sequences = [] + + # Win by rows. + rows = board + for row in rows: + # Go through each row and get any consecutive sequence of 4 cells + fours_across = find_sequences_of_four_cells_in_a_row(row) + sequences.extend(fours_across) + + # Win by columns + for col_idx in range(0, 7): + col = [ + board[0][col_idx], + board[1][col_idx], + board[2][col_idx], + board[3][col_idx], + board[4][col_idx], + board[5][col_idx], + ] + # Go through each column and get any consecutive sequence of 4 cells + fours_down = find_sequences_of_four_cells_in_a_row(col) + sequences.extend(fours_down) + + # Win by diagonals + # In Tic-Tac-Toe, we just had two diagonals and they were easy to compute. + # It's pretty simple here too, but more, so just a bit more to type out + # for the possible options. + # + # To help visualize this, here is the board with indices: (row,col) + # [ + # ['(0,0)', '(0,1)', '(0,2)', '(0,3)', '(0,4)', '(0,5)', '(0,6)'], + # ['(1,0)', '(1,1)', '(1,2)', '(1,3)', '(1,4)', '(1,5)', '(1,6)'], + # ['(2,0)', '(2,1)', '(2,2)', '(2,3)', '(2,4)', '(2,5)', '(2,6)'], + # ['(3,0)', '(3,1)', '(3,2)', '(3,3)', '(3,4)', '(3,5)', '(3,6)'], + # ['(4,0)', '(4,1)', '(4,2)', '(4,3)', '(4,4)', '(4,5)', '(4,6)'], + # ['(5,0)', '(5,1)', '(5,2)', '(5,3)', '(5,4)', '(5,5)', '(5,6)'], + # ] + # + # I'm sure there a clever double for i in range(0, rows) & for j in range(0, cols) + # solution. But I'm afraid it will be too confusing for lots of us. + # So I'll just do it long-hand down here. + diagonals = [ + + # Down to the right diagonals + [board[5][0]], # Not really used, too short, but here for building the pattern + [board[4][0], board[5][1]], # Not really used, too short, but here for building the pattern + [board[3][0], board[4][1], board[5][2]], # Not really used, too short, but here for building the pattern + [board[2][0], board[3][1], board[4][2], board[5][3]], + [board[1][0], board[2][1], board[3][2], board[4][3], board[5][4]], + [board[0][0], board[1][1], board[2][2], board[3][3], board[4][4], board[5][5]], + [board[0][1], board[1][2], board[2][3], board[3][4], board[4][5], board[5][6]], + [board[0][2], board[1][3], board[2][4], board[3][5], board[4][6]], + [board[0][3], board[1][4], board[2][5], board[3][6]], + [board[0][4], board[1][5], board[2][6]], # Not really used, too short, but here for building the pattern + [board[0][5], board[1][6]], # Not really used, too short, but here for building the pattern + [board[0][6]], # Not really used, too short, but here for building the pattern + + # Down to the left diagonals + [board[0][0]], # Not really used, too short, but here for building the pattern + [board[0][1], board[1][0]], # Not really used, too short, but here for building the pattern + [board[2][0], board[1][1], board[0][2]], # Not really used, too short, but here for building the pattern + [board[0][3], board[1][2], board[2][1], board[3][0]], + [board[0][4], board[1][3], board[2][2], board[3][1], board[4][0]], + [board[0][5], board[1][4], board[2][3], board[3][2], board[4][1], board[5][0]], + [board[0][6], board[1][5], board[2][4], board[3][3], board[4][2], board[5][1]], + [board[1][6], board[2][5], board[3][4], board[4][3], board[5][2]], + [board[2][6], board[3][5], board[4][4], board[5][3]], + [board[3][6], board[4][5], board[5][4]], # Not really used, too short, but here for building the pattern + [board[4][6], board[5][5]], # Not really used, too short, but here for building the pattern + [board[5][6]], # Not really used, too short, but here for building the pattern + ] + for diag in diagonals: + fours_diagonals = find_sequences_of_four_cells_in_a_row(diag) + sequences.extend(fours_diagonals) + + return sequences + + +def find_sequences_of_four_cells_in_a_row(cells: List[str]) -> List[List[str]]: + sequences = [] + for n in range(0, len(cells) - 3): + candidate = cells[n:n + 4] + if len(candidate) == 4: + sequences.append(candidate) + + return sequences + + +if __name__ == '__main__': + main() diff --git a/solutions/ch-08-problem-solving/readme.md b/solutions/ch-08-problem-solving/readme.md new file mode 100644 index 0000000..5a3c01c --- /dev/null +++ b/solutions/ch-08-problem-solving/readme.md @@ -0,0 +1,70 @@ +## Solution for Connect4 + +This one is VERY similar to TIC-TAC-TOE. There are three fundamental changes, other +than this, it's the same code for both games. + +### Change 1: Board data structure. + +We are using this data structure. Tic-Tac-Toe was 3x3. Connect4 is 7x6 (7 columns, 6 rows): + +```python +# Board is a list of rows +# Rows are a list of cells +board = [ + # 6 rows + [None, None, None, None, None, None, None], # 7 columns per row + [None, None, None, None, None, None, None], # 7 columns per row + [None, None, None, None, None, None, None], # 7 columns per row + [None, None, None, None, None, None, None], # 7 columns per row + [None, None, None, None, None, None, None], # 7 columns per row + [None, None, None, None, None, None, None], # 7 columns per row +] +``` + +### Change 2: You pick a column, drop the disk, it falls down + +The second major change is how you pick where to play. + +In Tic-Tac-Toe, it's choose the square. In Connect 4, it's choose the column, +drop the disk it fall as far as it can. We rewrote `choose_location()` accordingly. + +### Change 3: Finding lists of 4 in the rows, cols, and diagonals + +The final major change is finding wins. In Tic-Tac-Toe, we wrote a function called: + +```python +def get_winning_sequences(board): + ... +``` + +This function would turn rows, columns, and diagonals into just straight lists. Then +they are super simple to check. Are all of them one of the same kind and not empty? +For example, is a diagonal all X's? Then X's win. + +It's identical in connect 4. But finding these are a bit of a pain. You don't have to have +the whole row, or column, or diagonal the same to win. You just need 4 in a row of these. + +So we wrote a function called `find_sequences_of_four_cells_in_a_row(cells)`. It +takes a series of items, say 7 items, then returns all consecutive possibilities of 4. + +For example: + +```python +cells = [1, 2, 3, 4, 5, 6] +fours = find_sequences_of_four_cells_in_a_row(cells) +# fours = [ +# [1, 2, 3, 4], +# [2, 3, 4, 5], +# [3, 4, 5, 6] +#] +``` + +Then we can just return these out of `get_winning_sequences()` and it'll see if any are winners. +At that point, it's all the same as Tic-Tac-Toe. If they are all one type, that type is the winner. + +Finding the sequences is a bit more complex, but it's the same idea. Find all possible diagonals, +rows, and columns, use `find_sequences_of_four_cells_in_a_row()` to find the lists of 4 and check them. + +That's the game. + +See [connect4.py](./connect4.py) diff --git a/solutions/ch-09-working-with-files/connect4_files.py b/solutions/ch-09-working-with-files/connect4_files.py new file mode 100644 index 0000000..7faf5f9 --- /dev/null +++ b/solutions/ch-09-working-with-files/connect4_files.py @@ -0,0 +1,257 @@ +import json +import os +import random +from typing import List, Optional + + +def main(): + print() + print("Welcome to TIC TAC TOE from TALK PYTHON") + print() + show_leaderboard() + + # CREATE THE BOARD: + # Board is a list of rows + # Rows are a list of cells + board = [ + # 6 rows + [None, None, None, None, None, None, None], # 7 columns per row + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + ] + + # CHOOSE INITIAL PLAYER + # We could use X and O, but let's liven it + # up with some emoji from: https://emojipedia.org/baseball/ + symbols = ["🏀", "🥎"] + + active_player_index = 0 + player_name = input("What is your name player 1? ") + players = [player_name.capitalize(), "Computer"] + print(f"Welcome {players[0]}") + print(f"Your symbol will be {symbols[0]}.") + print(f"{players[1]}, will be {symbols[1]}.") + symbol = symbols[active_player_index] + player = players[active_player_index] + + # UNTIL SOMEONE WINS + while not find_winner(board): + # SHOW THE BOARD + player = players[active_player_index] + symbol = symbols[active_player_index] + + announce_turn(player) + show_board(board) + if not choose_location(board, symbol, active_player_index == 1): + print("That isn't an option, try again.") + continue + + # TOGGLE ACTIVE PLAYER + active_player_index = (active_player_index + 1) % len(players) + + print() + print(f"GAME OVER! {player} ({symbol}) has won with the board: ") + show_board(board) + record_win(player) + print() + + +def choose_location(board, symbol, is_computer): + if not is_computer: + column = int(input("Choose which column: ")) + else: + column = random.randint(1, len(board[0])) + print(f"Computer chooses column {column}") + + column -= 1 + if column < 0 or column >= len(board[0]): + return False + + row = find_bottom_row(board, column) + if row is None: + return False + + cell = board[row][column] + if cell is not None: + return False + + board[row][column] = symbol + return True + + +def find_bottom_row(board: List[List[str]], column: int) -> Optional[int]: + col_cells = [ + board[n][column] + for n in range(0, len(board)) + ] + + last_empty = None + for idx, cell in enumerate(col_cells): + if cell is None: + last_empty = idx + + return last_empty + + +def show_board(board): + for row_idx, row in enumerate(board, start=1): + print("| ", end='') + for col_idx, cell in enumerate(row, start=1): + empty_text = f"({row_idx}, {col_idx})" + symbol = f' {cell} ' if cell is not None else empty_text + print(symbol, end=" | ") + print() + + +def announce_turn(player): + print() + print(f"It's {player}'s turn. Here's the board:") + print() + + +def find_winner(board): + sequences = get_winning_sequences(board) + + for cells in sequences: + symbol1 = cells[0] + if symbol1 and all(symbol1 == cell for cell in cells): + return True + + return False + + +def get_winning_sequences(board): + sequences = [] + + # Win by rows. + rows = board + for row in rows: + # Go through each row and get any consecutive sequence of 4 cells + fours_across = find_sequences_of_four_cells_in_a_row(row) + sequences.extend(fours_across) + + # Win by columns + for col_idx in range(0, 7): + col = [ + board[0][col_idx], + board[1][col_idx], + board[2][col_idx], + board[3][col_idx], + board[4][col_idx], + board[5][col_idx], + ] + # Go through each column and get any consecutive sequence of 4 cells + fours_down = find_sequences_of_four_cells_in_a_row(col) + sequences.extend(fours_down) + + # Win by diagonals + # In Tic-Tac-Toe, we just had two diagonals and they were easy to compute. + # It's pretty simple here too, but more, so just a bit more to type out + # for the possible options. + # + # To help visualize this, here is the board with indices: (row,col) + # [ + # ['(0,0)', '(0,1)', '(0,2)', '(0,3)', '(0,4)', '(0,5)', '(0,6)'], + # ['(1,0)', '(1,1)', '(1,2)', '(1,3)', '(1,4)', '(1,5)', '(1,6)'], + # ['(2,0)', '(2,1)', '(2,2)', '(2,3)', '(2,4)', '(2,5)', '(2,6)'], + # ['(3,0)', '(3,1)', '(3,2)', '(3,3)', '(3,4)', '(3,5)', '(3,6)'], + # ['(4,0)', '(4,1)', '(4,2)', '(4,3)', '(4,4)', '(4,5)', '(4,6)'], + # ['(5,0)', '(5,1)', '(5,2)', '(5,3)', '(5,4)', '(5,5)', '(5,6)'], + # ] + # + # I'm sure there a clever double for i in range(0, rows) & for j in range(0, cols) + # solution. But I'm afraid it will be too confusing for lots of us. + # So I'll just do it long-hand down here. + diagonals = [ + + # Down to the right diagonals + [board[5][0]], # Not really used, too short, but here for building the pattern + [board[4][0], board[5][1]], # Not really used, too short, but here for building the pattern + [board[3][0], board[4][1], board[5][2]], # Not really used, too short, but here for building the pattern + [board[2][0], board[3][1], board[4][2], board[5][3]], + [board[1][0], board[2][1], board[3][2], board[4][3], board[5][4]], + [board[0][0], board[1][1], board[2][2], board[3][3], board[4][4], board[5][5]], + [board[0][1], board[1][2], board[2][3], board[3][4], board[4][5], board[5][6]], + [board[0][2], board[1][3], board[2][4], board[3][5], board[4][6]], + [board[0][3], board[1][4], board[2][5], board[3][6]], + [board[0][4], board[1][5], board[2][6]], # Not really used, too short, but here for building the pattern + [board[0][5], board[1][6]], # Not really used, too short, but here for building the pattern + [board[0][6]], # Not really used, too short, but here for building the pattern + + # Down to the left diagonals + [board[0][0]], # Not really used, too short, but here for building the pattern + [board[0][1], board[1][0]], # Not really used, too short, but here for building the pattern + [board[2][0], board[1][1], board[0][2]], # Not really used, too short, but here for building the pattern + [board[0][3], board[1][2], board[2][1], board[3][0]], + [board[0][4], board[1][3], board[2][2], board[3][1], board[4][0]], + [board[0][5], board[1][4], board[2][3], board[3][2], board[4][1], board[5][0]], + [board[0][6], board[1][5], board[2][4], board[3][3], board[4][2], board[5][1]], + [board[1][6], board[2][5], board[3][4], board[4][3], board[5][2]], + [board[2][6], board[3][5], board[4][4], board[5][3]], + [board[3][6], board[4][5], board[5][4]], # Not really used, too short, but here for building the pattern + [board[4][6], board[5][5]], # Not really used, too short, but here for building the pattern + [board[5][6]], # Not really used, too short, but here for building the pattern + ] + for diag in diagonals: + fours_diagonals = find_sequences_of_four_cells_in_a_row(diag) + sequences.extend(fours_diagonals) + + return sequences + + +def find_sequences_of_four_cells_in_a_row(cells: List[str]) -> List[List[str]]: + sequences = [] + for n in range(0, len(cells) - 3): + candidate = cells[n:n + 4] + if len(candidate) == 4: + sequences.append(candidate) + + return sequences + + +def show_leaderboard(): + leaders = load_leaders() + + sorted_leaders = list(leaders.items()) + sorted_leaders.sort(key=lambda l: l[1], reverse=True) + + print() + print("---------------------------") + print("LEADERS:") + for name, wins in sorted_leaders[0:5]: + print(f"{wins:,} -- {name}") + print("---------------------------") + print() + + +def load_leaders(): + directory = os.path.dirname(__file__) + filename = os.path.join(directory, 'leaderboard.json') + + if not os.path.exists(filename): + return {} + + with open(filename, 'r', encoding='utf-8') as fin: + return json.load(fin) + + +def record_win(winner_name): + leaders = load_leaders() + + if winner_name in leaders: + leaders[winner_name] += 1 + else: + leaders[winner_name] = 1 + + directory = os.path.dirname(__file__) + filename = os.path.join(directory, 'leaderboard.json') + + with open(filename, 'w', encoding='utf-8') as fout: + json.dump(leaders, fout) + + +if __name__ == '__main__': + main() diff --git a/solutions/ch-10-external-libraries/connect4_color.py b/solutions/ch-10-external-libraries/connect4_color.py new file mode 100644 index 0000000..3b411a0 --- /dev/null +++ b/solutions/ch-10-external-libraries/connect4_color.py @@ -0,0 +1,259 @@ +import json +import os +import random +from typing import List, Optional + +from colorama import Fore + + +def main(): + print(Fore.WHITE) + print(Fore.LIGHTGREEN_EX + "Welcome to TIC TAC TOE from TALK PYTHON" + Fore.WHITE) + print() + show_leaderboard() + + # CREATE THE BOARD: + # Board is a list of rows + # Rows are a list of cells + board = [ + # 6 rows + [None, None, None, None, None, None, None], # 7 columns per row + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + [None, None, None, None, None, None, None], + ] + + # CHOOSE INITIAL PLAYER + # We could use X and O, but let's liven it + # up with some emoji from: https://emojipedia.org/baseball/ + symbols = ["🏀", "🥎"] + + active_player_index = 0 + player_name = input(Fore.YELLOW + "What is your name player 1? " + Fore.WHITE) + players = [player_name.capitalize(), "Computer"] + print(f"Welcome {players[0]}" + Fore.WHITE) + print(f"Your symbol will be {symbols[0]}." + Fore.WHITE) + print(f"{players[1]}, will be {symbols[1]}." + Fore.WHITE) + symbol = symbols[active_player_index] + player = players[active_player_index] + + # UNTIL SOMEONE WINS + while not find_winner(board): + # SHOW THE BOARD + player = players[active_player_index] + symbol = symbols[active_player_index] + + announce_turn(player) + show_board(board) + if not choose_location(board, symbol, active_player_index == 1): + print(Fore.LIGHTRED_EX + "That isn't an option, try again." + Fore.WHITE) + continue + + # TOGGLE ACTIVE PLAYER + active_player_index = (active_player_index + 1) % len(players) + + print() + print(Fore.LIGHTGREEN_EX + f"GAME OVER! {player} ({symbol}) has won with the board: " + Fore.WHITE) + show_board(board) + record_win(player) + print() + + +def choose_location(board, symbol, is_computer): + if not is_computer: + column = int(input(Fore.YELLOW + "Choose which column: " + Fore.WHITE)) + else: + column = random.randint(1, len(board[0])) + print(Fore.YELLOW + f"Computer chooses column {column}" + Fore.WHITE) + + column -= 1 + if column < 0 or column >= len(board[0]): + return False + + row = find_bottom_row(board, column) + if row is None: + return False + + cell = board[row][column] + if cell is not None: + return False + + board[row][column] = symbol + return True + + +def find_bottom_row(board: List[List[str]], column: int) -> Optional[int]: + col_cells = [ + board[n][column] + for n in range(0, len(board)) + ] + + last_empty = None + for idx, cell in enumerate(col_cells): + if cell is None: + last_empty = idx + + return last_empty + + +def show_board(board): + for row_idx, row in enumerate(board, start=1): + print("| ", end='') + for col_idx, cell in enumerate(row, start=1): + empty_text = f"({row_idx}, {col_idx})" + symbol = f' {cell} ' if cell is not None else empty_text + print(symbol, end=" | ") + print() + + +def announce_turn(player): + print() + print(Fore.LIGHTCYAN_EX + f"It's {player}'s turn. Here's the board:" + Fore.WHITE) + print() + + +def find_winner(board): + sequences = get_winning_sequences(board) + + for cells in sequences: + symbol1 = cells[0] + if symbol1 and all(symbol1 == cell for cell in cells): + return True + + return False + + +def get_winning_sequences(board): + sequences = [] + + # Win by rows. + rows = board + for row in rows: + # Go through each row and get any consecutive sequence of 4 cells + fours_across = find_sequences_of_four_cells_in_a_row(row) + sequences.extend(fours_across) + + # Win by columns + for col_idx in range(0, 7): + col = [ + board[0][col_idx], + board[1][col_idx], + board[2][col_idx], + board[3][col_idx], + board[4][col_idx], + board[5][col_idx], + ] + # Go through each column and get any consecutive sequence of 4 cells + fours_down = find_sequences_of_four_cells_in_a_row(col) + sequences.extend(fours_down) + + # Win by diagonals + # In Tic-Tac-Toe, we just had two diagonals and they were easy to compute. + # It's pretty simple here too, but more, so just a bit more to type out + # for the possible options. + # + # To help visualize this, here is the board with indices: (row,col) + # [ + # ['(0,0)', '(0,1)', '(0,2)', '(0,3)', '(0,4)', '(0,5)', '(0,6)'], + # ['(1,0)', '(1,1)', '(1,2)', '(1,3)', '(1,4)', '(1,5)', '(1,6)'], + # ['(2,0)', '(2,1)', '(2,2)', '(2,3)', '(2,4)', '(2,5)', '(2,6)'], + # ['(3,0)', '(3,1)', '(3,2)', '(3,3)', '(3,4)', '(3,5)', '(3,6)'], + # ['(4,0)', '(4,1)', '(4,2)', '(4,3)', '(4,4)', '(4,5)', '(4,6)'], + # ['(5,0)', '(5,1)', '(5,2)', '(5,3)', '(5,4)', '(5,5)', '(5,6)'], + # ] + # + # I'm sure there a clever double for i in range(0, rows) & for j in range(0, cols) + # solution. But I'm afraid it will be too confusing for lots of us. + # So I'll just do it long-hand down here. + diagonals = [ + + # Down to the right diagonals + [board[5][0]], # Not really used, too short, but here for building the pattern + [board[4][0], board[5][1]], # Not really used, too short, but here for building the pattern + [board[3][0], board[4][1], board[5][2]], # Not really used, too short, but here for building the pattern + [board[2][0], board[3][1], board[4][2], board[5][3]], + [board[1][0], board[2][1], board[3][2], board[4][3], board[5][4]], + [board[0][0], board[1][1], board[2][2], board[3][3], board[4][4], board[5][5]], + [board[0][1], board[1][2], board[2][3], board[3][4], board[4][5], board[5][6]], + [board[0][2], board[1][3], board[2][4], board[3][5], board[4][6]], + [board[0][3], board[1][4], board[2][5], board[3][6]], + [board[0][4], board[1][5], board[2][6]], # Not really used, too short, but here for building the pattern + [board[0][5], board[1][6]], # Not really used, too short, but here for building the pattern + [board[0][6]], # Not really used, too short, but here for building the pattern + + # Down to the left diagonals + [board[0][0]], # Not really used, too short, but here for building the pattern + [board[0][1], board[1][0]], # Not really used, too short, but here for building the pattern + [board[2][0], board[1][1], board[0][2]], # Not really used, too short, but here for building the pattern + [board[0][3], board[1][2], board[2][1], board[3][0]], + [board[0][4], board[1][3], board[2][2], board[3][1], board[4][0]], + [board[0][5], board[1][4], board[2][3], board[3][2], board[4][1], board[5][0]], + [board[0][6], board[1][5], board[2][4], board[3][3], board[4][2], board[5][1]], + [board[1][6], board[2][5], board[3][4], board[4][3], board[5][2]], + [board[2][6], board[3][5], board[4][4], board[5][3]], + [board[3][6], board[4][5], board[5][4]], # Not really used, too short, but here for building the pattern + [board[4][6], board[5][5]], # Not really used, too short, but here for building the pattern + [board[5][6]], # Not really used, too short, but here for building the pattern + ] + for diag in diagonals: + fours_diagonals = find_sequences_of_four_cells_in_a_row(diag) + sequences.extend(fours_diagonals) + + return sequences + + +def find_sequences_of_four_cells_in_a_row(cells: List[str]) -> List[List[str]]: + sequences = [] + for n in range(0, len(cells) - 3): + candidate = cells[n:n + 4] + if len(candidate) == 4: + sequences.append(candidate) + + return sequences + + +def show_leaderboard(): + leaders = load_leaders() + + sorted_leaders = list(leaders.items()) + sorted_leaders.sort(key=lambda l: l[1], reverse=True) + + print() + print(Fore.LIGHTBLUE_EX + "---------------------------" + Fore.WHITE) + print(Fore.LIGHTBLUE_EX + "LEADERS:" + Fore.WHITE) + for name, wins in sorted_leaders[0:5]: + print(Fore.LIGHTBLUE_EX + f"{wins:,} -- {name}" + Fore.WHITE) + print(Fore.LIGHTBLUE_EX + "---------------------------" + Fore.WHITE) + print() + + +def load_leaders(): + directory = os.path.dirname(__file__) + filename = os.path.join(directory, 'leaderboard.json') + + if not os.path.exists(filename): + return {} + + with open(filename, 'r', encoding='utf-8') as fin: + return json.load(fin) + + +def record_win(winner_name): + leaders = load_leaders() + + if winner_name in leaders: + leaders[winner_name] += 1 + else: + leaders[winner_name] = 1 + + directory = os.path.dirname(__file__) + filename = os.path.join(directory, 'leaderboard.json') + + with open(filename, 'w', encoding='utf-8') as fout: + json.dump(leaders, fout) + + +if __name__ == '__main__': + main() diff --git a/solutions/ch-10-external-libraries/requirements.txt b/solutions/ch-10-external-libraries/requirements.txt new file mode 100644 index 0000000..3fcfb51 --- /dev/null +++ b/solutions/ch-10-external-libraries/requirements.txt @@ -0,0 +1 @@ +colorama diff --git a/solutions/ch-11-error-handling/readme.md b/solutions/ch-11-error-handling/readme.md new file mode 100644 index 0000000..63a7dd5 --- /dev/null +++ b/solutions/ch-11-error-handling/readme.md @@ -0,0 +1,42 @@ +## Solution for error handling in tic-tac-toe + +The only part of this first pass on the game that could fail is choosing the location. + +Did you notice that when it asks for a number, if you don't enter anything it crashes hard? + +How about if you state too much? It asks for row and you answer 2,3 (row and column)? Boom again. + +That's what we fixed here with `try/except`: + +```python +def choose_location(board, symbol): + try: + row = int(input("Choose which row: ")) + + row -= 1 + if row < 0 or row >= len(board): + return False + + column = int(input("Choose which column: ")) + column -= 1 + if column < 0 or column >= len(board[0]): + return False + + cell = board[row][column] + if cell is not None: + return False + + board[row][column] = symbol + return True + except ValueError as ve: + print(f"Error: Cannot convert input to a number.") + return False + except Exception: + # Not sure what else happened here, but didn't work. + return False +``` + +For a more advanced version, you could edit the tic-tac-toe from files and make sure we have +permissions to save to the files and that they are in a correct format for `json` to read and so on. + +See [tictactoe_errors_handled.py](./tictactoe_errors_handled.py) diff --git a/solutions/ch-11-error-handling/tictactoe_errors_handled.py b/solutions/ch-11-error-handling/tictactoe_errors_handled.py new file mode 100644 index 0000000..3291602 --- /dev/null +++ b/solutions/ch-11-error-handling/tictactoe_errors_handled.py @@ -0,0 +1,122 @@ +def main(): + print() + print("Welcome to TIC TAC TOE from TALK PYTHON") + print(" Safe edition!") + print() + + # CREATE THE BOARD: + # Board is a list of rows + # Rows are a list of cells + board = [ + [None, None, None], + [None, None, None], + [None, None, None], + ] + + # CHOOSE INITIAL PLAYER + active_player_index = 0 + players = ["Michael", "Computer"] + symbols = ["X", "O"] + player = players[active_player_index] + + # UNTIL SOMEONE WINS + while not find_winner(board): + # SHOW THE BOARD + player = players[active_player_index] + symbol = symbols[active_player_index] + + announce_turn(player) + show_board(board) + if not choose_location(board, symbol): + print("That isn't an option, try again.") + continue + + # TOGGLE ACTIVE PLAYER + active_player_index = (active_player_index + 1) % len(players) + + print() + print(f"GAME OVER! {player} has won with the board: ") + show_board(board) + print() + + +def choose_location(board, symbol): + try: + row = int(input("Choose which row: ")) + + row -= 1 + if row < 0 or row >= len(board): + return False + + column = int(input("Choose which column: ")) + column -= 1 + if column < 0 or column >= len(board[0]): + return False + + cell = board[row][column] + if cell is not None: + return False + + board[row][column] = symbol + return True + except ValueError as ve: + print(f"Error: Cannot convert input to a number.") + return False + except Exception: + # Not sure what else happened here, but didn't work. + return False + +def show_board(board): + for row in board: + print("| ", end='') + for cell in row: + symbol = cell if cell is not None else "_" + print(symbol, end=" | ") + print() + + +def announce_turn(player): + print() + print(f"It's {player}'s turn. Here's the board:") + print() + + +def find_winner(board): + sequences = get_winning_sequences(board) + + for cells in sequences: + symbol1 = cells[0] + if symbol1 and all(symbol1 == cell for cell in cells): + return True + + return False + + +def get_winning_sequences(board): + sequences = [] + + # Win by rows + rows = board + sequences.extend(rows) + + # Win by columns + for col_idx in range(0, 3): + col = [ + board[0][col_idx], + board[1][col_idx], + board[2][col_idx], + ] + sequences.append(col) + + # Win by diagonals + diagonals = [ + [board[0][0], board[1][1], board[2][2]], + [board[0][2], board[1][1], board[2][0]], + ] + sequences.extend(diagonals) + + return sequences + + +if __name__ == '__main__': + main() diff --git a/solutions/readme.md b/solutions/readme.md index 6245c59..2e5e7ea 100644 --- a/solutions/readme.md +++ b/solutions/readme.md @@ -1,15 +1,7 @@ -# Solutions: Proceed with caution. +# Solutions: [Enabled] We've decided to include the solutions to the practice exercises. Initially, the intention was to omit them. It can be frustrating. However, **it's working your way through that actually drives the learning**. It feels good to see the solution, but to make it really click, you have to make it your own and work your way through it. -That said, a large number of students have requested that I add these and so here they are. - -But, I want to you be sure you want to go down this path. Ideally, this is a *check my solution* type of thing. So we have made the steps a little bit manual. To see the solution, you have to check out the `with_solutions` branch. They do no appear in the default `master` branch (this one). - -You can do this on GitHub by just using the branch dropdown like this: - -![](./resources/branches.png) - -Then you will see a solutions folder below this one. +Ideally, this is a *check my solution* type of thing. You should now see the solutions by chapter above (under construction at the moment.) Best of luck on the course, Michael! \ No newline at end of file diff --git a/solutions/resources/branches.png b/solutions/resources/branches.png deleted file mode 100644 index e7bdf0f..0000000 Binary files a/solutions/resources/branches.png and /dev/null differ