Last active
December 24, 2020 10:02
-
-
Save GreatBahram/2d94cea3462b35d6b20c4461241aa078 to your computer and use it in GitHub Desktop.
Beyond the basic stuff with Python - Hanoi
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Four-in-a-Row, by Al Sweigart al@inventwithpython.com A tile-dropping game to get four-in-a-row, similar to Connect Four.""" | |
import sys | |
from enum import Enum | |
from textwrap import dedent | |
class Cell(str, Enum): | |
EMPTY = "." | |
PLAYER_X = "X" | |
PLAYER_O = "O" | |
# Mine | |
# Constants used for displaying the board: | |
EMPTY_SPACE = "." | |
# A period is easier to count than a space. | |
PLAYER_X = "X" | |
PLAYER_O = "O" | |
# Note: Update BOARD_TEMPLATE & COLUMN_LABELS if BOARD_WIDTH is changed. | |
BOARD_WIDTH = 7 | |
BOARD_HEIGHT = 6 | |
COLUMN_LABELS = ("1", "2", "3", "4", "5", "6", "7") | |
assert len(COLUMN_LABELS) == BOARD_WIDTH | |
# The template string for displaying the board: | |
BOARD_TEMPLATE = dedent( | |
"""\ | |
1234567 | |
+-------+ | |
|{}{}{}{}{}{}{}| | |
|{}{}{}{}{}{}{}| | |
|{}{}{}{}{}{}{}| | |
|{}{}{}{}{}{}{}| | |
|{}{}{}{}{}{}{}| | |
|{}{}{}{}{}{}{}| | |
+-------+""" | |
) | |
def main(): | |
"""Runs a single game of Four-in-a-Row.""" | |
print( | |
dedent( | |
"""\ | |
Four-in-a-Row, by Al Sweigart al@inventwithpython.com Two players | |
take turns dropping tiles into one of seven columns, trying to make | |
Four-in-a-Row horizontally, vertically, or diagonally.""" | |
) | |
) | |
# Set up a new game: | |
gameBoard = getNewBoard() | |
playerTurn = PLAYER_X | |
while True: | |
# Run a player's turn. | |
# Display the board and get player's move: | |
displayBoard(gameBoard) | |
playerMove = getPlayerMove(playerTurn, gameBoard) | |
gameBoard[playerMove] = playerTurn | |
# Check for a win or tie: | |
if isWinner(playerTurn, gameBoard): | |
displayBoard(gameBoard) | |
# Display the board one last time. | |
print("Player {} has won!".format(playerTurn)) | |
sys.exit() | |
elif isFull(gameBoard): | |
displayBoard(gameBoard) | |
# Display the board one last time. | |
print("There is a tie!") | |
sys.exit() | |
# Switch turns to other player: | |
if playerTurn == PLAYER_X: | |
playerTurn = PLAYER_O | |
elif playerTurn == PLAYER_O: | |
playerTurn = PLAYER_X | |
def getNewBoard(): | |
"""Returns a dictionary that represents a Four-in-a-Row board. | |
The keys are (columnIndex, rowIndex) tuples of two integers, and the | |
values are one of the "X", "O" or "." (empty space) strings.""" | |
board = {} | |
for rowIndex in range(BOARD_HEIGHT): | |
for columnIndex in range(BOARD_WIDTH): | |
board[(columnIndex, rowIndex)] = EMPTY_SPACE | |
return board | |
def displayBoard(board): | |
"""Display the board and its tiles on the screen.""" | |
# Prepare a list to pass to the format() string method for the board | |
# template. The list holds all of the board's tiles (and empty | |
# spaces) going left to right, top to bottom: | |
tileChars = [] | |
for rowIndex in range(BOARD_HEIGHT): | |
for columnIndex in range(BOARD_WIDTH): | |
tileChars.append(board[(columnIndex, rowIndex)]) | |
# Display the board: | |
print(BOARD_TEMPLATE.format(*tileChars)) | |
def getPlayerMove(playerTile, board): | |
"""Let a player select a column on the board to drop a | |
tile into. Returns a tuple of the (column, row) that the tile falls | |
into.""" | |
while True: | |
# Keep asking player until they enter a valid move. | |
print(f"Player {playerTile}, enter 1 to {BOARD_WIDTH} or QUIT:") | |
response = input("> ").upper().strip() | |
if response == "QUIT": | |
print("Thanks for playing!") | |
sys.exit() | |
if response not in COLUMN_LABELS: | |
print(f"Enter a number from 1 to {BOARD_WIDTH}.") | |
continue # Ask player again for their move. | |
columnIndex = int(response) - 1 # -1 for 0-based column indexes. | |
# If the column is full, ask for a move again: | |
if board[(columnIndex, 0)] != EMPTY_SPACE: | |
print("That column is full, select another one.") | |
continue | |
# Ask player again for their move. | |
# Starting from the bottom, find the first empty space. | |
for rowIndex in range(BOARD_HEIGHT - 1, -1, -1): | |
if board[(columnIndex, rowIndex)] == EMPTY_SPACE: | |
return (columnIndex, rowIndex) | |
def isFull(board): | |
"""Returns True if the `board` has no empty spaces, otherwise returns False.""" | |
for rowIndex in range(BOARD_HEIGHT): | |
for columnIndex in range(BOARD_WIDTH): | |
if board[(columnIndex, rowIndex)] == EMPTY_SPACE: | |
return False # Found an empty space, so return False. | |
return True # All spaces are full | |
def isWinner(playerTile, board): | |
"""Returns True if `playerTile` has four tiles in a rowon `board`, otherwise returns False.""" | |
# Go through the entire board, checking for four-in-a- row: | |
for columnIndex in range(BOARD_WIDTH - 3): | |
for rowIndex in range(BOARD_HEIGHT): | |
# Check for four-in-a-row going across to the right: | |
tile1 = board[(columnIndex, rowIndex)] | |
tile2 = board[(columnIndex + 1, rowIndex)] | |
tile3 = board[(columnIndex + 2, rowIndex)] | |
tile4 = board[(columnIndex + 3, rowIndex)] | |
if tile1 == tile2 == tile3 == tile4 == playerTile: | |
return True | |
for columnIndex in range(BOARD_WIDTH): | |
for rowIndex in range(BOARD_HEIGHT - 3): | |
# Check for four-in-a-row going down: | |
tile1 = board[(columnIndex, rowIndex)] | |
tile2 = board[(columnIndex, rowIndex + 1)] | |
tile3 = board[(columnIndex, rowIndex + 2)] | |
tile4 = board[(columnIndex, rowIndex + 3)] | |
if tile1 == tile2 == tile3 == tile4 == playerTile: | |
return True | |
for columnIndex in range(BOARD_WIDTH - 3): | |
for rowIndex in range(BOARD_HEIGHT - 3): | |
# Check for four-in-a-row going right-down diagonal: | |
tile1 = board[(columnIndex, rowIndex)] | |
tile2 = board[(columnIndex + 1, rowIndex + 1)] | |
tile3 = board[(columnIndex + 2, rowIndex + 2)] | |
tile4 = board[(columnIndex + 3, rowIndex + 3)] | |
if tile1 == tile2 == tile3 == tile4 == playerTile: | |
return True | |
# Check for four-in-a-row going left-down diagonal: | |
tile1 = board[(columnIndex + 3, rowIndex)] | |
tile2 = board[(columnIndex + 2, rowIndex + 1)] | |
tile3 = board[(columnIndex + 1, rowIndex + 2)] | |
tile4 = board[(columnIndex, rowIndex + 3)] | |
if tile1 == tile2 == tile3 == tile4 == playerTile: | |
return True | |
return False | |
# If this program was run (instead of imported), run the game: | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""THE TOWER OF HANOI, by Al Sweigart al@inventwithpython.com A stack-moving puzzle game.""" | |
import copy | |
import sys | |
from textwrap import dedent | |
from typing import Dict, List, Final | |
TOTAL_DISKS: Final = 5 # More disks means a more difficult puzzle. | |
SOLVED_TOWER: Final = list(range(TOTAL_DISKS, 0, -1)) | |
def main(): | |
"""Runs a single game of The Tower of Hanoi.""" | |
print( | |
dedent( | |
"""\ | |
THE TOWER OF HANOI, by Al Sweigart al@inventwithpython.com | |
Move the tower of disks, one disk at a time, to another | |
tower. Larger | |
disks cannot rest on top of a smaller disk. | |
More info at https://en.wikipedia.org/wiki/Tower_of_Hanoi | |
""" | |
) | |
) | |
"""The towers dictionary has keys "A", "B", and "C" and | |
values that are lists representing a tower of disks. The list | |
contains integers representing disks of different sizes, and the | |
start of the list is the bottom of the tower. For a game with 5 | |
disks, the list [5, 4, 3, 2, 1] represents a completed tower. | |
The blank list [] represents a tower of no disks. The list [1, 3] | |
has a larger disk on top of a smaller disk and is an invalid configuration. | |
The list [3, 1] is allowed since smaller disks can go on top of larger ones.""" | |
# Start with all disks on tower A: | |
towers: Dict[str, List[int]] = {"A": copy.copy(SOLVED_TOWER), "B": [], "C": []} | |
# Count the number of moves | |
move_nums: int = 0 | |
while True: # Run a single turn on each iteration of this loop. | |
# Display the towers and disks: | |
displayTowers(towers) | |
# Ask the user for a move: | |
fromTower, toTower = getPlayerMove(towers) | |
# Move the top disk from fromTower to toTower: | |
disk = towers[fromTower].pop() | |
towers[toTower].append(disk) | |
move_nums += 1 | |
# Check if the user has solved the puzzle: | |
if SOLVED_TOWER in (towers["B"], towers["C"]): | |
displayTowers(towers) # Display the towers one last time. | |
print(f"You have solved the puzzle in {move_nums} moves! Well done!") | |
print(f"It takes {(2**TOTAL_DISKS)-1} moves to solve it, at minimum.") | |
sys.exit() | |
def getPlayerMove(towers): | |
"""Asks the player for a move. Returns (fromTower, toTower).""" | |
while True: # Keep asking player until they enter a valid move. | |
print('Enter the letters of "from" and "to" towers, or QUIT.') | |
print("(e.g., AB to moves a disk from tower A to tower B.)") | |
print() | |
response = input("> ").upper().strip() | |
if response in ("QUIT", "Q"): | |
print("Thanks for playing!") | |
sys.exit() | |
# Make sure the user entered valid tower letters: | |
if response not in ("AB", "AC", "BA", "BC", "CA", "CB"): | |
print("Enter one of AB, AC, BA, BC, CA, or CB.") | |
continue | |
# Ask player again for their move. | |
# Use more descriptive variable names: | |
fromTower, toTower = response[0], response[1] | |
if len(towers[fromTower]) == 0: | |
# The "from" tower cannot be an empty tower: | |
print("You selected a tower with no disks.") | |
# Ask player again for their move. | |
continue | |
elif len(towers[toTower]) == 0: | |
# Any disk can be moved onto an empty "to" tower: | |
return fromTower, toTower | |
elif towers[toTower][-1] < towers[fromTower][-1]: | |
print("Can't put larger disks on top of smaller ones.") | |
# Ask player again for their move. | |
continue | |
else: | |
# This is a valid move, so return the selected towers: | |
return fromTower, toTower | |
def displayTowers(towers): | |
"""Display the three towers with their disks.""" | |
# Display the three towers: | |
for level in range(TOTAL_DISKS, -1, -1): | |
for tower in (towers["A"], towers["B"], towers["C"]): | |
if level >= len(tower): | |
# Display the bare pole with no disk. | |
displayDisk(0) | |
else: | |
# Display thedisk. | |
displayDisk(tower[level]) | |
print() | |
# Display the tower labels A, B, and C: | |
emptySpace = " " * (TOTAL_DISKS) | |
print("{0} A{0}{0} B{0}{0} C\n".format(emptySpace)) | |
def displayDisk(width): | |
"""Display a disk of the given width. A width of 0 means no disk.""" | |
emptySpace = " " * (TOTAL_DISKS - width) | |
if width == 0: | |
# Display a pole segment without a disk: | |
print(f"{emptySpace}||{emptySpace}", end="") | |
else: | |
# Display the disk: | |
disk = "@" * width | |
numLabel = str(width).rjust(2, "_") | |
print(f"{emptySpace}{disk}{numLabel}{disk}{emptySpace}", end="") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment