Gomoku JS ('-' * 9) Gomoku 5 in a row game.
Forked from Anton Mudrenok's Pen Gomoku JS.
Forked from Anton Mudrenok's Pen Gomoku JS.
A Pen by Thomas Gak Deluen on CodePen.
Gomoku JS ('-' * 9) Gomoku 5 in a row game.
Forked from Anton Mudrenok's Pen Gomoku JS.
Forked from Anton Mudrenok's Pen Gomoku JS.
A Pen by Thomas Gak Deluen on CodePen.
section | |
.controls | |
.newGameCtrl | |
input(type="checkbox" id="check") | |
label.newMain(for="check") | |
.newMainText new | |
.newContainer | |
.newPlate | |
.boardCellCircle(id="new-O") | |
.newPlate | |
.boardCellCross(id="new-X") | |
.sizeCtrl | |
.newMain | |
.newMainText(id="scale-Up") + | |
.sizeCtrl | |
.newMain | |
.newMainText(id="scale-Down") - | |
.messages | |
.messagesContainer | |
.newMainText(id="message") try to get 5 in a row! | |
.board | |
- for (var x = 0; x < 15; x++) | |
.boardRow | |
- for (var y = 0; y < 15; y++) | |
.boardCol | |
.boardCell(id=x + "-" + y) |
/** | |
* browserify, combines 3 modules | |
*/ | |
(function e(t, n, r) { | |
function s(o, u) { | |
if (!n[o]) { | |
if (!t[o]) { | |
var a = typeof require == "function" && require; | |
if (!u && a) return a(o, !0); | |
if (i) return i(o, !0); | |
var f = new Error("Cannot find module '" + o + "'"); | |
throw f.code = "MODULE_NOT_FOUND", f | |
} | |
var l = n[o] = { | |
exports: {} | |
}; | |
t[o][0].call(l.exports, function(e) { | |
var n = t[o][1][e]; | |
return s(n ? n : e) | |
}, l, l.exports, e, t, n, r) | |
} | |
return n[o].exports | |
} | |
var i = typeof require == "function" && require; | |
for (var o = 0; o < r.length; o++) s(r[o]); | |
return s | |
})({ | |
/** | |
* browserify ends, modules: | |
*/ | |
1: [function(require, module, exports) { | |
module.exports = function() { | |
var win = [ | |
[1, 1, 1, 1, 1] | |
]; | |
var unCovered4 = [ | |
[0, 1, 1, 1, 1, 0] | |
]; | |
var unCovered3 = [ | |
[0, 1, 1, 1, 0, 0], | |
[0, 0, 1, 1, 1, 0], | |
[0, 1, 0, 1, 1, 0], | |
[0, 1, 1, 0, 1, 0] | |
]; | |
var unCovered2 = [ | |
[0, 0, 1, 1, 0, 0], | |
[0, 1, 0, 1, 0, 0], | |
[0, 0, 1, 0, 1, 0], | |
[0, 1, 1, 0, 0, 0], | |
[0, 0, 0, 1, 1, 0], | |
[0, 1, 0, 0, 1, 0] | |
]; | |
var covered4 = [ | |
[-1, 1, 0, 1, 1, 1], | |
[-1, 1, 1, 0, 1, 1], | |
[-1, 1, 1, 1, 0, 1], | |
[-1, 1, 1, 1, 1, 0], | |
[0, 1, 1, 1, 1, -1], | |
[1, 0, 1, 1, 1, -1], | |
[1, 1, 0, 1, 1, -1], | |
[1, 1, 1, 0, 1, -1] | |
]; | |
var covered3 = [ | |
[-1, 1, 1, 1, 0, 0], | |
[-1, 1, 1, 0, 1, 0], | |
[-1, 1, 0, 1, 1, 0], | |
[0, 0, 1, 1, 1, -1], | |
[0, 1, 0, 1, 1, -1], | |
[0, 1, 1, 0, 1, -1], | |
[-1, 1, 0, 1, 0, 1, -1], | |
[-1, 0, 1, 1, 1, 0, -1], | |
[-1, 1, 1, 0, 0, 1, -1], | |
[-1, 1, 0, 0, 1, 1, -1] | |
]; | |
(function() { //add same combinations for another player | |
var allCombos = [win, unCovered4, unCovered3, unCovered2, covered4, covered3]; | |
for (var k = 0; k < allCombos.length; k++) { | |
var temp = []; | |
for (var j = 0; j < allCombos[k].length; j++) { | |
var tmp = []; | |
for (var i = 0; i < allCombos[k][j].length; i++) | |
tmp[i] = -allCombos[k][j][i]; | |
temp.push(tmp); | |
} | |
for (var m = 0; m < temp.length; m++) { | |
allCombos[k].push(temp[m]); | |
} | |
} | |
}()); | |
var valueCombo = function(w, u2, u3, u4, c3, c4) { | |
if (w > 0) return 1000000000; | |
if (u4 > 0) return 100000000; | |
if (c4 > 1) return 10000000; | |
if (u3 > 0 && c4 > 0) return 1000000; | |
if (u3 > 1) return 100000; | |
if (u3 == 1) { | |
if (u2 == 3) return 40000; | |
if (u2 == 2) return 38000; | |
if (u2 == 1) return 35000; | |
return 3450; | |
} | |
if (c4 == 1) { | |
if (u2 == 3) return 4500; | |
if (u2 == 2) return 4200; | |
if (u2 == 1) return 4100; | |
return 4050; | |
} | |
if (c3 == 1) { | |
if (u2 == 3) return 3400; | |
if (u2 == 2) return 3300; | |
if (u2 == 1) return 3100; | |
} | |
if (c3 == 2) { | |
if (u2 == 2) return 3000; | |
if (u2 == 1) return 2900; | |
} | |
if (c3 == 3) { | |
if (u2 == 1) return 2800; | |
} | |
if (u2 == 4) return 2700; | |
if (u2 == 3) return 2500; | |
if (u2 == 2) return 2000; | |
if (u2 == 1) return 1000; | |
return 0; | |
}; | |
var findArray = function(arr, inArr) { | |
var fCount = arr.length; | |
var sCount = inArr.length; | |
var k; | |
for (var i = 0; i <= fCount - sCount; i++) { | |
k = 0; | |
for (var j = 0; j < sCount; j++) { | |
if (arr[i + j] == inArr[j]) k++; | |
else break; | |
} | |
if (k == sCount) return true; | |
} | |
return false; | |
}; | |
var isAnyInArrays = function(combos, arr) { | |
for (var i = 0; i < combos.length; i++) { | |
if (findArray(arr, combos[i])) return true; | |
} | |
return false; | |
}; | |
var combinations = {}; | |
combinations.winValue = 1000000000; | |
combinations.valuePosition = function(arr1, arr2, arr3, arr4) { // 4 directions | |
var w = 0, | |
u2 = 0, | |
u3 = 0, | |
u4 = 0, | |
c3 = 0, | |
c4 = 0; | |
var allArr = [arr1, arr2, arr3, arr4]; | |
for (var i = 0; i < allArr.length; i++) { | |
if (isAnyInArrays(win, allArr[i])) { | |
w++; | |
continue; | |
} | |
if (isAnyInArrays(covered4, allArr[i])) { | |
c4++; | |
continue; | |
} | |
if (isAnyInArrays(covered3, allArr[i])) { | |
c3++; | |
continue; | |
} | |
if (isAnyInArrays(unCovered4, allArr[i])) { | |
u4++; | |
continue; | |
} | |
if (isAnyInArrays(unCovered3, allArr[i])) { | |
u3++; | |
continue; | |
} | |
if (isAnyInArrays(unCovered2, allArr[i])) { | |
u2++; | |
} | |
} | |
return valueCombo(w, u2, u3, u4, c3, c4); | |
}; | |
return combinations; | |
}; | |
}, {}], | |
2: [function(require, module, exports) { | |
Array.matrix = function(m, n, initial) { | |
var a, i, j, mat = []; | |
for (i = 0; i < m; i++) { | |
a = []; | |
for (j = 0; j < n; j++) { | |
a[j] = initial; | |
} | |
mat[i] = a; | |
} | |
return mat; | |
}; | |
var initCombinations = require('./combinations'); | |
module.exports = function(player) { | |
var gameSize = 5; // 5 in line | |
var ring = 1; // ring size around current cells | |
var win = false; | |
var cellsCount = 15; | |
var curState = Array.matrix(15, 15, 0); | |
var complexity = 1; | |
var maxPlayer = player || -1; // X = 1, O = -1 | |
var combinations = initCombinations(); | |
if (maxPlayer === -1) curState[7][7] = 1; | |
var checkWin = function() { | |
for (var i = 0; i < cellsCount; i++) { | |
for (var j = 0; j < cellsCount; j++) { | |
if (curState[i][j] == 0) continue; | |
var playerVal = combinations.valuePosition( | |
getCombo(curState, curState[i][j], i, j, 1, 0), | |
getCombo(curState, curState[i][j], i, j, 0, 1), | |
getCombo(curState, curState[i][j], i, j, 1, 1), | |
getCombo(curState, curState[i][j], i, j, 1, -1) | |
); | |
if (playerVal === combinations.winValue) { | |
win = true; | |
} | |
} | |
} | |
}; | |
var miniMax = function minimax(node, depth, player, parent) { | |
if (depth == 0) return heuristic(node, parent); | |
var alpha = Number.MIN_VALUE; | |
var childs = getChilds(node, player); | |
for (var i = 0; i < childs.length; i++) { | |
alpha = Math.max(alpha, -minimax(childs[i], depth - 1, -player, node)); | |
} | |
return alpha; | |
}; | |
var isAllSatisfy = function(candidates, pointX, pointY) { | |
var counter = 0; | |
for (var i = 0; i < candidates.length; i++) { | |
if (pointX != candidates[i][0] || pointY != candidates[i][1]) counter++; | |
} | |
return counter == candidates.length; | |
}; | |
var getChilds = function(parent, player) { | |
var children = []; | |
var candidates = []; | |
for (var i = 0; i < cellsCount; i++) { | |
for (var j = 0; j < cellsCount; j++) { | |
if (parent[i][j] != 0) { | |
for (var k = i - ring; k <= i + ring; k++) { | |
for (var l = j - ring; l <= j + ring; l++) { | |
if (k >= 0 && l >= 0 && k < cellsCount && l < cellsCount) { | |
if (parent[k][l] == 0) { | |
var curPoint = [k, l]; | |
var flag = isAllSatisfy(candidates, curPoint[0], curPoint[1]); | |
if (flag) candidates.push(curPoint); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
for (var f = 0; f < candidates.length; f++) { | |
var tmp = Array.matrix(cellsCount, cellsCount, 0); | |
for (var m = 0; m < cellsCount; m++) { | |
for (var n = 0; n < cellsCount; n++) { | |
tmp[m][n] = parent[m][n]; | |
} | |
} | |
tmp[candidates[f][0]][candidates[f][1]] = -player; | |
children.push(tmp); | |
} | |
return children; | |
}; | |
var getCombo = function(node, curPlayer, i, j, dx, dy) { | |
var combo = [curPlayer]; | |
for (var m = 1; m < gameSize; m++) { | |
var nextX1 = i - dx * m; | |
var nextY1 = j - dy * m; | |
if (nextX1 >= cellsCount || nextY1 >= cellsCount || nextX1 < 0 || nextY1 < 0) break; | |
var next1 = node[nextX1][nextY1]; | |
if (node[nextX1][nextY1] == -curPlayer) { | |
combo.unshift(next1); | |
break; | |
} | |
combo.unshift(next1); | |
} | |
for (var k = 1; k < gameSize; k++) { | |
var nextX = i + dx * k; | |
var nextY = j + dy * k; | |
if (nextX >= cellsCount || nextY >= cellsCount || nextX < 0 || nextY < 0) break; | |
var next = node[nextX][nextY]; | |
if (next == -curPlayer) { | |
combo.push(next); | |
break; | |
} | |
combo.push(next); | |
} | |
return combo; | |
}; | |
var heuristic = function(newNode, oldNode) { | |
for (var i = 0; i < cellsCount; i++) { | |
for (var j = 0; j < cellsCount; j++) { | |
if (newNode[i][j] != oldNode[i][j]) { | |
var curCell = newNode[i][j]; | |
var playerVal = combinations.valuePosition( | |
getCombo(newNode, curCell, i, j, 1, 0), | |
getCombo(newNode, curCell, i, j, 0, 1), | |
getCombo(newNode, curCell, i, j, 1, 1), | |
getCombo(newNode, curCell, i, j, 1, -1) | |
); | |
newNode[i][j] = -curCell; | |
var oppositeVal = combinations.valuePosition( | |
getCombo(newNode, -curCell, i, j, 1, 0), | |
getCombo(newNode, -curCell, i, j, 0, 1), | |
getCombo(newNode, -curCell, i, j, 1, 1), | |
getCombo(newNode, -curCell, i, j, 1, -1) | |
); | |
newNode[i][j] = -curCell; | |
return 2 * playerVal + oppositeVal; | |
} | |
} | |
} | |
return 0; | |
}; | |
var getLogic = {}; | |
getLogic.winState = ""; | |
getLogic.makeAnswer = function(x, y) { | |
var that = this; | |
curState[x][y] = maxPlayer; | |
checkWin(); | |
if (win) { | |
that.winState = "you win"; | |
return ""; | |
} | |
var answ = [-1, -1]; | |
var c = getChilds(curState, maxPlayer); | |
var maxChild = -1; | |
var maxValue = Number.MIN_VALUE; | |
for (var k = 0; k < c.length; k++) { | |
var curValue = miniMax(c[k], 0, -maxPlayer, curState); | |
if (complexity > 1) { | |
//var curValue2 = miniMax(c[k], complexity - 1, -maxPlayer, curState); | |
//use it for more complex game! | |
} | |
if (maxValue < curValue) { | |
maxValue = curValue; | |
maxChild = k; | |
} | |
} | |
for (var i = 0; i < cellsCount; i++) { | |
for (var j = 0; j < cellsCount; j++) { | |
if (c[maxChild][i][j] != curState[i][j]) { | |
answ[0] = i; | |
answ[1] = j; | |
curState[answ[0]][answ[1]] = -maxPlayer; | |
checkWin(); | |
if (win) { | |
that.winState = "you lost"; | |
} | |
return answ; | |
} | |
} | |
} | |
return answ; | |
}; | |
return getLogic; | |
}; | |
}, { | |
"./combinations": 1 | |
}], | |
3: [function(require, module, exports) { | |
$(document).ready(function() { | |
var initLogic = require('./gomoku/logic'); | |
var logic = initLogic(); | |
$("#7-7").addClass("boardCellCross"); | |
var currValue = -1; // player - O, computer - X | |
var gameOver = false; | |
$('div.boardCol').mousedown(handleMouseDown); | |
function handleMouseDown(e) { | |
if (gameOver) return ""; | |
var cell = $(this); | |
if (cell.children().hasClass("boardCellCircle")) return ""; | |
if (cell.children().hasClass("boardCellCross")) return ""; | |
var indexes = (cell.children().attr('id')).split("-"); | |
var answer = logic.makeAnswer(indexes[0], indexes[1]); | |
if (answer !== "") { | |
var getedId = '#' + answer[0] + '-' + answer[1]; | |
$(getedId).addClass(deserve()); | |
} else currValue *= -1; | |
cell.children().addClass(deserve()); | |
function deserve() { | |
currValue *= -1; | |
if (currValue === 1) { | |
return "boardCellCross"; | |
} | |
return "boardCellCircle"; | |
} | |
if (logic.winState !== "") { | |
var message = $("#message"); | |
message.text(logic.winState); | |
gameOver = true; | |
message.removeClass("looseState"); | |
if (logic.winState === "you lost") { | |
message.addClass("looseState"); | |
} | |
} | |
} | |
$("#scale-Up").click(handleScale); | |
$("#scale-Down").click(handleScale); | |
function handleScale(e) { | |
var value = 100; | |
var minValue = 300; | |
var delta = $(this).attr('id').split("-")[1]; | |
var board = $(".board"); | |
var controls = $(".controls"); | |
if (delta === "Up") { | |
board.width(board.width() + value); | |
board.height(board.height() + value); | |
controls.width(controls.width() + value); | |
controls.height(controls.height() + value / 15); | |
} | |
if (delta === "Down" && board.width() > minValue) { | |
board.width(board.width() - value); | |
board.height(board.height() - value); | |
controls.width(controls.width() - value); | |
controls.height(controls.height() - value / 15); | |
} | |
} | |
$("#new-O").parent().click(handleNewGame); | |
$("#new-X").parent().click(handleNewGame); | |
function handleNewGame(e) { | |
var index = ($(this).children().attr('id')).split("-")[1]; | |
$(".boardCell").removeClass("boardCellCross boardCellCircle"); | |
gameOver = false; | |
$("#message").text(""); | |
if (index === "O") { | |
logic = initLogic(); | |
$("#7-7").addClass("boardCellCross"); | |
currValue = -1; | |
} | |
if (index === "X") { | |
logic = initLogic(1); | |
currValue = 1; | |
} | |
$("#check").prop('checked', false); | |
} | |
}); | |
}, { | |
"./gomoku/logic": 2 | |
}] | |
}, {}, [3]) |
@import "bourbon"; | |
@import url(http://fonts.googleapis.com/css?family=Roboto:700); | |
$FlatWetAsphalt : #34495e; | |
$FlatAlizarin : #e74c3c; | |
$FlatMidnightBlue : #2c3e50; | |
$FlatEmerald : #2ecc71; | |
$FlatClouds: #ecf0f1; | |
$mainFont: 'Roboto', sans-serif; | |
$boardWidth: 400px; | |
$boardSize: 15; | |
$boardElementWidth: 3px; | |
body { | |
margin: 0; | |
padding: 0; | |
background-color: $FlatMidnightBlue; | |
min-width: 300px; | |
} | |
section { | |
margin-top: 10px; | |
} | |
.board { | |
margin: auto; | |
margin-top: 10px; | |
width: $boardWidth; | |
height: $boardWidth; | |
} | |
.boardRow { | |
width: 100%; | |
height: 100% / $boardSize; | |
border-bottom: 1px solid $FlatMidnightBlue; | |
box-sizing: border-box; | |
} | |
.boardCol { | |
$columnWidth : 100%; | |
width: $columnWidth / $boardSize; | |
height: 100%; | |
background-color: $FlatWetAsphalt; | |
float: left; | |
border-radius: 3px; | |
border-left: 1px solid $FlatMidnightBlue; | |
box-sizing: border-box; | |
} | |
.boardCellCross { | |
$cellWidth : 70% * 1.41; | |
margin: auto; | |
margin-top: (100% - $cellWidth) / 2; | |
width: $boardElementWidth; | |
height: $cellWidth; | |
background-color: $FlatEmerald; | |
transform: rotate(-45deg); | |
border-radius: $boardElementWidth / 2; | |
&:before { | |
background-color: inherit; | |
border-radius: inherit; | |
height: inherit; | |
width: inherit; | |
position: absolute; | |
content: ""; | |
transform: rotate(90deg); | |
} | |
} | |
.boardCellCircle { | |
$cellWidth : 70%; | |
margin: (100% - $cellWidth) / 2; | |
width: $cellWidth; | |
height: $cellWidth; | |
border: $boardElementWidth solid $FlatAlizarin; | |
border-radius: 50%; | |
box-sizing: border-box; | |
} | |
.controls { | |
width: $boardWidth; | |
height: $boardWidth / $boardSize; | |
margin: auto; | |
} | |
.newGameCtrl, .sizeCtrl, .messages { | |
position: relative; | |
display: inline-block; | |
height: 100%; | |
width: 100% / 15 * 1; | |
} | |
.sizeCtrl,.messages { | |
float:right; | |
} | |
.messages { | |
width: 100% /15 *11; | |
z-index: 0; | |
} | |
#check { | |
display: none; | |
} | |
.newContainer { | |
z-index: 10; | |
} | |
.newMain, .sizeScale, .messagesContainer { | |
width: 100%; | |
height: 100%; | |
position: absolute; | |
z-index: 50; | |
display: table; | |
text-align: center; | |
} | |
.newMain, .sizeScale { | |
cursor: pointer; | |
background-color: $FlatEmerald; | |
border-radius: 50%; | |
&:hover { | |
background-color: $FlatAlizarin; | |
} | |
} | |
.newMainText { | |
font-family: $mainFont; | |
font-size: 13px; | |
color: $FlatMidnightBlue; | |
display: table-cell; | |
vertical-align: middle; | |
} | |
.messagesContainer .newMainText { | |
color: $FlatEmerald; | |
} | |
.messagesContainer .looseState { | |
color: $FlatAlizarin; | |
} | |
.newPlate { | |
width: 100%; | |
height: 100%; | |
opacity: 0; | |
@include transition(all 0.2s ease-in); | |
@include position(absolute, 0 0 0 0); | |
z-index: 5; | |
&:hover { | |
background-color: $FlatWetAsphalt; | |
} | |
} | |
$base: 0; | |
@for $i from 1 through 2 { | |
.newPlate:nth-of-type(#{$i}) { | |
@include transition-delay(#{$base + s}); | |
} | |
$base: $base + 0.3; | |
} | |
#check:checked ~ .newMain + .newContainer { | |
.newPlate { | |
opacity: 1; | |
} | |
.newPlate:nth-of-type(1) { | |
left: 100%; | |
} | |
.newPlate:nth-of-type(2) { | |
left: 200%; | |
} | |
} |