Source code for tictac

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


# A game of tic-tac-toe.

# Game parameters
n = 3 # grid size
nrow = 3 # number of consecutive marks needed for a victory
grid = np.zeros([n,n], dtype=int) # the play area

n_players = 2 # number of players (will be set in game_ai)

# 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([n,n], 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(n): for j in range(n): if grid[i,j] == 0: return True return False
[docs]def all_plays(grid, player=None): """ Lists the coordinates of all empty slots. returns: list of possible play actions """ empty_slots = [] for i in range(n): for j in range(n): if grid[i,j] == 0: empty_slots.append( [i,j] ) 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 all_reasonable_plays_alternative(grid, player=None): """ Lists the coordinates of all empty slots that have an occupied neighbor. returns: list of possible play actions """ good_slots = [] for i in range(n): for j in range(n): if grid[i,j] == 0: good = False for di in [-1, 0, 1]: ni = (i+di)%n for dj in [-1, 0, 1]: nj = (j+dj)%n if ni != 0 or nj != 0: if grid[ni,nj] > 0: good = True if good: good_slots.append( [i,j] ) if len(good_slots) == 0: return all_plays(grid) else: return good_slots
[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(n): for j in range(n): 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 """ type = grid[i,j] if type == 0: return 0 streak = True count = 1 # count horizontally ii = i+1 while streak: if ii < n: 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 < n: 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 < n and ii < n: 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 < n: 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(n): 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(n): # 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(n): # 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 < n-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) """ row = play[0] col = play[1] grid[row, col] = player
[docs]def can_take_slot(i,j,grid): """ Checks if one can play in the given slot. i: the row to check j: 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[i,j] == 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 and row where the player wants to play. player: number of the player whose turn it is grid: the play area (current game state) returns: the coordinates of the chosen slot as tuple (row, column) """ draw(grid) print("You are "+PSYM[player]) row = -1 col = -1 ok = False while not ok: while col < 0 or col >= n: try: col = int(input("choose column: ")) except: print("that's not an integer (enter -9 to quit)") col = -1 if col == -9: quit() while row < 0 or row >= n: try: row = int(input("choose row: ")) except: print("that's not an integer (enter -9 to quit)") col = -1 if row == -9: quit() if can_take_slot(row, col, grid): ok = True else: print("can't play there") col = -1 row = -1 return row, 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(PSYM[i]+" won!") if winner == 0: print("The game ended in a draw.")