"""Flooder, by Al Sweigart al@inventwithpython.com
A colorful game where you try to fill the board with a single color. Has
a mode for colorblind players.
Inspired by the "Flood It!" game.
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: large, bext, game"""
import random, sys
try:
import bext
except ImportError:
print('This program requires the bext module, which you')
print('can install by following the instructions at')
print('https://pypi.org/project/Bext/')
sys.exit()
# Set up the constants:
BOARD_WIDTH = 16 # (!) Try changing this to 4 or 40.
BOARD_HEIGHT = 14 # (!) Try changing this to 4 or 20.
MOVES_PER_GAME = 20 # (!) Try changing this to 3 or 300.
# Constants for the different shapes used in colorblind mode:
HEART = chr(9829) # Character 9829 is '♥'.
DIAMOND = chr(9830) # Character 9830 is '♦'.
SPADE = chr(9824) # Character 9824 is '♠'.
CLUB = chr(9827) # Character 9827 is '♣'.
BALL = chr(9679) # Character 9679 is '●'.
TRIANGLE = chr(9650) # Character 9650 is '▲'.
BLOCK = chr(9608) # Character 9608 is '█'
LEFTRIGHT = chr(9472) # Character 9472 is '─'
UPDOWN = chr(9474) # Character 9474 is '│'
DOWNRIGHT = chr(9484) # Character 9484 is '┌'
DOWNLEFT = chr(9488) # Character 9488 is '┐'
UPRIGHT = chr(9492) # Character 9492 is '└'
UPLEFT = chr(9496) # Character 9496 is '┘'
# A list of chr() codes is at https://inventwithpython.com/chr
# All the color/shape tiles used on the board:
TILE_TYPES = (0, 1, 2, 3, 4, 5)
COLORS_MAP = {0: 'red', 1: 'green', 2:'blue',
3:'yellow', 4:'cyan', 5:'purple'}
COLOR_MODE = 'color mode'
SHAPES_MAP = {0: HEART, 1: TRIANGLE, 2: DIAMOND,
3: BALL, 4: CLUB, 5: SPADE}
SHAPE_MODE = 'shape mode'
def main():
bext.bg('black')
bext.fg('white')
bext.clear()
print('''Flooder, by Al Sweigart al@inventwithpython.com
Set the upper left color/shape, which fills in all the
adjacent squares of that color/shape. Try to make the
entire board the same color/shape.''')
print('Do you want to play in colorblind mode? Y/N')
response = input('> ')
if response.upper().startswith('Y'):
displayMode = SHAPE_MODE
else:
displayMode = COLOR_MODE
gameBoard = getNewBoard()
movesLeft = MOVES_PER_GAME
while True: # Main game loop.
displayBoard(gameBoard, displayMode)
print('Moves left:', movesLeft)
playerMove = askForPlayerMove(displayMode)
changeTile(playerMove, gameBoard, 0, 0)
movesLeft -= 1
if hasWon(gameBoard):
displayBoard(gameBoard, displayMode)
print('You have won!')
break
elif movesLeft == 0:
displayBoard(gameBoard, displayMode)
print('You have run out of moves!')
break
def getNewBoard():
"""Return a dictionary of a new Flood It board."""
# Keys are (x, y) tuples, values are the tile at that position.
board = {}
# Create random colors for the board.
for x in range(BOARD_WIDTH):
for y in range(BOARD_HEIGHT):
board[(x, y)] = random.choice(TILE_TYPES)
# Make several tiles the same as their neighbor. This creates groups
# of the same color/shape.
for i in range(BOARD_WIDTH * BOARD_HEIGHT):
x = random.randint(0, BOARD_WIDTH - 2)
y = random.randint(0, BOARD_HEIGHT - 1)
board[(x + 1, y)] = board[(x, y)]
return board
def displayBoard(board, displayMode):
"""Display the board on the screen."""
bext.fg('white')
# Display the top edge of the board:
print(DOWNRIGHT + (LEFTRIGHT * BOARD_WIDTH) + DOWNLEFT)
# Display each row:
for y in range(BOARD_HEIGHT):
bext.fg('white')
if y == 0: # The first row begins with '>'.
print('>', end='')
else: # Later rows begin with a white vertical line.
print(UPDOWN, end='')
# Display each tile in this row:
for x in range(BOARD_WIDTH):
bext.fg(COLORS_MAP[board[(x, y)]])
if displayMode == COLOR_MODE:
print(BLOCK, end='')
elif displayMode == SHAPE_MODE:
print(SHAPES_MAP[board[(x, y)]], end='')
bext.fg('white')
print(UPDOWN) # Rows end with a white vertical line.
# Display the bottom edge of the board:
print(UPRIGHT + (LEFTRIGHT * BOARD_WIDTH) + UPLEFT)
def askForPlayerMove(displayMode):
"""Let the player select a color to paint the upper left tile."""
while True:
bext.fg('white')
print('Choose one of ', end='')
if displayMode == COLOR_MODE:
bext.fg('red')
print('(R)ed ', end='')
bext.fg('green')
print('(G)reen ', end='')
bext.fg('blue')
print('(B)lue ', end='')
bext.fg('yellow')
print('(Y)ellow ', end='')
bext.fg('cyan')
print('(C)yan ', end='')
bext.fg('purple')
print('(P)urple ', end='')
elif displayMode == SHAPE_MODE:
bext.fg('red')
print('(H)eart, ', end='')
bext.fg('green')
print('(T)riangle, ', end='')
bext.fg('blue')
print('(D)iamond, ', end='')
bext.fg('yellow')
print('(B)all, ', end='')
bext.fg('cyan')
print('(C)lub, ', end='')
bext.fg('purple')
print('(S)pade, ', end='')
bext.fg('white')
print('or QUIT:')
response = input('> ').upper()
if response == 'QUIT':
print('Thanks for playing!')
sys.exit()
if displayMode == COLOR_MODE and response in tuple('RGBYCP'):
# Return a tile type number based on the response:
return {'R': 0, 'G': 1, 'B': 2,
'Y': 3, 'C': 4, 'P': 5}[response]
if displayMode == SHAPE_MODE and response in tuple('HTDBCS'):
# Return a tile type number based on the response:
return {'H': 0, 'T': 1, 'D':2,
'B': 3, 'C': 4, 'S': 5}[response]
def changeTile(tileType, board, x, y, charToChange=None):
"""Change the color/shape of a tile using the recursive flood fill
algorithm."""
if x == 0 and y == 0:
charToChange = board[(x, y)]
if tileType == charToChange:
return # Base Case: Already is the same tile.
board[(x, y)] = tileType
if x > 0 and board[(x - 1, y)] == charToChange:
# Recursive Case: Change the left neighbor's tile:
changeTile(tileType, board, x - 1, y, charToChange)
if y > 0 and board[(x, y - 1)] == charToChange:
# Recursive Case: Change the top neighbor's tile:
changeTile(tileType, board, x, y - 1, charToChange)
if x < BOARD_WIDTH - 1 and board[(x + 1, y)] == charToChange:
# Recursive Case: Change the right neighbor's tile:
changeTile(tileType, board, x + 1, y, charToChange)
if y < BOARD_HEIGHT - 1 and board[(x, y + 1)] == charToChange:
# Recursive Case: Change the bottom neighbor's tile:
changeTile(tileType, board, x, y + 1, charToChange)
def hasWon(board):
"""Return True if the entire board is one color/shape."""
tile = board[(0, 0)]
for x in range(BOARD_WIDTH):
for y in range(BOARD_HEIGHT):
if board[(x, y)] != tile:
return False
return True
# If this program was run (instead of imported), run the game:
if __name__ == '__main__':
main()