From 66b29c5ad1747e43e448449639d53055ec9483d8 Mon Sep 17 00:00:00 2001 From: Karl Yogi Date: Sat, 13 Aug 2016 07:48:43 +0800 Subject: [PATCH] machine learning AI --- index.html | 23 +- ml.js | 3 +- ttt.js | 599 +++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 555 insertions(+), 70 deletions(-) diff --git a/index.html b/index.html index 862be7d..3a817fe 100644 --- a/index.html +++ b/index.html @@ -5,12 +5,21 @@ Document - - - +
+
+ + + + +
+
+ x win: 0 + o win: 0 + tie: 0 +
+
+ + @@ -22,7 +31,7 @@ function chooseYourRole() { // ***Demo Start*** - board.chooseRole('x'); + board.chooseRole('o'); // ***Demo End*** } diff --git a/ml.js b/ml.js index b1562ab..bd8c24f 100644 --- a/ml.js +++ b/ml.js @@ -1,9 +1,8 @@ - var ml = { "updateConstant": 0.4, "hypothesis": [0.5,0.5,0.5,0.5,0.5,0.5], chooseMove: function() { - + }, getTrainingExamples: function(history){ diff --git a/ttt.js b/ttt.js index bf71df4..2587bb9 100644 --- a/ttt.js +++ b/ttt.js @@ -1,11 +1,255 @@ - var board = { +var ml = { + "updateConstant": 0.4, + "hypothesis": [0.5,0.5,0.5,0.5,0.5,0.5, 0.5], + 'winners': [ + // rows + { + a: 1, + b: 2, + c: 3 + }, { + a: 4, + b: 5, + c: 6 + }, { + a: 7, + b: 8, + c: 9 + }, + + // columns + { + a: 1, + b: 4, + c: 7 + }, { + a: 2, + b: 5, + c: 8 + }, { + a: 3, + b: 6, + c: 9 + }, + + // diagonals + { + a: 1, + b: 5, + c: 9 + }, { + a: 3, + b: 5, + c: 7 + } + ], + checkEnd: function(currentBoard) { + var end = false; + // check win sitution + for (var i = 0; i < ml.winners.length; i++) { + var win = ml.winners[i]; + if (currentBoard[win.a-1] === currentBoard[win.b-1] && currentBoard[win.b-1] === currentBoard[win.c-1]) { + end = true; + } + } + + // check tie situation + if(!end) { + var flag = 0; + for(var i = 0; i<=8; i++) { + if('-' != currentBoard[i]) { + flag++; + } + } + if(9 == flag) { + end = true; + } + } + return end; + }, + getTrainingExamples: function(history) { + var mockPlayer = 'x'; // mock this is the selected player + var trainingExamples = new Array(); + for(var i = 0; i < history.length; i++) { + if(ml.checkEnd(history[i])) { // if end + if(ml.getWinner(history[i]) == mockPlayer) { + trainingExamples.push(new Array(ml.getFeatures(history[i]), 100)); + } else if(false == ml.getWinner(history[i])) { + trainingExamples.push(new Array(ml.getFeatures(history[i]), 0)); + } else { + trainingExamples.push(new Array(ml.getFeatures(history[i]), -100)); + } + } else { // not end + if(i+2 >= (history.length-1)) { + if(false == ml.getWinner(history[(history.length-1)-1])) { + trainingExamples.push(new Array(ml.getFeatures(history[i]), 0)); + } else { + trainingExamples.push(new Array(ml.getFeatures(history[i]), -100)); + } + } else { + trainingExamples.push(new Array(ml.getFeatures(history[i]), ml.evaluateBoard(history[i+2]))); + } + } + } + + return trainingExamples; + }, + evaluateBoard: function(currentBoard) { + var features = ml.getFeatures(currentBoard); + var sum = 0; + for(var i = 0; i< features.length; i++) { + sum += features[i]* ml.hypothesis[i]; + } + return sum; + }, + getBoardArray: function(currentBoard) { + return currentBoard.split(''); + }, + getWinner: function(currentBoard) { + var winner = false; + // check win sitution + for (var i = 0; i < ml.winners.length; i++) { + var win = ml.winners[i]; + if (currentBoard[win.a-1] === currentBoard[win.b-1] && currentBoard[win.b-1] === currentBoard[win.c-1]) { + winner = win[win.a-1]; + break; + } + } + return winner; + }, + getFeatures: function(currentBoard) { + //x1 = number of instances of 2 x's in a row with an open subsequent square + //x2 = number of instances of 2 o's in a row with an open subsequent square + //x3 = number of instances of an x in an open row or column + //x4 = number of instances of an o in an open row or column + //x5 = number of instances of 3 xs in a row + //x6 = number of instances of 3 os in a row + var possibilities = new Array(); + possibilities = ml.getPossibilities(currentBoard); + var x1 = 0 + var x2 = 0 + var x3 = 0 + var x4 = 0 + var x5 = 0 + var x6 = 0 + possibilities.forEach(function(element, index, array){ + var zeros = 0; + var Xs = 0; + var Os = 0; + element.forEach(function(element0, index0, array0){ + if("-" == element0) + zeros++; + else if("x" == element0) + Xs++; + else if('o' == element0) + Os++; + }); + + if (Xs == 2 && zeros == 1) + x1 += 1 + else if( Os == 2 && zeros == 1) + x2 += 1 + else if( Xs == 1 && zeros == 2) + x3 += 1 + else if (Os == 1 && zeros == 2) + x4 += 1 + else if (Xs == 3) + x5 += 1 + else if (Os == 3) + x6 += 1 + }); + return new Array(x1,x2,x3,x4,x5,x6); + + }, + getPossibilities: function(currentBoard) { + var possibilities = new Array(); + possibilities = possibilities.concat(ml.getRows(currentBoard)); + possibilities = possibilities.concat(ml.getColumns(currentBoard)); + possibilities = possibilities.concat(ml.getDiagonals(currentBoard)); + return possibilities; + }, + getBoard: function(currentBoard) { + var thisBoard = new Array(new Array(),new Array(),new Array()); + thisBoard[0] = currentBoard.slice(0,3); + thisBoard[1] = currentBoard.slice(3,6); + thisBoard[2] = currentBoard.slice(6,9); + return thisBoard; + }, + getRows: function(currentBoard) { + currentBoard = ml.getBoard(currentBoard); + return currentBoard; + }, + getColumns: function(currentBoard) { + currentBoard = ml.getBoard(currentBoard); + var columns = new Array(new Array(),new Array(),new Array()); + for(var i=0; i score(a, player); + }); + var top = win[0]; + if (!board[top.b].player) return top.b; + if (!board[top.a].player) return top.a; + if (!board[top.c].player) return top.c; + throw 'not supposed to happen'; + }, + // use cookie record board history + recordHistory:function() { + var boardStatus = board.getStatus(); + boardStatus = boardStatus.join(''); + var history = board.getCookie("history"); + if("" == history) + history = new Array(); + else + history = history.split(','); + history.push(boardStatus); + board.setCookie("history", history, 30); + + }, + getHistory: function(){ + var history = board.getCookie("history"); + if("" == history) + history = new Array(); + else + { + history = history.split(','); + history.forEach(function(element, index, array) { + array[index] = array[index].split(""); + }) + } + return history; + }, + getHypothesis:function() { + var hypothesis = board.getCookie("hypothesis"); + if("" == hypothesis) + hypothesis = ml.hypothesis + else + { + hypothesis = hypothesis.split(","); + hypothesis.forEach(function(element,index,array) { + array[index] = parseFloat(array[index]); + }) + } + return hypothesis; + }, + + getCookie: function(c_name){ + if (document.cookie.length>0){   + c_start=document.cookie.indexOf(c_name + "=")   + if (c_start!=-1){ + c_start=c_start + c_name.length+1   + c_end=document.cookie.indexOf(";",c_start)   + if (c_end==-1) c_end=document.cookie.length   + return unescape(document.cookie.substring(c_start,c_end)) + } + } + return "" + }, + setCookie:function(c_name, value, expiredays){ +  var exdate=new Date(); +  exdate.setDate(exdate.getDate() + expiredays); +  document.cookie=c_name+ "=" + escape(value) + ((expiredays==null) ? "" : ";expires="+exdate.toGMTString()); + }, + getSuccessorsX: function() { + var status = board.getStatus(); + var successor = new Array(); + for(var i = 0; i<8; i++) + { + if('-' == status[i]) + { + status[i] = 'x'; + successor.push(new Array(i+1, status)); + } + } + return status; + }, + + getSuccessors: function(){ + var status = board.getStatus(); + var successor = new Array(); + for(var i = 0; i<8; i++) + { + var temp = status; + if('-' == temp[i]) + { + temp[i] = board.selectedPlayer; + successor.push(new Array(i+1, temp)); + } + } + return successor; + }, + }; // opposite player @@ -103,6 +518,7 @@ function handlePress(id, event) { if (!board.end && board.selectedPlayer && !board[id].player &&player==board.currPlayer) { drawPlayer(board.stage, board[id].x, board[id].y, board.selectedPlayer); board[id].player = board.selectedPlayer; + board.recordHistory(); checkEndCondition(); board.currPlayer = oppositPlayer(board.currPlayer); } @@ -135,7 +551,7 @@ function drawPlaySurface(stage, x, y, id) { } function drawWinner(win, player) { - console.log('draw winner'); + console.log('draw winner '+player); var winLine = new createjs.Shape(); winLine.graphics.setStrokeStyle(3); winLine.graphics.beginStroke(board[player].color); @@ -148,18 +564,47 @@ function drawWinner(win, player) { function checkEndCondition() { if (board.end) return true; + // get winner for (var i = 0; i < board.winners.length; i++) { var win = board.winners[i]; - console.log('' + board[win.a].player + board[win.b].player + board[win.c].player); + // console.log('' + board[win.a].player + board[win.b].player + board[win.c].player); if (!!board[win.a].player && board[win.a].player === board[win.b].player && board[win.b].player === board[win.c].player) { console.log('Win: ' + JSON.stringify(win)); drawWinner(win, board[win.a].player); board.end = true; board.winner = board[win.a].player; + board[win.a].player == "x"?board.addData(1):board.addData(2); + + } + } + + // get tie + if(!board.end) { + var flag = 0; + for(var i = 1; i<=9; i++) { + if(null != board[i].player) { + flag++; + } + } + if(9 == flag) { + board.end = true; + board.addData(3); + console.log("reach a tie"); } } + if(board.end) { clearInterval(board.interval); + if(2 == board.type && board.ml_loop >1) // if machine learning && still got loop number + { + board.ml_loop--; + reset(); + board.interval = setInterval("action()", board.delay); + + // machine training + ml.updateWeights(board.getHistory(),ml.getTrainingExamples(board.getHistory())); + board.setCookie("hypothesis",ml.hypothesis,30); + } } return board.end; } @@ -208,31 +653,6 @@ function score(play, player) { return 0; } -function selectNextPos(player) { - var winners = board.winners.slice(); - var win = winners.sort(function(a, b) { - return score(b, player) > score(a, player); - }); - var top = win[0]; - if (!board[top.b].player) return top.b; - if (!board[top.a].player) return top.a; - if (!board[top.c].player) return top.c; - throw 'not supposed to happen'; -} - -function botTurn() { - var player = oppositPlayer(board.selectedPlayer); - if(player==board.currPlayer) { - // console.log(2); - var pos = selectNextPos(player); - board[pos].player = player; - drawPlayer(board.stage, board[pos].x, board[pos].y, player); - checkEndCondition(); - board.currPlayer = oppositPlayer(board.currPlayer); - } - -} - function oppositPlayer(player) { return 'x'==player?'o':'x'; } @@ -268,6 +688,7 @@ function reset() { board.end = false; board.winner = ""; board.start = false; + board.selectedPlayer = null; } function drawBoard(stage) { @@ -280,6 +701,22 @@ function drawBoard(stage) { } } +function chooseMachineMove() { + var successors = board.getSuccessors(); + var bestSuccessor = successors[0]; + var bestValue = ml.evaluateBoard(bestSuccessor[1]); + + successors.forEach(function(element,index, array){ + value = ml.evaluateBoard(element[1]); + if(value > bestValue) { + bestValue = value; + bestSuccessor = element; + } + }); + + move(bestSuccessor[0]); +} + // 'x' or 'o' @@ -312,6 +749,7 @@ function checkMove(i) { return flag; } +// "x" || "o" || "" function getWinner() { if(board.end) return board.winner; @@ -327,17 +765,56 @@ function isEnd() { } function action() { - if(!board.isSelected()) { - chooseYourRole(); - } - if(board.start&&!board.end) { - if(board.currPlayer == board.selectedPlayer) - { - yourAI(); - }else { - botTurn(oppositPlayer(board.selectedPlayer)) - } - } + switch(board.type) + { + case 1: // common + if(!board.isSelected()) { + chooseYourRole(); + } + if(board.start&&!board.end) { + if(board.currPlayer == board.selectedPlayer) + { + yourAI(); + }else { + board.botTurn(oppositPlayer(board.selectedPlayer)) + } + } + break; + case 2: // machine learning + if(!board.isSelected()) { + chooseYourRole(); + } + + if(board.start&&!board.end) { + + if(board.currPlayer == board.selectedPlayer) + { + yourAI(); + }else { + board.botTurn(oppositPlayer(board.selectedPlayer)) + } + } + break; + case 3: // machine learning action + if(!board.isSelected()) { + chooseYourRole(); + } + + if(board.start&&!board.end) { + + if(board.currPlayer == board.selectedPlayer) + { + machineAI(); + }else { + board.botTurn(oppositPlayer(board.selectedPlayer)) + } + } + break; + } +} + +function machineAI(){ + chooseMachineMove(); } window.onload = function() {