Last active
September 11, 2023 09:58
-
-
Save dutchLuck/460e8d4f0b9dbb37b0273ecb39aab17e to your computer and use it in GitHub Desktop.
Retro text version of Connect 4 game.
This file contains hidden or 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
#! /usr/bin/python3 | |
# | |
# C 4 | |
# | |
# This code attempts to play the game of Connect 4 | |
# using: text mode input and output. | |
# | |
# Last edited Mon Apr 3 20:58:53 2023 | |
# | |
# c4 doesn't incorporate much game awareness in | |
# its play, so it should be relatively easy to | |
# beat. | |
# | |
# 0v2 Look ahead 1 token for a win or to stop human player potential win | |
# 0v1 Added code for repeated games | |
# 0v0 original version | |
# | |
# Roadmap for additional capability | |
# 0v3 Add more aggressive strategy to help computer win | |
# | |
""" | |
Board of 6 rows of 7 columns is mapped to a cell number | |
as shown below; - | |
| 5 | 11 | 17 | 23 | 29 | 35 | 41 | | |
| 4 | 10 | 16 | 22 | 28 | 34 | 40 | | |
| 3 | 9 | 15 | 21 | 27 | 33 | 39 | | |
| 2 | 8 | 14 | 20 | 26 | 32 | 38 | | |
| 1 | 7 | 13 | 19 | 25 | 31 | 37 | | |
| 0 | 6 | 12 | 18 | 24 | 30 | 36 | | |
====1======2======3======4======5======6======7=== | |
""" | |
import random # computer only plays random at this stage | |
import time # provides a time delay through sleep() | |
board = 42 * [0] # The playing board is a list | |
def clearGameBoard( board ): | |
for cnt in range( 42 ): | |
board[ cnt ] = 0 | |
def columnIsntFull( board, column ): | |
return board[( 6 * column ) - 1 ] == 0 # As long as the top cell is empty the column can be considered available | |
def printTokenInSpace( player ): | |
if player == 1: | |
print( ' C ', end = '' ) # Indicate a computer token in the cell | |
elif player == 101: | |
print( 'wCn', end = '' ) # Indicate a winning computer token in the cell | |
elif player == 2: | |
print( ' P ', end = '' ) # Indicate a human player token in the cell | |
elif player == 102: | |
print( 'wPn', end = '' ) # Indicate a winning human player token in the cell | |
else: | |
print( ' ? ', end = '' ) # Indicate something has gone wrong | |
def printBoard( board ): | |
print() # a blank line before the board | |
for row in range( 5, -1, -1): # move from upper to lower rows | |
for col in range( row, row + 41, 6): # move across each row | |
print( '|', end = '' ) | |
if col >= 36: # test for right hand end of board | |
if board[ col ] == 0: # test for unpopulated cell | |
print(' |') | |
else: | |
printTokenInSpace( board[ col ] ) | |
print( '|' ) | |
else: | |
if board[ col ] == 0: # test for unpopulated cell | |
print(' ', end = '' ) | |
else: | |
printTokenInSpace( board[ col ] ) | |
print( '==1===2===3===4===5===6===7==' ) | |
def testForVerticalWin( index, player ): | |
fourInA_Row = (board[ index] == player) and (board[ index + 1] == player) and (board[ index + 2] == player) and (board[ index + 3] == player) | |
return fourInA_Row | |
def testForHorizontalWin( index, player ): | |
fourInA_Row = (board[ index] == player) and (board[ index + 6] == player) and (board[ index + 12] == player) and (board[ index + 18] == player) | |
return fourInA_Row | |
def testForDiagRightWin( index, player ): | |
fourInA_Row = (board[ index] == player) and (board[ index + 7] == player) and (board[ index + 14] == player) and (board[ index + 21] == player) | |
return fourInA_Row | |
def testForDiagLeftWin( index, player ): | |
fourInA_Row = (board[ index] == player) and (board[ index - 5] == player) and (board[ index - 10] == player) and (board[ index - 15] == player) | |
return fourInA_Row | |
def testAllForVerticalWin( player ): | |
vertWin = False | |
for col in range( 0, 42, 6 ): | |
for index in range( col, col + 3, 1 ): | |
vertWin = vertWin or testForVerticalWin( index, player ) | |
return vertWin | |
def testAllForHorizontalWin( player ): | |
horizWin = False | |
for row in range( 0, 6, 1 ): | |
for index in range( row, row + 24, 6 ): | |
horizWin = horizWin or testForHorizontalWin( index, player ) | |
return horizWin | |
def testAllForDiagRightWin( player ): | |
diagWin = False | |
for row in range( 0, 3, 1): | |
for index in range( row, row + 24, 6 ): | |
diagWin = diagWin or testForDiagRightWin( index, player ) | |
return diagWin | |
def testAllForDiagLeftWin( player ): | |
diagWin = False | |
for row in range( 0, 3, 1 ): | |
for index in range( row + 18, row + 42, 6 ): | |
diagWin = diagWin or testForDiagLeftWin( index, player ) | |
return diagWin | |
def testAllForPlayerWin( player ): | |
return testAllForVerticalWin( player ) or testAllForHorizontalWin( player ) or testAllForDiagRightWin( player ) or testAllForDiagLeftWin( player ) | |
def testAllForComputerWin(): | |
return testAllForPlayerWin( 1 ) | |
def testAllForHumanPlayerWin(): | |
return testAllForPlayerWin( 2 ) | |
def findIndexOfBottomMostEmptyLevelInColumn( column, board ): | |
if board[( 6 * column ) - 1 ] != 0: | |
print( '?? Column {} is already full'.format( column )) | |
return -1 | |
else: | |
lowestIndex = 6 * ( column - 1 ) | |
for index in range( 6 * ( column - 1 ), 6 * column, 1 ): | |
if board[ index ] != 0: | |
lowestIndex = index + 1 | |
return lowestIndex | |
def columnToCompleteA_PossibleWin( player, board ): | |
col = 0 | |
result = False | |
index = -1 | |
while col < 7 and not result: | |
col = col + 1 | |
if columnIsntFull( board, col ): | |
index = findIndexOfBottomMostEmptyLevelInColumn( col, board ) | |
board[ index ] = player | |
result = testAllForPlayerWin( player ) | |
board[ index ] = 0 # restore board | |
if not result: # ensure index gets reset if no Win found | |
index = -1 | |
if index == -1: | |
col = -1 | |
return( col ) | |
def columnToCompleteA_PossibleComputerWin( board ): | |
return( columnToCompleteA_PossibleWin( 1, board )) | |
def columnToBlockA_PossibleHumanPlayerWin( board ): | |
return( columnToCompleteA_PossibleWin( 2, board )) | |
def computerDropToken( board, token ): | |
index = -1 # Ensure the while loop runs at least once | |
while index == -1: | |
col = columnToCompleteA_PossibleComputerWin( board ) | |
## print( 'column returned by columnToCompleteA_PossibleComputerWin() is {}'.format( col )) | |
if col == -1: | |
col = columnToBlockA_PossibleHumanPlayerWin( board ) | |
## print( 'column returned by columnToBlockA_PossibleHumanPlayerWin() is {}'.format( col )) | |
if col == -1: # if no column chosen yet then chose a random column | |
col = random.randint( 1, 7 ) | |
if columnIsntFull( board, col ): | |
print( '\nComputer has chosen column {}'.format( col )) | |
index = findIndexOfBottomMostEmptyLevelInColumn( col, board ) | |
## print( 'Index for column {} is {}'.format( col, index )) | |
if index > -1: | |
board[ index ] = token | |
return index | |
def humanDropToken( board, token ): | |
yetToGetValidInput = True | |
while yetToGetValidInput: | |
print( '\nWhich column (1 to 7) do you want to drop a token into ? (0 to exit) : ', end = '' ) | |
response = input() | |
if response == '': | |
print( "?? No column number was specified, Please try again" ) | |
else: | |
# If human input is 0 then exit | |
if response == '0': | |
return -2 | |
elif response in [ '1', '2', '3', '4', '5', '6', '7' ]: | |
col = int( response ) | |
# Error check the human input and limit it to something sensible | |
if col > 7 or col < 1: | |
print( "?? There is no column {}, Please try again".format( col )) | |
elif columnIsntFull( board, col ): | |
yetToGetValidInput = False | |
else: | |
print( '?? Column {} is already full, Please try again.'.format( col )) | |
else: | |
print( '?? {} is not a number between 1 and 7, Please try again.'.format( response )) | |
# Drop the token into the lowest available cell | |
index = findIndexOfBottomMostEmptyLevelInColumn( col, board ) | |
## print( 'Bottom Index for column {} is {}'.format( col, index )) | |
# If the column is already full exit the game | |
if index > -1: | |
board[ index ] = token | |
return index | |
def emptyCellCount( build ): | |
count = 0 | |
for index in range( 0, 42, 1 ): | |
if board[ index ] == 0: | |
count = count + 1 | |
return count | |
def printInstructions(): | |
print() | |
print( 'The aim of this game is to get four tokens in a row,' ) | |
print( 'either vertically, horizontally or on a diagonal.' ) | |
print( 'This is acheived by dropping each one in 1 of 7 columns' ) | |
print( 'when it is your turn to play on the following board.' ) | |
printBoard( board ) | |
print( 'While you are working on arranging for four tokens in' ) | |
print( 'a row the computer will also take turns to drop its' ) | |
print( 'tokens into the columns, also aiming for four in a row.' ) | |
print( 'The winner is the first player to get four tokens in a row.' ) | |
print() | |
def doComputerTurn( board ): | |
index = computerDropToken( board, 1 ) | |
printBoard( board ) | |
if testAllForComputerWin(): | |
print( '\nCommiserations the computer has four in a row, so it Wins' ) | |
index = -1 # Flag Game finished | |
return index | |
def doHumanPlayerTurn( board ): | |
index = humanDropToken( board, 2 ) | |
if index > -1: | |
printBoard( board ) | |
if testAllForHumanPlayerWin(): | |
print( '\nCongratulations you have four in a row, so you Win!' ) | |
index = -1 # Flag Game finished | |
return index | |
# Start of main code | |
random.seed(a=None, version=2) | |
# | |
print( 'Do you want instructions? [ y or n ]: ', end = '' ) | |
response = input() | |
if response == 'y' or response == 'Y': | |
printInstructions() | |
# | |
gameNumber = 0 # Keep track of number of games played | |
computerWins = 0 # Keep track of number of games won by the computer | |
humanPlayerWins = 0 # Keep track of number of games won by the human player | |
startNewGame = True | |
while startNewGame: | |
clearGameBoard( board ) # Clear board just in case game is played repeatedly | |
gameNumber = gameNumber + 1 | |
print( '\nStarting Game Number {}'.format( gameNumber )) | |
print( '\nDo you wish to play first? [ y or n ]: ', end = '' ) | |
response = input() | |
## print( 'response was "{}"'.format( response )) | |
for loopCnt in range( 1, 22, 1 ): | |
print( '\n\n>>> Starting round {} <<<'.format( loopCnt )) | |
if response == 'y' or response == 'Y': | |
index = doHumanPlayerTurn( board ) # Get the column, drop the token and check for a win (returns -1) | |
if index > -1: # If index is -1 then Human Player has already won | |
index = doComputerTurn( board ) # Determine the column, drop the token and check for a win (returns -1) | |
if index == -1: | |
computerWins += 1 | |
elif index == -1: | |
humanPlayerWins += 1 | |
else: | |
index = doComputerTurn(board ) # If index is set to -1 then computer has won | |
if index > -1: | |
index = doHumanPlayerTurn( board ) | |
if index == -1: | |
humanPlayerWins += 1 | |
elif index == -1: | |
computerWins += 1 | |
# Exit if end-of-game flag is set | |
if index < 0: # exit if a win or the human player has entered 0 | |
print( 'Exiting game {}.'.format( gameNumber )) | |
break | |
print( '\n\n>>> Game {} board after round {} is; -'.format( gameNumber, loopCnt )) | |
printBoard( board ) | |
time.sleep( 1 ) | |
if emptyCellCount( board ) <= 0: | |
print( 'No further play is possible as all columns are full - Exiting drawn game.' ) | |
break | |
print( '\nDo you wish to play again? [ y or n ]: ', end = '' ) | |
rspns = input() | |
startNewGame = rspns == 'y' or rspns == 'Y' | |
print( 'Bye, you played {} game(s) and won {} time(s) - thanks for playing.'.format( gameNumber, humanPlayerWins )) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment