www.damionmullins.com Damion Mullins, MS, MBA

Connect 4 with Machine Learning

Version 1.0, developed in 2020



As a way to work on machine learning and Python, I decided to write a version of connect 4 and integrate machine learning concepts into it. In this version of connect 4, I have hard coded procedures written in JavaScript for the application to achieve basic moves and counter moves.

However, if the application can move freely and not have to perform a blocking move or a winning move, an Ajax call is sent to Python in which a custom algorithm and sql query is run on stored winning and losing moves. The application uses weights to determine the best move to make. After every win or loss, the application sends another Ajax call and stores all of the moves and meta data about the moves in order to increase its ability to learn.

Below is the source code with brief descriptions of each file for my connect 4 with machine learning demonstration.

home.js: This file initializes the game
                   
$(document).ready(function () {
	home.startGame();
});

var home = (function (parent, $, shared, game) {
	parent.func = {};
	parent.ajaxConfig = {};
	parent.data = {};
	
	parent.startGame = function () {
		parent.func = {
			'afterAjax': function (payload) {
				home.renderGame(payload);
			}
		};
		
		parent.ajaxConfig = {
			'url': '/sample1/home/start_game',
			'method': 'GET'
		}
		
		shared.ajax(parent.ajaxConfig, parent.data, parent.func);
	};
	
	parent.renderGame = function (payload) {
		$("#gameBoardCont").html(payload.gameBoard);
		parent.showWins(payload.wins);
		game.start();
	};
	
	parent.showWins = function (obj) {
		$("#compWins").html(obj.comp);
		$("#playerWins").html(obj.opp);
		$("#draws").html(obj.draw);
	};
	
	return parent;
})(home || {}, $, shared, game);
                    
game.js: This file handles post-move processes and game board configuration after each move.

$(document).ready(function () {
	$("#msgCont").html("Player 1 Begin");
});

$(document).on("click", ".cell", function (evt) {
	if (false === $("#msgCont").hasClass("done") && false == globals.disableClick) {
		game.cellClicked(evt.target.id);
	}
});

var globals = {
	disableClick: false
};

var game = (function (parent, $, globals) {
	parent.turn = 1;
	parent.order = 1;
	parent.vsComputer = 1;
	parent.disableClick = false;
	
	parent.start = function () {
		parent.updateBoard();
	};
	
	parent.cellClicked = function (id) {
		if ("" === $("#" + id).attr('data-chosenBy') 
		    && 0 === Number($("#" + id).attr('data-blocked'))) {
			$("#" + id).attr('data-chosenBy', parent.turn);
			$("#" + id).attr('data-order', parent.order);
			$("[data-chosenBy='1']")
				.css("background-color", "red")
				.html("1");
			$("[data-chosenBy='2']")
				.css("background-color", "blue")
				.html("2");
			parent.unBlockAboveCell(id);
			parent.changeTurn();
			$("#msgCont").html("Player " + parent.turn + " choose a square");
			parent.initWinCheck();
		}
	};
	
	parent.unBlockAboveCell = function (id) {
		var row = id.split("r")[1].split("c")[0] - 1;
		var cell = id.split("c")[1];
		if (0 !== row) {
			$("#r" + row + "c" + cell)
				.css("background-color", "#ffffff")
				.attr('data-blocked', 0);
		}
	};
	
	parent.changeTurn = function () {
		parent.turn = (1 === parent.turn) ? 2 : 1;
		if (1 === Number(parent.turn)) {
			parent.order = Number(parent.order) + 1;
		}
		
		if (2 === Number(parent.turn) && 1 === parent.vsComputer) {
			globals.disableClick = true;
			setTimeout(function(){
				parent.initComputerTurn();
			}, 1000);
		}
	};
	
	parent.updateBoard = function () {
		$(".cell").not("[id^=r6]").css("background-color", "#cccccc");
		$("[id^=r6]").attr('data-blocked', 0);
	};
	
	parent.initWinCheck = function () {
		logic.getChosenCells();
		parent.checkForWin(1);
		parent.checkForWin(2);
		parent.checkForStaleMate();
		logic.clearMemory();
	};
	
	parent.checkForStaleMate = function () {
		if (42 === Number(logic.noCells)) {
			$("#msgCont").addClass("done");
			$("#msgCont").html("It's a Draw!");
			parent.sendWinToApp(3);
		}
	};
	
	parent.initWin = function () {
		var winner = (1 === Number(parent.turn)) ? 2 : 1;
		$("#msgCont").addClass("done");
		$("#msgCont").html("Player " + winner + " Wins!");
		if (1 === Number(parent.vsComputer)) {
			parent.sendWinToApp(winner);
		}
	};
	
	parent.sendWinToApp = function (winner) {
		var data = {
			"winner": winner,
			"orderedChoices": logic.orderedChoices
		}
		
		var func = {
			'afterAjax': function (payload) {
				game.saveWinComplete(payload);
			}
		};
		
		var ajaxConfig = {
			'url': '/sample1/logic/win',
			'method': 'GET'
		}
		
		shared.ajax(ajaxConfig, data, func);
	};
	
	parent.saveWinComplete = function (payload) {
		if (0 === Number(payload.saved)) {
			alert("Win could not be saved to database");
		}
	};
	
	parent.checkForWin = function (user) {
		var arr = logic.opponentCells;
		if (2 === Number(user)) {
			arr = logic.computerCells;
		}
		arr.sort(function(a, b) {
			return a - b;
		});
		for (var i = 0; i <= arr.length; i++) {
			var id = arr[i];
			if (typeof id !== "undefined") {
				var r = id.split("r")[1].split("c")[0];
				var c = id.split("c")[1];
				var hRWin = parent.horizontalRightWin(r, c, arr);
				var hLWin = parent.horizontalLeftWin(r, c, arr);
				var vertWin = parent.verticalWin(r, c, arr);
				var pDiagWin = parent.positiveDiagnolWin(r, c, arr);
				var nDiagWin = parent.negativeDiagnolWin(r, c, arr);
				
				if (true === hRWin 
					|| true === hLWin
					|| true === vertWin
					|| true === pDiagWin
					|| true === nDiagWin) {
						parent.initWin();
						return true;
				}
			}
		}
	};
	
	parent.negativeDiagnolWin = function (r, c, arr) {
		var win = true;
		for (var i = 1; i <= 3; i++) {
			var newR = Number(r) - i;
			var newC = Number(c) - i;
			var nextCell = "r" + newR + "c" + newC;
			if (-1 === $.inArray(nextCell, arr)) {
				win = false;
			}
		}
		
		return win;
	};	
	
	parent.positiveDiagnolWin = function (r, c, arr) {
		var win = true;
		for (var i = 1; i <= 3; i++) {
			var newR = Number(r) - i;
			var newC = Number(c) + i;
			var nextCell = "r" + newR + "c" + newC;
			if (-1 === $.inArray(nextCell, arr)) {
				win = false;
			}
		}
		
		return win;
	};
	
	parent.verticalWin = function (r, c, arr) {
		var win = true;
		for (var i = 1; i <= 3; i++) {
			var newR = Number(r) - i;
			var nextCell = "r" + newR + "c" + c;
			if (-1 === $.inArray(nextCell, arr)) {
				win = false;
			}
		}
	
		return win;
	};
	
	parent.horizontalRightWin = function (r, c, arr) {
		var win = true;
		for (var i = 1; i <= 3; i++) {
			var newC = Number(c) + i;
			var nextCell = "r" + r + "c" + newC;
			if (-1 === $.inArray(nextCell, arr)) {
				win = false;
			}
		}
		
		return win;
	};
	
	parent.horizontalLeftWin = function (r, c, arr) {
		var win = true;
		for (var i = 1; i <= 4; i++) {
			var newC = Number(c) - i;
			var nextCell = "r" + r + "c" + newC;
			if (-1 === $.inArray(nextCell, arr)) {
				win = false;
			}
		}
		
		return win;
	};
	
	parent.initComputerTurn = function () {
		globals.disableClick = false;
		logic.init(parent.order);
	};
	
	return parent;
})(game || {}, $, globals, logic);
                    
logic.js: This file handles move procedures for the application. It has the client-side algorithm to enable the application to play the game, as well as the ajax call to the server in order to process win-loss data and determine a better move.

var logic = (function (parent, $, shared) {
	parent.computerCells = [];
	parent.opponentCells = [];
	parent.openCells = [];
	parent.potentialChoices = [];
	parent.urgentChoices = [];
	parent.orderedChoices = [];
	parent.computerWinMove = [];
	parent.urgentCell = "";
	parent.noCells = 0;
	
	parent.init = function (order) {
		if (true === parent.initRightLeftTop(parent.isFirstMove(order), order)) {
			parent.chooseSpace();
		}
	};
	
	parent.chooseSpace = function () {
		if (parent.potentialChoices.length > 0) {
			var id = parent.checkForWinMove();
			if ("" !== id) {
				parent.makeChoice(id);
			} else {
				id = parent.processMoveId();
			}
			
			if ("" === parent.urgentCell) {
				parent.initAjaxMoveCall(id); 
			} else {
				parent.makeChoice(parent.urgentCell);
			}
		}
	};
	
	parent.makeChoice = function (id) {
		$elem = $("#" + id);
		$elem.trigger('click');
		parent.clearMemory();
	};
	
	parent.processMoveId = function () {
		var id = parent.analyzeOpponentChoices();
		if ("" === id) {
			id = parent.potentialChoices[0];
		}
		
		return id;
	};
	
	parent.initAjaxMoveCall = function (id) {
		var data = {
			"jsOptionId": id,
			"moveCount": game.order,
			"openCells": parent.openCells,
			"potentialChoices": parent.potentialChoices,
			"urgentChoices": parent.urgentChoices,
			"opponentCells": parent.opponentCells,
			"computerCells": parent.computerCells,
			"orderedChoices": parent.orderedChoices
		}
		
		var func = {
			'afterAjax': function (payload, data) {
				return logic.triggerChoice(payload, data);
			}
		};
		
		var ajaxConfig = {
			'url': '/sample1/logic/index',
			'method': 'GET'
		}
		
		shared.ajax(ajaxConfig, data, func);
	};
	
	parent.triggerChoice = function (payload, data) {
		var id = data.jsOptionId;
		if ("" !== payload.value && null !== payload.value) {
			id = payload.value;
		}
		parent.makeChoice(id);
	};
	
	parent.chooseMostUrgent = function () {
		var id = "";
		if (parent.urgentChoices.length > 0) {
			parent.urgentChoices.sort();
			var choice = parent.urgentChoices[parent.urgentChoices.length - 1];
			id = choice.split("_")[1];
		}
		
		return id;
	};
	
	
	parent.getRecordWithHighestWeightAndFrequency = function (id) {
		var obj = {};
		for (var i = 0; i <= parent.computerWinMove.length; i++) {
			if (typeof parent.computerWinMove[i] !== "undefined") {
				var arr = parent.computerWinMove[i].split("_");
				if (obj.hasOwnProperty(arr[1])) {
					obj[arr[1]] = Number(obj[arr[1]]) + Number(arr[0]);
				} else {
					obj[arr[1]] = Number(arr[0]);
				}
			}
		}
		
		$.each(obj, function (index, value) {
			if (Number(value) >= 3) {
				id = index;
				return;
			}
		});
		
		return id;
	};
	
	parent.chooseWinningOption = function () {
		var id = "";
		if (parent.computerWinMove.length > 0) {
			parent.computerWinMove.sort();
			id = parent.getRecordWithHighestWeightAndFrequency(id);
		}
		
		return id;
	};
	
	parent.analyzeOpponentChoices = function () {
		for (var i = 0; i <= parent.opponentCells.length; i++) {
			var id = parent.opponentCells[i];
			if (typeof id !== "undefined") {
				var r = id.split("r")[1].split("c")[0];
				var c = id.split("c")[1];
				parent.itemsConnected(r, c, 0, "hR", "opp");
				parent.itemsConnected(r, c, 0, "hL", "opp");
				parent.itemsConnected(r, c, 0, "vert", "opp");
				parent.itemsConnected(r, c, 0, "dR", "opp");
				parent.itemsConnected(r, c, 0, "dL", "opp");
				parent.itemsConnected(r, c, 0, "dRneg", "opp");
				parent.itemsConnected(r, c, 0, "dLneg", "opp");
			}
		}
		
		return parent.chooseMostUrgent();
	};
	
	parent.checkForWinMove = function () {
		for (var i = 0; i <= parent.computerCells.length; i++) {
			var id = parent.computerCells[i];
			if (typeof id !== "undefined") {
				var r = id.split("r")[1].split("c")[0];
				var c = id.split("c")[1];
				parent.itemsConnected(r, c, 0, "hR", "comp");
				parent.itemsConnected(r, c, 0, "hL", "comp");
				parent.itemsConnected(r, c, 0, "vert", "comp");
				parent.itemsConnected(r, c, 0, "dR", "comp");
				parent.itemsConnected(r, c, 0, "dL", "comp");
				parent.itemsConnected(r, c, 0, "dRneg", "comp");
				parent.itemsConnected(r, c, 0, "dLneg", "comp");
			}
		}
		
		return parent.chooseWinningOption(); 
	};
	
	parent.itemsConnected = function (r, c, weight, type, who) {
		var id = "";
		if ("hR" === type) {
			c = Number(c) + 1;
			id = "r" + r + "c" + c;
		}
		
		if ("hL" === type) {
			c = Number(c) - 1;
			id = "r" + r + "c" + c;
		}
		
		if ("vert" === type) {
			r = Number(r) - 1;
			id = "r" + r + "c" + c;
		}
		
		if ("dR" === type) {
			r = Number(r) - 1;
			c = Number(c) + 1;
			id = "r" + r + "c" + c;
		}
		
		if ("dL" === type) {
			r = Number(r) - 1;
			c = Number(c) - 1;
			id = "r" + r + "c" + c;
		}
		
		if ("dRneg" === type) {
			r = Number(r) + 1;
			c = Number(c) + 1;
			id = "r" + r + "c" + c;
		}
		
		if ("dLneg" === type) {
			r = Number(r) + 1;
			c = Number(c) - 1;
			id = "r" + r + "c" + c;
		}
		
		if ("opp" === who) {
			parent.processForOpponentData(weight, id, r, c, type, who);	
		} else if ("comp" === who) {
			parent.processForComputerData(weight, id, r, c, type, who);	
		}
	};
	
	parent.processForComputerData = function (weight, id, r, c, type, who) {
		if (-1 !== $.inArray(id, parent.computerCells)) {
			weight = Number(weight) + 1;
			parent.itemsConnected(r, c, weight, type, who);
		} else {
			parent.validateForWinningCell(r, c, weight);
		}
	};
	
	parent.processForOpponentData = function (weight, id, r, c, type, who) {
		if (-1 !== $.inArray(id, parent.opponentCells)) {
			weight = Number(weight) + 1;
			parent.itemsConnected(r, c, weight, type, who);
		} else {
			parent.validateForUrgentCell(r, c, weight);
		}
	};
	
	parent.validateForWinningCell = function (r, c, weight) {
		var newC = "r" + r + "c" + c;
		if (-1 !== $.inArray(newC, parent.openCells)) {
			newC = weight + "_r" + r + "c" + c;
			parent.computerWinMove.push(newC);
		}
	};
	
	parent.validateForUrgentCell = function (r, c, weight) {
		var newC = "r" + r + "c" + c;
		if (-1 !== $.inArray(newC, parent.openCells)) {
			if (Number(weight) >= 2) {
				parent.urgentCell = "r" + r + "c" + c;
			}
			newC = weight + "_r" + r + "c" + c;
			parent.urgentChoices.push(newC);
		}
	};
	
	parent.clearMemory = function () {
		parent.computerCells = [];
		parent.opponentCells = [];
		parent.openCells = [];
		parent.potentialChoices = [];
		parent.urgentChoices = [];
		parent.computerWinMove = [];
		parent.noCells = true;
		parent.urgentCell = "";
	};
	
	parent.initRightLeftTop = function (moved, order) {
		if (false === moved) {
			parent.getChosenCells();
			parent.getOpenHorizSpaces("sub");
			parent.getOpenHorizSpaces("add");
			parent.getOpenVertSpaces();
			
			return true;
		}
	};
	
	parent.getOpenVertSpaces = function () {
		for (var i2 = 1; i2 <= 3; i2++) {
			for (var i = 0; i <= parent.opponentCells.length; i++) {
				var id = parent.opponentCells[i];
				if (typeof id !== "undefined") {
					var r = id.split("r")[1].split("c")[0] - Number(i2);
					var c = id.split("c")[1];
					parent.validateCell(r, c, parent.openCells);
				}
			}
		}
	};
	
	parent.getOpenHorizSpaces = function (dir) {
		for (var i2 = 1; i2 <= 3; i2++) {
			for (var i = 0; i <= parent.opponentCells.length; i++) {
				var id = parent.opponentCells[i];
				if (typeof id !== "undefined") {
					var r = id.split("r")[1].split("c")[0];
					var c = id.split("c")[1] - Number(i2);
					if ("add" === dir) {
						c = id.split("c")[1] + Number(i2);
					}
					parent.validateCell(r, c);
				}
			}
		}
	};
	
	parent.validateCell = function (r, c) {
		var newC = "r" + r + "c" + c;
		if (-1 !== $.inArray(newC, parent.openCells)) {
			parent.potentialChoices.push(newC);
		}
	}
	
	parent.isFirstMove = function (order) {
		if ((false === game.compFirst && 1 === Number(order)) 
			|| (true === game.compFirst && 2 === Number(order))) {
			var $elem = "";
			if ("" === $("#r6c4").attr('data-chosenBy')) {
				$elem = $("#r6c4");
			} else if ("" === $("#r6c5").attr('data-chosenBy')) {
				$elem = $("#r6c5");
			}
			
			$elem.trigger('click');
			return true;
		}
		
		return false;
	};
	
	parent.getChosenCells = function () {
		var count = 0
		$(".cell").each(function (i, elem) {
			var id = $(elem).prop("id");
			var chosenBy = $(elem).attr('data-chosenBy');
			if (1 === Number(chosenBy)) {
				parent.opponentCells.push(id);
				parent.populateOrderedChoices(elem, id, chosenBy);
			}
			
			if (2 === Number(chosenBy)) {
				parent.computerCells.push(id);
				parent.populateOrderedChoices(elem, id, chosenBy);
			}
			
			if ("" === chosenBy 
			    && 0 === Number($(elem).attr('data-blocked'))) {
				parent.openCells.push(id);
			}
			
			if ("" !== chosenBy) {
				count++;
			}
		});
		parent.noCells = count;
	};
	
	parent.populateOrderedChoices = function (elem, id, chosenBy) {
		var order_id = $(elem).attr('data-order') + "_" + chosenBy + "_" + id;
		if (-1 === $.inArray(order_id, parent.orderedChoices)) {
			parent.orderedChoices.push(order_id);
		}
	};
	
	return parent;
})(logic || {}, $, shared);
                    
home.py: This file generates the game board on initialize; resets the database, if chosen; queries all wins, losses, and draws in the Sqlite database; and handles static resources.

from django.http import HttpResponse, JsonResponse
from django.template import loader
from ..shared.sharedresources import SharedResources
from sample1.models import Moves
from sample1.models import Winner
import json
from django.shortcuts import render
import random

class Home:
    
    def index(self, request):
        home = loader.get_template('home/home.html')
        shared_resources = SharedResources()
        header, footer = shared_resources.get_page_resources();
        rand_number = random.randint(0,1000)
        resources = {
            'rand': rand_number, 
            'js': self.js_resources(rand_number), 
            'css': self.css_resources(rand_number)
        }
        combined = (header.render(resources, request) 
                    + home.render({}, request) 
                    + footer.render(resources, request))
        
        return HttpResponse(combined)
    
    def start_game(self, request):
        game_board = loader.get_template('gameBoard/gameBoard.html')
        context = {
            "rows": self.get_rows(),
            "cells": self.get_cells()
        }
        
        return JsonResponse(
            {
                "gameBoard": game_board.render(context, request), 
                "wins": self.get_wins_and_losses()
            }, safe=False)
        
    def reset_database(self, request):
        Moves.objects.all().delete()
        Winner.objects.all().delete()
        
        return JsonResponse({"reset": 1}, safe=False)
    
    def get_wins_and_losses(self):
        opp_win_count = Winner.objects.filter(**{'winner': "opp"}).count()
        comp_win_count = Winner.objects.filter(**{'winner': "comp"}).count()
        draw_count = Winner.objects.filter(**{'winner': "draw"}).count()
        
        return {"opp": opp_win_count, "comp": comp_win_count, "draw": draw_count}
        
    def get_rows(self):
        return [1,2,3,4,5,6]
    
    def get_cells(self):
        return [1,2,3,4,5,6,7]
    
    def js_resources(self, rand_number):
        return ('<script type="text/javascript" src="/static/js/game/game.js?id=' + str(rand_number) + '" > </script>' +
                '<script type="text/javascript" src="/static/js/home/home.js?id=' + str(rand_number) + '"> </script>')
    
    def css_resources(self, rand_number):
        return ('<link rel="stylesheet" type="text/css" href="/static/css/home/home.css?id=' + str(rand_number) + '">' +
                '<link rel="stylesheet" type="text/css" href="/static/css/game/game.css?id=' + str(rand_number) + '">')              
                    
inputvalidation.py: This file performs all validations

from django.http import HttpResponse, JsonResponse
from django.template import loader
import json
from django.shortcuts import render

class InputValidation:
    
    def getParams(self, request):
        try:
            paramObj = {
                "jsOptionId": self.valid_str_format(request.GET['jsOptionId']),
                "moveCount": self.valid_int(request.GET['moveCount']),
                "openCells": self.valid_list_format(request.GET.getlist('openCells[]')),
                "potentialChoices": self.valid_list_format(request.GET.getlist('potentialChoices[]')),
                "urgentChoices": self.valid_list_format2(request.GET.getlist('urgentChoices[]')),
                "opponentCells": self.valid_list_format(request.GET.getlist('opponentCells[]')),
                "computerCells": self.valid_list_format(request.GET.getlist('computerCells[]')),
                "orderedChoices": self.valid_list_format3(request.GET.getlist('orderedChoices[]'))
            }
            
            return paramObj
        except:
            raise
        
    def valid_winner(self, value):
        try:
            if (False == isinstance(int(value), int) and (1 != value and 2 != value)):
                raise ValueError("incorrect format (1)")
            
            return value
        except ValueError as error:
            raise error
        
    def valid_int(self, value):
        try:
            if (False == isinstance(int(value), int)):
                raise ValueError("incorrect format (2)")
            
            return value
        except ValueError as error:
            raise error
    
    def valid_cell_format(self, val):
        try:
            arr = list(str(val))
            if 4 != len(arr):
                raise ValueError("incorrect format (3)")
            elif 'r' != arr[0] and 'c' != arr[2]:
                 raise ValueError("incorrect format (4)")
            elif False == isinstance(int(arr[1]), int) and False == isinstance(int(arr[3]), int):
                 raise ValueError("incorrect format (5)")
                
            return val    
        except ValueError as error:
            raise error
    
    def valid_list_format(self, array):
        try:
            if (False == isinstance(array, list)):
                raise ValueError("incorrect format (6)")
            
            for val in array: 
                self.valid_cell_format(val)
                
            return array
        except ValueError as error:
            raise error
        
    def valid_list_format2(self, array):
        try:
            if (False == isinstance(array, list)):
                raise ValueError("incorrect format (7)")
            
            for val in array: 
                arr = list(str(val))
                if 6 != len(arr):
                    raise ValueError("incorrect format (8)")
                elif 'r' != arr[2] and 'c' != arr[4]:
                     raise ValueError("incorrect format (9)")
                elif False == isinstance(int(arr[3]), int) and False == isinstance(int(arr[5]), int):
                     raise ValueError("incorrect format (10)")
                
            return array
        except ValueError as error:
            raise error
                
    def valid_list_format3(self, array):
        try:
            if (False == isinstance(array, list)):
                raise ValueError("incorrect format (11)")
            
            for val in array: 
                arr = list(str(val))
                if (8 != len(arr) and 9 != len(arr)):
                    raise ValueError("incorrect format (12)")
                elif (('r' != arr[4] and 'r' != arr[5]) and ('c' != arr[6] and 'c' != arr[7])):
                     raise ValueError("incorrect format (13)")
                elif (False == isinstance(int(arr[0]), int) and False == isinstance(int(arr[1]), int)):
                     raise ValueError("incorrect format (14)")
                
            return array
        except ValueError as error:
            raise error
                
    def valid_str_format(self, value):
        try:
            arr = list(str(value))
            if 4 != len(arr):
                raise ValueError("incorrect format (15)")
            elif 'r' != arr[0] and 'c' != arr[2]:
                 raise ValueError("incorrect format (16)")
            elif False == isinstance(int(arr[1]), int) and False == isinstance(int(arr[3]), int):
                 raise ValueError("incorrect format (17)")
            else:
                return value
        except ValueError as error:
            raise error                    
                    
logic.py: This file runs the machine learning algorithm

from django.http import HttpResponse, JsonResponse
from django.template import loader
from ..validation.inputvalidation import InputValidation
from sample1.models import Moves
from sample1.models import Winner
import json
from django.shortcuts import render
import random

class Logic:
    
    def index(self, request):
        try:
            input_validation = InputValidation()
            value = self.process_previous_wins(input_validation.getParams(request))
            
            return JsonResponse({"value": value}, safe=False)
        except:
            raise
        
    def win(self, request):
        try:
            input_validation = InputValidation()
            ordered_choices = input_validation.valid_list_format3(request.GET.getlist('orderedChoices[]'))
            ordered_choices.sort()
            winner = input_validation.valid_winner(request.GET['winner'])
            self.logWin(winner)
            ret_val = self.iterate_winner_data(ordered_choices, winner)
            
            return JsonResponse({"saved": ret_val}, safe=False)
        except:
            raise
        
    def logWin(self, winner):
        player = "opp" 
        if 2 == int(winner):
            player = "comp"
        elif 3 == int(winner):
            player = "draw"
            
        win = Winner()
        win.winner = player
        win.save()
    
    def save_winner(self, cell, order, chosenBy, winner):
        moves = Moves()
        moves.cell = cell
        moves.moveCount = order
        moves.player = chosenBy
        moves.winner = winner
        moves.save()
        
    def iterate_winner_data(self, ordered_choices, winner):
        try:
            input_validation = InputValidation()
            for value in ordered_choices:
                sub_list = value.split('_')
                order = input_validation.valid_int(sub_list[0])
                chosen_by = input_validation.valid_int(sub_list[1])
                cell = input_validation.valid_cell_format(sub_list[2])
                
                self.save_winner(cell, order, chosen_by, winner)
            
            return 1    
        except:
            return 0
            
        
    def process_previous_wins(self, obj):
        return self.select_next_move_records(obj)
    
    def select_next_move_records(self, obj):
        move_count = obj["moveCount"]
        potential_choices = obj["potentialChoices"]
        open_cells = obj["openCells"]
        moves = Moves.objects.filter(**{'moveCount': move_count})
        weights = {}
        weights_opp = {}
        ret_cell = ""
        highest_weight_comp = 1
        highest_weight_opp = 1
        options = []

        weights, weights_opp, highest_weight_comp = self.populate_objects(
            moves, 
            open_cells, 
            weights, 
            highest_weight_comp, 
            weights_opp, 
            highest_weight_opp)
                    
        retCell = self.use_js_cell(obj, weights, ret_cell)
    
        return self.weigh_options(
            ret_cell, 
            weights,
            potential_choices,
            highest_weight_comp, 
            weights_opp)
        
    def weigh_options(
            self, 
            ret_cell, 
            weights,
            potential_choices,
            highest_weight_comp, 
            weights_opp):
        if len(weights) > 1:
            potentialsList = [x for x in potential_choices 
                 if potential_choices.count(x) > 1]
            
            ###if not potentialsList:
            for cell, heaviest in weights.items():
                if heaviest == highest_weight_comp:
                    if cell in potential_choices and cell not in weights_opp:
                        ret_cell = cell
            ###else:
                ###ret_cell = potentialsList[0]
                
        return ret_cell
        
    
    def use_js_cell(self, obj, weights, ret_cell):
        if (0 == len(weights) or (1 == len(weights) 
            and obj["jsOptionId"] in weights)):
            ret_cell = obj["jsOptionId"]
            
            return ret_cell
    
    def populate_objects(self, moves, open_cells, weights, highest_weight_comp, weights_opp, highest_weight_opp):
        for move in moves:
            if move.cell in open_cells and 2 == move.player:
                if weights.get(move) is None:
                    weights[move.cell] = 1
                else:
                    weights[move.cell] = weights[move.cell] + 1
                    if weights[move.cell] > highest_weight_comp:
                        highest_weight_comp = weights[move.cell]
            elif move.cell in open_cells and 1 == move.player:
                if weights_opp.get(move) is None:
                    weights_opp[move.cell] = 1
                else:
                    weights_opp[move.cell] = weights_opp[move.cell] + 1
                    if weights_opp[move.cell] > highest_weight_opp:
                        highest_weight_opp = weights_opp[move.cell]
                        
        return weights, weights_opp, highest_weight_comp   
                    
model.py: This file holds the ORM for the database tables
 
from django.db import models

class Moves(models.Model):
    cell = models.CharField(max_length=4)
    moveCount = models.IntegerField(default=0)
    player = models.IntegerField(default=0)
    winner = models.IntegerField(default=0)
    
class Winner(models.Model):
    winner = models.CharField(max_length=4)                    
                    
Play! More Projects!