Source code for n_in_a_row

import numpy as np
import os
import copy
from numpy.random import default_rng

# A game of 4 (or n) in a row.
# In this game, you drop markers on a grid in turns 
# and try to get 4 (or n) of your markers in a row.


# Game parameters
nx = 7 # number of columns
ny = 5 # number of rows
nrow = 4 # number of consecutive tokens needed for a victory
grid = np.zeros([ny, nx], dtype=int) # the play area

n_players = 2 # number of players (typically 2, but why not try 3?)

# Symbols for different players
P1 = "X"
P2 = "O"
P3 = "#"
P4 = "@"
PSYM = [" ", P1, P2, P3, P4]



[docs]def new_game(): """ Creates an empty play area. returns: the empty play area (the initial game state) """ return np.zeros([ny, nx], dtype=int)
[docs]def can_continue(grid): """ Checks if there are available spaces in the play area. Does not check if the game is done due to someone winning. returns: True if there are empty spaces, False if not. """ for i in range(nx): if grid[0, i] == 0: return True return False
[docs]def all_plays(grid, player=None): """ Lists the indices of all columns that have empty slots. returns: list of possible play actions """ empty_slots = [] for i in range(nx): if grid[0, i] == 0: empty_slots.append( i ) return empty_slots
[docs]def all_reasonable_plays(grid, player=None): """ Same as all_plays(). returns: list of possible play actions """ return all_plays(grid)
[docs]def check_winner(grid): """ Checks if someone has won the game. returns: the number of the winning player if there is one, 0 otherwise """ for i in range(ny): for j in range(nx): row = count_consecutives(i,j, grid) if row >= nrow: return grid[i,j] return 0
[docs]def previous_player(player): """ Number of the player whose turn comes before the given player. Note: Player numbering starts from 1 and ends at n_players. player: the player to check returns: the number of the previous player """ return (player)%n_players + 1
[docs]def next_player(player): """ Number of the player whose turn comes after the given player. Note: Player numbering starts from 1 and ends at n_players. player: the player to check returns: the number of the next player """ return (player)%n_players + 1
[docs]def count_consecutives(i,j,grid): """ Starting from the slot at grid[i,j], counts the highest number of consecutive squares with tokens of the same player. If the starting position has no token, returns 0. For ay given square, there are 8 possible directions for forming a line: up, down, left, rigth and the 4 diagonals. This function only checks half of them: down, right, down-right and down-left. i: vertical position (y coordinate) of the initial slot j: horizontal position (x coordinate) of the initial slot grid: the play area (current game state) returns: the highest number of similar consecutive tokens """ # If there is no token at this position, return 0 type = grid[i,j] if type == 0: return 0 streak = True count = 1 # count horizontally ii = i+1 while streak: if ii < ny: if grid[ii,j] == type: count += 1 ii += 1 else: streak = False else: streak = False max_count = count count = 1 streak = True # count vertically jj = j+1 while streak: if jj < nx: if grid[i,jj] == type: count += 1 jj += 1 else: streak = False else: streak = False if count > max_count: max_count = count count = 1 streak = True # count diagonally down-left ii = i+1 jj = j+1 while streak: if jj < nx and ii < ny: if grid[ii,jj] == type: count += 1 ii += 1 jj += 1 else: streak = False else: streak = False if count > max_count: max_count = count count = 1 streak = True # count diagonally down-right ii = i+1 jj = j-1 while streak: if jj >= 0 and ii < ny: if grid[ii,jj] == type: count += 1 ii += 1 jj -= 1 else: streak = False else: streak = False if count > max_count: max_count = count return max_count
[docs]def draw(grid): """ Draw the play area with text graphics. grid: the play area (current game state) """ # Clear the terminal. # The command depends on the operating system. os.system('cls' if os.name == 'nt' else 'clear') # Construct the header with column indices and # the horizontal lines. topheader = " " vline = " " for j in range(nx): if j < 10: topheader += " "+str(j)+" " elif j < 100: topheader += " "+str(j)+" " vline += "---+" # print the header print(topheader) # loop over all horizontal rows for i in range(ny): # start each line with its index line = " " if i < 10: line = " "+str(i)+" " elif i < 100: line = " "+str(i)+" " # loop over all slots (columns) in this row for j in range(nx): # mark each slot as either empty or with a player token if grid[i,j] == 0: # empty slot line += " |" else: # someone has put their token in this slot for type in range(1,n_players+1): if grid[i,j] == type: line += " "+PSYM[type]+" |" # print the row print(line[:-1]) # after each row except the last one, print a separating line if i < ny-1: print(vline[:-1])
[docs]def make_move(play, player, grid): """ Let a player have a turn. The function modifies the given game state (grid) to match the situation after the move has been carried out. play: the play action (move) to take player: number of the player who makes the move grid: the play area (current game state) """ for i in range(ny): if grid[ny-i-1, play] == 0: grid[ny-i-1, play] = player return
[docs]def can_take_slot(i,grid): """ Checks if one can play in the given column. i: the column to check grid: the play area (current game state) returns: True if there are empty slots in column i, False otherwise """ if grid[0, i] == 0: return True else: return False
[docs]def ask_for_move(player,grid): """ Asks a human player for a play action (move). This is done by visualizing the game and then asking for the index of the column where the player wants to play. player: number of the player whose turn it is grid: the play area (current game state) returns: the index of the column where the player decides to play """ # visualize the situation draw(grid) # notify whose turn it is print("You are "+PSYM[player]) col = -1 ok = False # keep asking for a decision until a valid decision is made while not ok: # keep asking until the choice is a valid column index while col < 0 or col >= nx: try: # try to read input as an integer col = int(input("choose column: ")) except: # if the input is invalid print("that's not an integer (enter -9 to quit)") col = -1 if col == -9: # entering -9 quits the game quit() # check that the chosen column is not full if can_take_slot(col, grid): ok = True else: print("can't play there") col = -1 return col
[docs]def copy_game(grid): """ Returns a copy of the given game state. grid: the play area (current game state) returns: a copy of grid """ return copy.deepcopy(grid)
[docs]def declare_winner(winner): """ Celebrates the victory of a player or declares a draw. winner: number of the winning player or 0 for a draw """ for i in range(1,len(PSYM)): if winner == i: print("") print(PSYM[i]+" won!") print("") if winner == 0: print("") print("The game ended in a draw.") print("")