def _create_playField(x=6, y=7): """ Creates a 2D matrix of zeros. Default size is 6X7 :param x: int: X axis size :param y: int: Y axis Size :return: 2D int array """ log.debug("Generating playfield with dimensions [{}][{}]".format(x, y)) playField = np.zeros( (x, y)) # uses numpy to create a 2d array of pure zeros (im being lazy) return playField
def _get_next_open_row(playField, col): """ Checks where the piece will land on this row :param playField: The play field :param col: The target column :return: int representing the row number """ # Iterates through the rows finding the first row where there is no existing piece for i in range(ROW_COUNT): if playField[i][col] == 0: log.debug("Selected row {} for {}".format(i, col)) return i return -1 # negative 1 = no space
def exportPlay(column): """ Converts play to an array and exports this data to a text file :param column: The players move :return: """ global GatherMove GatherMove = False # Ensures that only plays after exported boards are recorded dataForExport = [] # Prepare a list for the data to be dumped into """ #Intended AI output would be list of probabilities indicating best move #e.g: [0.1, 0.3, 0.5, 0.7, 0.5, 0.2, 0.1] #Would indicate best move to be 4th column """ for i in range(0, 7): if column == i: dataForExport.append( 1.0) #For move at col 2 would result in [0,0,1,0,0,0,0] else: dataForExport.append(0.0) # EXPORTING CODE # if not os.path.isdir("trainingData"): # Verify the desired folder exists, if not, create it log.debug("Training Data folder missing... creating") os.mkdir("trainingData") fileNum = 0 try: while True: # this loop makes sure it wont overwrite an existing file, then writes filename = "trainingData/ExportedMove{}.txt".format(fileNum) if os.path.isfile(filename): fileNum += 1 else: f = open(filename, "w") f.write(str(dataForExport)) f.close() log.info("Exported current state to {}".format(filename)) return True except Exception as e: # Error handling ^-^ log.error("Failed to export game state: {}".format(e)) return False
def _input(playField, turn, pos): """ Gets the player's move and validate it param playField: the play field :param turn: the current turn :return: the column the player chose """ # If AI is enabled, this if statement will call ai to give a column number if TestMode: if AIMode and (turn % 2 == 0): return _get_AI_move(playField) return random.randint(0, ROW_COUNT) if turn % 2 == 0 and AIMode: log.debug("Polling AI code for its move...") col = _get_AI_move(playField) """ # todo: call some function that'll return a column number col = 0 # todo: remove this line return # todo: remove this line and uncomment and edit the line below col = ["some function to get a value off the AI"] """ ### SANITY CHECKS ### if col is None: log.critical("AI returned Null value") _quit(1) # exit with an error condition try: int(col) except ValueError: log.critical("AI didnt return an int") _quit(1) # exit with an error condition if col > COLUMN_COUNT - 1: log.critical("AI returned an impossible position") _quit(1) # exit with an error condition else: # value from the AI should be known good now, it can be used safely log.debug("AI is putting its piece on column {}".format(col)) return col else: # if AIMode is not enabled, or its player 1, take input if turn % 2 == 0 and DataGatherMode: col = random.sample([0, 1, 2, 3, 4, 5, 6], 1) while not _validate_move(playField, col): col = random.sample([0, 1, 2, 3, 4, 5, 6], 1) log.debug("Randomiser clicked at {}|{} = column: {}".format( pos[0], pos[1], col)) else: posx = pos[0] col = int(math.floor(posx / 100)) if col > COLUMN_COUNT - 1: return None log.debug("Player clicked at {}|{} = column: {}".format( pos[0], pos[1], col)) return col
def _fit_model(epochs): # try: # global model # global training_input # global training_output # # log.info('Fitting model') # # model.fit(training_input, training_output, epochs=epochs) #Not currently working # # # log.info('\tModel fitted\n') # return True # # except: # log.error('\tUnknown error in NeuralNetwork._fit_model\n') # return False # global model # global training_input # global training_output # # # print(model.summary()) # # log.info('Fitting model') # model.fit(training_input, training_output, epochs=epochs) # Not currently working # # # log.info('\tModel fitted\n') try: log.info('Fitting model') global model model.fit(training_input, training_output, epochs=epochs, batch_size=128) # Trains neural network log.debug("\tModel fitted\n") return True except: log.error("\tFailed to fit model\n") return False
def _drop_piece(playField, row, col, player): """ Places the piece onto the playField :param playField: The play field :param row: The target row :param col: The target column :param player: The player making the move :return: None """ try: log.debug("P{}: Placing piece at [{}][{}]".format(player, row, col)) playField[row][col] = player if GatherMove: # Exports play if board exported exportPlay(col) except Exception as e: log.error("Failed to place piece: {}".format(e)) return False
def flattenAndExport(playfield): """ Flattens the 2D array into a 1D array and exports this data to a text file :param playfield: The game's playfield :return: """ global GatherMove GatherMove = True # The play made at this point to be recorded, for AI target data dataForExport = [] # Prepare a list for the data to be dumped into for row in playfield: for item in row: dataForExport.append( item) # dump each item one by one into this new list # EXPORTING CODE # if not os.path.isdir("trainingData"): # Verify the desired folder exists, if not, create it log.debug("Training Data folder missing... creating") os.mkdir("trainingData") fileNum = 0 try: while True: # this loop makes sure it wont overwrite an existing file, then writes filename = "trainingData/ExportedState{}.txt".format(fileNum) if os.path.isfile(filename): fileNum += 1 else: f = open(filename, "w") f.write(str(dataForExport)) f.close() log.info("Exported current state to {}".format(filename)) return True except Exception as e: # Error handling ^-^ log.error("Failed to export game state: {}".format(e)) return False
Version: 0.01 Purpose: Creates/Saves/Loads neural network for Connect-Four game Author: Graham Mark Broadbent Date: 12/03/19 """ import logging from logManager import log log.info('Program Begin\n') import DataFormatter as df # Because I couldn't find an easier option, I made one log.info('Importing Packages') import os log.debug('\tImported OS') import tensorflow as tf log.debug('\tImported TensorFlow') # TensorFlow 1.5 import keras from keras.models import Sequential from keras.layers import Dense, Dropout log.debug('\tImported Keras') import numpy as np log.debug('\tImported Numpy') log.info('\tImporting Done\n') training_input = [] training_output = [] model = None new_model = None has_model = False
def _game_loop(playField): """ The main game loop :param playField: the play field :return: """ global TestMode global DataGatherMode global AIMode global AI global turn global hueHSV log.info("Game Loop started") turn = 1 while True: # BIG SCARY EVENT LOOP! for event in pygame.event.get(): # poll pygame for events if event.type == pygame.QUIT: # Allow game to quit _quit() if not TestMode: if event.type == pygame.MOUSEMOTION: # User moved the mouse, so move their piece after their cursor for A E S T H E T I C S pygame.draw.rect( screen, (0, 0, 0), (0, 0, width, 100) ) # hide the last frame of motion, turn this off for some really trippy stuff posx = event.pos[0] # get the location of the cursor if posx < ( COLUMN_COUNT * 100 ) - 25 and posx > 25: # messy way of clamping the location above the game columns if turn % 2 == 0: # determine whos turn it is, and make the piece that colour pygame.draw.circle(screen, (206, 22, 48), (posx, 50), int(radius)) else: pygame.draw.circle(screen, (255, 201, 23), (posx, 50), int(radius)) pygame.display.update() # refresh the screen if event.type == pygame.MOUSEBUTTONDOWN or turn % 2 == 0 and AIMode: # click = drop piece if turn % 2 == 0 and AIMode: col = _input(playField, turn, pos=[0, 0]) else: col = _input( playField, turn, event.pos ) # determine what column the user clicked above if col is not None: # None = the user didnt click in a valid location, so we ignore it row = _get_next_open_row( playField, col) # determine what row we should place a piece if row != -1: _drop_piece(playField, row, col, turn % 2 + 1) # drop said piece if _winning_move(playField, turn % 2 + 1): # check if a player has won log.info( "Win condition met for player {}".format( turn % 2 + 1)) renderer(playField) print("Player {} is the Winner in {} turns!". format(turn % 2 + 1, turn)) if not TestMode: pygame.display.update() end_screen(turn % 2 + 1, turn) pygame.time.wait( 2000 ) # wait for a bit to allow the player to B A S K in their glory quit() return # exit the game loop and quit return else: log.info("Unable to place piece on column " + str(col)) turn -= 1 renderer(playField) pygame.display.update() turn += 1 if event.type == pygame.KEYDOWN: # Bit of code for Mark, because he asked for the game to export its current state if event.key == pygame.K_F6 and turn % 2 == 1: # Check if the user pressed F6 then export log.debug("Exporting current game state...") flattenAndExport(playField) if event.key == pygame.K_t: TestMode = not TestMode renderer(playField) pygame.display.flip() log.debug("Test Mode set to {}".format(TestMode)) start_game() if event.key == pygame.K_d: DataGatherMode = not DataGatherMode log.debug( "Data Gather mode set to {}".format(DataGatherMode)) if event.key == pygame.K_a: AIMode = not AIMode if AIMode and (not AI): try: # AI = keras.models.load_model('AI.model') # If first time AI toggles, imports the NN nn._construct("AI") # nn._save_model("AI") AI = True log.info("Neural Network model loaded") except: log.error("Failed to load Neural Network model") AIMode = False # Ensures not trying to get info from absent AI log.debug("AI mode set to {}".format(AIMode)) if TestMode: col = _input(playField, turn, 1) if col is not None: # None = the user didnt click in a valid location, so we ignore it row = _get_next_open_row( playField, col) # determine what row we should place a piece if row != -1: _drop_piece(playField, row, col, turn % 2 + 1) # drop said piece if _winning_move(playField, turn % 2 + 1): # check if a player has won log.info( "Win condition met for player {}".format(turn % 2 + 1)) renderer(playField) hueHSV = hueHSV + 1 pygame.display.update() print("Player {} is the Winner in {} turns!".format( turn % 2 + 1, turn)) pygame.time.wait(10) return turn += 1 unique, counts = np.unique(playField, return_counts=True) dictionary = dict(zip(unique, counts)) try: if dictionary[0] == 0: return except KeyError: log.warning("Playfield full, restarting...") renderer(playField) pygame.display.update() return except ValueError: pass
from logManager import log import os # import keras import random import NeuralNetwork as nn ROW_COUNT = 6 COLUMN_COUNT = 7 AIMode = True DataGatherMode = False GatherMove = False TestMode = False hueHSV = 0 log.debug("AI mode set to {}".format(AIMode)) log.debug("Data Gather mode set to {}".format(DataGatherMode)) log.debug("Test Mode set to {}".format(TestMode)) pygame.init() width = (COLUMN_COUNT + 2) * 100 height = (ROW_COUNT + 1) * 100 size = (width, height) radius = int(100 / 2 - 5) screen = pygame.display.set_mode(size) pygame.display.set_caption("Connect 4 AI Game") logo_image = pygame.image.load("unnamed.png") pygame.display.set_icon(logo_image) turn = 0 # Variable to hold turn for UI AI = False # Variable for NN, set to 0 before loaded # Text objects
def main_menu(): """ The main menu screen :return: None """ # Escape condition if test mode is enabled if TestMode: return log.info("Loading main menu") main = True counter = 0 difficulty = ["Easy", "Medium", "Hard"] # Variables to hold various colours blue = (29, 92, 193) white = (255, 255, 255) black = (0, 0, 0) yellow = (255, 255, 0) dark_yellow = (210, 225, 0) red = (255, 0, 0) dark_red = (210, 0, 0) # Renders text for each button text_surface = small_text.render(difficulty[counter], True, black) text_surface1 = large_text.render("Connect 4", True, black) text_surface2 = small_text.render("Play", True, black) text_surface3 = small_text.render("Quit", True, black) screen.fill(blue) # Draws the main title text_rect = text_surface1.get_rect() text_rect.center = ((width / 2), (height / 2)) screen.blit(text_surface1, text_rect) #Loop to update the main menu while it is in use while main: for event in pygame.event.get(): if event.type != pygame.MOUSEMOTION: log.debug(event) if event.type == pygame.QUIT: pygame.quit() quit() # Variables to store mouse information mouse = pygame.mouse.get_pos() click = pygame.mouse.get_pressed() # If statement to respond to users interaction with play button if 225 + 150 > mouse[0] > 225 and 550 + 50 > mouse[1] > 550: # Change button colour while it is under cursor pygame.draw.rect(screen, dark_yellow, (225, 550, 150, 50)) # End loop and take player to the game if they click play if click[0] == 1: screen.fill(black) main = False else: pygame.draw.rect(screen, yellow, (225, 550, 150, 50)) # If statement to respond to users interaction with quit button if 525 + 150 > mouse[0] > 525 and 550 + 50 > mouse[1] > 550: # Change button colour while it is under cursor pygame.draw.rect(screen, dark_red, (525, 550, 150, 50)) # Exit the game if the user presses quit if click[0] == 1: pygame.quit() quit() else: pygame.draw.rect(screen, red, (525, 550, 150, 50)) # If statement to respond to users interaction with difficulty button if 375 + 150 > mouse[0] > 375 and 450 + 50 > mouse[1] > 450: # Change button colour while it is under cursor pygame.draw.rect(screen, dark_yellow, (375, 450, 150, 50)) # Changes counter that corresponds with difficulty type if clicked if click[0] == 1: if counter == 2: counter = 0 else: counter += 1 # Renders text with new difficulty setting text_surface = small_text.render(difficulty[counter], True, black) pygame.time.wait(100) else: pygame.draw.rect(screen, yellow, (375, 450, 150, 50)) # Draws difficulty text onto button text_rect = text_surface.get_rect() text_rect.center = ((375 + (150 / 2)), (450 + (50 / 2))) screen.blit(text_surface, text_rect) # Draws play text onto button text_rect = text_surface2.get_rect() text_rect.center = ((225 + (150 / 2)), (550 + (50 / 2))) screen.blit(text_surface2, text_rect) # Draws quit text onto button text_rect = text_surface3.get_rect() text_rect.center = ((525 + (150 / 2)), (550 + (50 / 2))) screen.blit(text_surface3, text_rect) # Updates display pygame.display.update()
import logging from logManager import log log.info('Program Begin\n') log.info('Importing Packages') import tensorflow as tf log.debug('\tImported TensorFlow') #TensorFlow 1.5 import keras log.debug('\tImported Keras') import numpy as np log.debug('\tImported Numpy') #import matplotlib.pyplot as plt #Just to see end result, not required for purpose #print('\tImported Matplotlib.Pyplot') log.info('\tImporting Done\n') log.debug('Setting Initial Variables') bannerChar = '¬' log.debug('\tVariables Set\n') log.debug('Creating Dataset') mnist = keras.datasets.mnist #In practice, would probably be replaced with X000 possible game states log.debug('\tDataset Created\n') log.debug('Loading Data') (xTrain, yTrain), (xTest, yTest) = mnist.load_data()