From 9fe0832d7620bb6a8f45527539d998bce8e54789 Mon Sep 17 00:00:00 2001 From: KingOfDog Date: Mon, 8 Apr 2019 21:54:07 +0200 Subject: [PATCH] Initial commit --- Civilization.js | 162 +++ CivilizationAI.js | 14 + Game.js | 550 ++++++++ GameElement.js | 62 + NeuralNetwork.js | 202 +++ Tile.js | 104 ++ Warrior.js | 247 ++++ assets/farm.svg | 1 + assets/farmer.svg | 69 + assets/hoplite.svg | 82 ++ assets/knight.svg | 53 + assets/servant.svg | 1 + assets/tower_big.svg | 1 + assets/tower_small.svg | 54 + assets/townhall.svg | 48 + assets/tree.svg | 104 ++ index.html | 20 + index.js | 28 + main.js | 196 +++ package-lock.json | 40 + package.json | 16 + style.css | 389 +++++ synaptic.min.js | 3048 ++++++++++++++++++++++++++++++++++++++++ 23 files changed, 5491 insertions(+) create mode 100644 Civilization.js create mode 100644 CivilizationAI.js create mode 100644 Game.js create mode 100644 GameElement.js create mode 100644 NeuralNetwork.js create mode 100644 Tile.js create mode 100644 Warrior.js create mode 100644 assets/farm.svg create mode 100644 assets/farmer.svg create mode 100644 assets/hoplite.svg create mode 100644 assets/knight.svg create mode 100644 assets/servant.svg create mode 100644 assets/tower_big.svg create mode 100644 assets/tower_small.svg create mode 100644 assets/townhall.svg create mode 100644 assets/tree.svg create mode 100644 index.html create mode 100644 index.js create mode 100644 main.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 style.css create mode 100644 synaptic.min.js diff --git a/Civilization.js b/Civilization.js new file mode 100644 index 0000000..31192b3 --- /dev/null +++ b/Civilization.js @@ -0,0 +1,162 @@ +class Civilization { + constructor(player, game, x, y, addEnvironment = true, addTownhall = true) { + this.player = player; + this.game = game; + this.origin = {x: x, y: y}; + this.uuid = guid(); + + this.color = player.color; + this.balance = 10; + + if(addEnvironment) { + console.log(x, y); + this.spawn(); + } + + console.log(addTownhall); + if(addTownhall) + this.townhall = new Townhall(this, this.game); + } + + spawn() { + const field = this.game.field; + field[this.origin.x][this.origin.y].overtake(this); + field[this.origin.x][this.origin.y].getAdjacentTiles().forEach(tile => tile.overtake(this)); + } + + destroy() { + this.game.field.forEach(col => { + col.forEach(tile => { + if(tile.claimedBy === this) { + tile.claimedBy = null; + + if(tile.object) { + if(tile.object instanceof Warrior) + tile.object.kill(); + else if(tile.object instanceof Building) + tile.object.destroy(); + else if(tile.object instanceof Tree) + tile.object.cutDown(); + } + } + }) + }) + } + + getBalanceChange() { + let balanceChange = this.getSize(); + + balanceChange += 4 * this.getFarms().length; + + for(let tower of this.getTowers()) { + balanceChange -= tower.costsPerRound; + } + + for(let troop of this.getTroops()) { + balanceChange -= troop.costsPerRound; + } + + balanceChange -= this.getTreeCount(); + + return balanceChange; + } + + getCivilizationTiles() { + const tiles = []; + + for(let x = 0; x < this.game.fieldSize.x; x++) { + for(let y = 0; y < this.game.fieldSize.y; y++) { + if(!!this.game.field[x][y].claimedBy && this.game.field[x][y].claimedBy.uuid === this.uuid) { + tiles.push(this.game.field[x][y]); + } + } + } + + return tiles; + } + + getFarms() { + return Object.values(this.game.buildings).filter(building => building instanceof Farm && building.civilization === this); + } + + getSize() { + return this.getCivilizationTiles().length; + } + + getTowers() { + return []; + } + + getTreeCount() { + return this.getCivilizationTiles().filter(tile => !!tile.object && tile.object instanceof Tree).length; + } + + getTroops() { + return Object.values(this.game.warriors).filter(warrior => warrior.civilization === this); + } + + processRound() { + this.balance += this.getBalanceChange(); + + if(this.balance <= 0) { + this.getTroops().forEach(troop => { + troop.kill(); + }); + } + } + + select(selected) { + this.isSelected = selected; + + // TODO: Update UI + } + + split() { + let tiles = this.getCivilizationTiles(); + const initialSize = tiles.length; + + console.log('hallo welt', tiles.length, tiles[0].getAdjacentCivilizationTiles(this).length); + + if(tiles.length === tiles[0].getAdjacentCivilizationTiles(this).length + 1 && !!this.townhall) + return; + + const oldIndex = game.civilizations[this.player.uuid].findIndex(civ => civ.uuid === this.uuid); + game.civilizations[this.player.uuid].splice(oldIndex, 1); + if(tiles.length === 0) + return; + + const newCivs = []; + + if(!!this.townhall) + this.townhall.destroy(); + while(tiles.length > 0) { + console.log('hey'); + + const civTiles = [tiles[0], ...tiles[0].getAdjacentCivilizationTiles(this)]; + tiles = tiles.filter(tile => !civTiles.includes(tile)); + + const availableTiles = civTiles.filter(tile => !tile.object); + let newOrigin = civTiles[Math.floor(Math.random() * civTiles.length)]; + if(availableTiles.length > 0) { + newOrigin = availableTiles[Math.floor(Math.random() * availableTiles.length)]; + } + console.log(availableTiles.length > 0); + const newCivilization = game.addCivilization(this.player, newOrigin.pos.x, newOrigin.pos.y, this.constructor !== CivilizationAI, false, availableTiles.length > 0); + newCivs.push(newCivilization); + + newCivilization.balance = Math.round(this.balance * (civTiles.length / initialSize)); + + civTiles.forEach(tile => { + tile.claimedBy = newCivilization; + if(!!tile.object && (tile.object instanceof Warrior || tile.object instanceof Tower)) { + tile.object.civilization = newCivilization; + } + }); + } + + newCivs.forEach(civ => { + + }); + + } +} \ No newline at end of file diff --git a/CivilizationAI.js b/CivilizationAI.js new file mode 100644 index 0000000..bc4378a --- /dev/null +++ b/CivilizationAI.js @@ -0,0 +1,14 @@ +class CivilizationAI extends Civilization { + constructor(player, game, x, y, addEnvironment, addTownhall) { + super(player, game, x, y, addEnvironment, addTownhall); + } + + processRound() { + const availableWarriors = Object.values(this.game.warriors).filter(warrior => warrior.civilization === this); + availableWarriors.forEach(warrior => { + + }); + + super.processRound(); + } +} \ No newline at end of file diff --git a/Game.js b/Game.js new file mode 100644 index 0000000..7094fd3 --- /dev/null +++ b/Game.js @@ -0,0 +1,550 @@ +/** + * @property {Node} btnTroops + * @property {Node} btnFarmer + * @property {Node} btnHoplite + * @property {Node} btnServant + * @property {Node} btnKnight + * @property {Node} btnTowers + * @property {Node} btnFarm + * @property {Node} btnTowerSmall + * @property {Node} btnTowerBig + */ +class Game { + constructor() { + this.fieldSize = {x: 20, y: 10}; + this.scale = 1; + this.deltaX = 0; + this.deltaY = 0; + + this.warriors = {}; + this.buildings = {}; + this.field = []; + this.civilizations = {}; + this.trees = {}; + + this.round = 1; + + this.hexagonSize = {width: 27.85714285714286, padding: 32.16760145166612, offset: 6.9285714285}; + this.initializeElements(); + this.resize(); + this.createHexagons(); + + this.registerEventListener(); + } + + initializeElements() { + this.elements = {}; + + this.container = body.createChild('div', 'gameContainer'); + this.gameStats = this.container.createChild('div', 'gameStats'); + this.viewContainer = this.container.createChild('div', 'viewContainer'); + this.view = this.viewContainer.createChild('div', 'view'); + this.playground = this.view.createChild('ul', 'playground'); + this.btns = this.container.createChild('div', 'controls'); + + this.elements.roundParent = this.gameStats.createChild('h3', ['gameStat', 'left'], 'Round: '); + this.elements.round = this.elements.roundParent.createChild('span', 'count', '1'); + + this.elements.balanceParent = this.gameStats.createChild('h3', ['gameStat', 'center']); + this.elements.balance = this.elements.balanceParent.createChild('span', 'count'); + this.elements.balanceChange = this.elements.balanceParent.createChild('span', ['count', 'text-grey']); + + this.elements.player = this.gameStats.createChild('h3', ['gameStat', 'right'], 'Player ' + player.color.toUpperCase()); + + this.nextRoundBtn = this.btns.createChild('button', ['btn', 'btn-primary', 'btn-nextRound'], 'Next Round'); + this.nextRoundBtn.addEventListener('click', () => { + this.nextRound() + }); + + [this.btnTroops, this.btnFarmer, this.btnHoplite, this.btnServant, this.btnKnight] = createTroopsBtn(this.btns); + + [this.btnFarmer, this.btnHoplite, this.btnServant, this.btnKnight].forEach((btn, index) => { + const types = [Farmer, Hoplite, Servant, Knight]; + this.addBtnClickListener(btn, types[index], 'addWarrior'); + }); + + [this.btnTowers, this.btnFarm, this.btnTowerSmall, this.btnTowerBig] = createTowersBtn(this.btns); + + [this.btnFarm, this.btnTowerSmall, this.btnTowerBig].forEach((btn, index) => { + const types = [Farm, SmallTower, BigTower]; + this.addBtnClickListener(btn, types[index], 'addBuilding'); + }); + } + + addBtnClickListener(btn, className, functionName) { + btn.addEventListener('click', () => { + if (!!this.selectedCivilization) { + const newObj = { + type: 'new', + class: className, + function: functionName, + civilization: this.selectedCivilization + }; + + if (!!this.draggingElement && + !!this.selectedObject && + this.selectedObject.type === 'new' && + this.selectedObject.class === newObj.class && + this.selectedObject.civilization === newObj.civilization) + return; + + this.deselectObject(); + this.draggingElement = className.getDraggingObject(this); + this.selectedObject = newObj; + } + }); + } + + addBuilding(x, y, civilization, type) { + const tile = this.field[x][y]; + + console.log(tile, type); + + if (type.initialCosts > civilization.balance) + return false; + + console.log('creatio'); + if (!tile.claimedBy || tile.claimedBy.player !== civilization.player) // Is non-friendly territory + return false; + + if (tile.object !== null) + return false; + + console.log('success'); + + const building = new type(x, y, this, civilization); + + tile.object = building; + this.buildings[building.uuid] = building; + return true; + } + + addCivilization(player, x, y, realPlayer = false, addEnvironment = true, addTownhall = true) { + if (!this.civilizations[player.uuid]) + this.civilizations[player.uuid] = []; + + const CivilizationType = realPlayer ? Civilization : CivilizationAI; + const newCivilization = new CivilizationType(player, this, x, y, addEnvironment, addTownhall); + + this.civilizations[player.uuid].push(newCivilization); + return newCivilization; + } + + addTree(x, y) { + if (!!this.field[x][y].object) + return; + + const tree = new Tree(x, y, this); + this.trees[tree.uuid] = tree; + } + + addWarrior(x, y, civilization, type) { + const tile = this.field[x][y]; + + if (type.initialCosts > civilization.balance) + return false; + + const warrior = new type(x, y, this, civilization); + const player = civilization.player; + + if (!!tile.claimedBy && tile.claimedBy.player === player) { // Is friendly territory + if (!!tile.object) { // Is object on the tile? + if (tile.object instanceof Tree) { + tile.object.cutDown(); + } else if (tile.object instanceof Warrior) { + if (tile.object.rank <= 4 - warrior.rank) { + tile.object.merge(warrior); + } + return false; + } else if (tile.object instanceof Building) { + return false; + } + } + } else { // Is hostile territory + if (!tile.getAdjacentTiles().find(adjTile => adjTile.claimedBy === civilization)) { // There isn't any tile of the given civilization nearby + return false; + } + + // TODO: Check adjacent tiles + + if (!!tile.object) { + if (tile.object instanceof Tree) { + tile.object.cutDown(); + } else if (tile.object instanceof Warrior) { + if (tile.object.rank < warrior.rank) { + tile.object.kill(); + } else { + return false; + } + } else if (tile.object instanceof Building) { + if (tile.object.rank < warrior.rank) { + tile.object.destroy(); + } else { + return false; + } + } + } + } + + this.warriors[warrior.uuid] = warrior; + tile.object = warrior; + tile.overtake(civilization); + return true; + } + + createHexagons() { + for (let x = 0; x < this.fieldSize.x; x++) { + this.field.push([]); + } + + const margin = Math.round(this.hexagonSize.width * .1); + + for (let y = 0; y < this.fieldSize.y; y++) { + for (let x = 0; x < this.fieldSize.x; x++) { + const listItem = this.playground.createChild('li'); + + listItem.style.width = this.hexagonSize.width + "px"; + listItem.style.marginRight = margin + 'px'; + listItem.style.paddingBottom = this.hexagonSize.padding + "px"; + + if (y % 2 !== 0) { + if (x === 0) + listItem.style.marginLeft = margin / 2 + 'px'; + + listItem.style.marginTop = listItem.style.marginBottom = -2 * margin + "px"; + listItem.classList.add('even-row'); + } + + listItem.createChild('div', ['hexagon', 'grey']); + + this.field[x][y] = new Tile(this, listItem, x, y); + } + } + } + + calculateHexagonSize() { + const paddingRatio = this.hexagonSize.padding / this.hexagonSize.width; + const offsetRatio = this.hexagonSize.offset / this.hexagonSize.width; + + this.container.style.width = ''; + + const widthPercentage = (100 / this.fieldSize.x * .9); + let width = Math.round(widthPercentage / 100 * this.container.clientWidth); + if (width % 2 !== 0) + width -= 1; + this.hexagonSize.width = width; + + this.hexagonSize.padding = this.hexagonSize.width * paddingRatio; + this.hexagonSize.offset = this.hexagonSize.width * offsetRatio; + } + + increaseRound() { + this.round++; + this.elements.round.innerText = this.round; + } + + nextRound() { + // TODO: Simulate other players + + // Lets the trees breed + Object.values(this.trees).forEach(tree => { + tree.breed(); + }); + + // Handles finances of the civilizations + Object.values(this.civilizations).forEach(civilizations => { + civilizations.forEach(civilization => { + civilization.processRound(); + }); + }); + + // Sets all warriors waiting + Object.values(this.warriors).forEach(warrior => { + if (warrior.civilization.player === player) { + warrior.setNeedsAction(true); + } + }); + + this.increaseRound(); + this.updateBalance(); + } + + registerEventListener() { + let mouseStartX; + let mouseStartY; + let startX; + let startY; + + this.view.onmousedown = e => { + mouseStartX = e.clientX; + mouseStartY = e.clientY; + + startX = parseInt(this.view.style.left.slice(0, this.view.style.left.length - 2)) || 0; + startY = parseInt(this.view.style.top.slice(0, this.view.style.top.length - 2)) || 0; + }; + + this.view.onmousemove = e => { + if (!!mouseStartX && !!mouseStartY) { + let deltaX = e.clientX - mouseStartX + startX; + let deltaY = e.clientY - mouseStartY + startY; + + this.repositionView(deltaX, deltaY); + } + }; + + this.view.onmouseup = e => { + mouseStartX = undefined; + mouseStartY = undefined; + }; + + this.viewContainer.onwheel = e => { + const prevScale = this.scale; + if (e.deltaY < 0) { + this.scale *= 1.1; + } else { + this.scale /= 1.1; + } + this.scale = Math.max(this.scale, 1); + this.scale = Math.min(this.scale, 10); + const deltaScale = this.scale / prevScale; + this.view.style.transform = `scale(${this.scale})`; + + if (!!this.draggingElement) { + let dragging = this.draggingElement.childNodes[0]; + dragging.style.width = deltaScale * dragging.clientWidth + 'px'; + dragging.style.height = deltaScale * dragging.clientHeight + 'px'; + } + + let centerY = -(this.view.clientHeight - this.viewContainer.clientHeight); + if (this.view.clientHeight < this.viewContainer.clientHeight) { + centerY /= 2; + } + this.view.classList.add('zooming'); + this.repositionView(this.deltaX * deltaScale, centerY - (centerY - this.deltaY) * deltaScale); + setTimeout(() => this.view.classList.remove('zooming'), 200); + }; + + document.onmousemove = (ev) => { + if (!!this.draggingElement) { + const draggingContainer = this.draggingElement; + const dragging = draggingContainer.childNodes[0]; + + draggingContainer.style.left = ev.clientX + 'px'; + draggingContainer.style.top = ev.clientY + 'px'; + + if (!!prevMouseEvent) { + clearTimeout(timeout); + const vel = (ev.clientX - prevMouseEvent.clientX) / (ev.timeStamp - prevMouseEvent.timeStamp); + + let rotation = Math.min(Math.max(vel * 80, -80), 80); + + dragging.style.transform = 'rotateZ(' + rotation + 'deg)'; + + timeout = setTimeout(() => { + dragging.style.transform = 'rotateZ(0deg)'; + }, 50); + } + + prevMouseEvent = ev; + } + }; + + this.container.addEventListener('contextmenu', (ev) => ev.preventDefault()); + + window.addEventListener('resize', () => this.resize()); + } + + repositionView(deltaX, deltaY) { + const maxDeltaX = (this.view.clientWidth * this.scale - this.viewContainer.clientWidth) / 2; + const maxDeltaTop = this.view.clientHeight * (this.scale - 1) / 2; + let maxDeltaBottom = (this.view.clientHeight - this.viewContainer.clientHeight); + if (this.view.clientHeight < this.viewContainer.clientHeight) { + maxDeltaBottom /= 2; + } + + deltaX = Math.min(deltaX, maxDeltaX); + deltaX = Math.max(deltaX, -maxDeltaX); + deltaY = Math.min(deltaY, maxDeltaTop); + deltaY = Math.max(deltaY, -maxDeltaBottom); + + this.deltaX = deltaX; + this.deltaY = deltaY; + + this.view.style.left = deltaX + 'px'; + this.view.style.top = deltaY + 'px'; + } + + resize() { + this.calculateHexagonSize(); + + const containerWidth = Math.round(this.hexagonSize.width / .9) * this.fieldSize.x; + this.container.style.width = containerWidth + 'px'; + + const margin = Math.round(this.hexagonSize.width * .1); + this.field.forEach((col, x) => { + col.forEach((tile, y) => { + tile.element.style.width = this.hexagonSize.width + "px"; + tile.element.style.marginRight = margin + 'px'; + tile.element.style.paddingBottom = this.hexagonSize.padding + "px"; + + if (y % 2 !== 0) { + if (x === 0) + tile.element.style.marginLeft = margin / 2 + 'px'; + + tile.element.style.marginTop = tile.element.style.marginBottom = -2 * margin + "px"; + } + }); + }); + + // this.view.style.width = containerWidth + this.hexagonSize.width / 2 + 'px'; + this.view.style.height = (this.hexagonSize.width / 1.01 * this.scale) * this.fieldSize.y + 'px'; + + this.field.forEach(col => { + col.forEach(tile => { + if (!!tile.object) { + tile.object.adjustPosition(); + } + }); + }); + + this.repositionView(0, 0); + } + + selectCivilization(civilization) { + if (!!this.selectedCivilization) + this.selectedCivilization.select(false); + + if (!!civilization && civilization.player === player) { + this.selectedCivilization = civilization; + civilization.select(true); + this.btnTroops.classList.remove('disabled'); + this.btnTowers.classList.remove('disabled'); + } else { + this.selectedCivilization = null; + this.btnTroops.classList.add('disabled'); + this.btnTroops.classList.remove('isActive'); + this.btnTowers.classList.add('disabled'); + this.btnTowers.classList.remove('isActive'); + } + + this.updateBalance(); + } + + selectWarrior(warrior) { + this.selectedObject = warrior; + } + + updateBalance() { + if (!!this.selectedCivilization && this.selectedCivilization.player === player) { + const balanceChange = this.selectedCivilization.getBalanceChange(); + + this.elements.balance.innerText = this.selectedCivilization.balance; + this.elements.balanceChange.innerText = balanceChange > 0 ? '+' + balanceChange : balanceChange; + } else { + this.elements.balance.innerText = null; + this.elements.balanceChange.innerText = null; + } + } + + deselectObject() { + if (!!this.selectedObject) { + this.selectedObject.isSelected = false; + } + this.selectedObject = null; + + if (!!this.draggingElement) { + this.draggingElement.parentNode.removeChild(this.draggingElement); + } + this.draggingElement = null; + } +} + +class CivilizationNN extends Civilization { + constructor(player, game, x, y) { + super(player, game, x, y, true, true); + } +} + +class GameNN extends Game { + constructor(nn) { + super(); + + this.ga = nn || new GeneticAlgorithm(10, 4, this); + for (let i = 0; i < this.ga.maxUnits; i++) { + let x; + let y; + let isGood = false; + while(!isGood) { + x = this.ga.random(0, 19); + y = this.ga.random(0, 9); + + if (!this.field[x][y].getAdjacentTiles().find(tile => !!tile.claimedBy)) + isGood = true; + } + + const civ = this.addCivilization(new Player('green'), x, y); + civ.index = i; + } + this.initialCivs = this.civilizations; + + this.fotze = true; + + if(!nn) { + this.ga.reset(); + this.ga.createPopulation(); + } + } + + addCivilization(player, x, y) { + if (!this.civilizations[player.uuid]) + this.civilizations[player.uuid] = []; + + const newCivilization = new CivilizationNN(player, this, x, y); + + this.civilizations[player.uuid].push(newCivilization); + + if(x < this.fieldSize.x - 1) + this.addWarrior(x + 1, y, newCivilization, Farmer); + + return newCivilization; + } + + nextRound() { + if(Object.values(this.civilizations).length === 0) { + this.ga.evolvePopulation(); + this.ga.iteration++; + } + + Object.values(this.civilizations).forEach(civilizations => { + civilizations.forEach(civilization => { + civilization.fitness = civilization.getSize(); + civilization.score = this.round; + this.ga.activateBrain(civilization); + }); + }); + + super.nextRound(); + + if(this.round === 1000) { + this.ga.evolvePopulation(); + this.ga.iteration++; + + Object.values(this.civilizations).forEach(player => { + player.forEach(civ => { + civ.destroy(); + }); + }); + this.civilizations = this.initialCivs; + Object.values(this.civilizations).forEach(player => { + player.forEach(civ => { + civ.spawn(); + }); + }); + this.round = 0; + } + setTimeout(() => { + if (this.fotze) + this.nextRound(); + }, 50); + } +} \ No newline at end of file diff --git a/GameElement.js b/GameElement.js new file mode 100644 index 0000000..b7a3da3 --- /dev/null +++ b/GameElement.js @@ -0,0 +1,62 @@ +class GameElement { + constructor(x, y, game, asset, setObject = true) { + this.game = game; + this.x = Math.min(x, this.game.fieldSize.x - 1); + this.y = Math.min(y, this.game.fieldSize.y - 1); + this.uuid = guid(); + + this.createElement(asset); + + if(setObject) + this.game.field[x][y].object = this; + } + + adjustPosition() { + const tile = this.game.field[this.x][this.y].element; + this.displayWidth = Math.round(this.game.hexagonSize.width * .9); + let offsetX = tile.offsetLeft; + let offsetY = tile.offsetTop; + + if (this.y % 2 !== 0) + offsetX += Math.round(this.displayWidth * .5); + + this.element.style.width = this.displayWidth + 'px'; + this.element.style.left = offsetX + 'px'; + this.element.style.top = offsetY + 'px'; + } + + createElement(asset) { + this.element = GameElement.createElement(asset, (this.constructor.name).toLowerCase()); + this.adjustPosition(); + this.game.view.appendChild(this.element); + } + + static createElement(asset, className) { + const element = document.createElement('img'); + element.src = 'assets/' + asset; + element.ondragstart = () => { + return false + }; + element.ondragend = () => { + return false + }; + element.classList.add('gameElement', className); + return element; + } + + static getDraggingObject(game, className, image) { + const draggingContainer = body.createChild('div', 'draggingContainer'); + const dragging = GameElement.createElement(image, className); + dragging.classList.add('dragging'); + dragging.style.width = game.hexagonSize.width * game.scale * 1.1 + 'px'; + dragging.style.height = game.hexagonSize.padding * game.scale * 1.1 + 'px'; + draggingContainer.appendChild(dragging); + + return draggingContainer; + } + + update() { + this.adjustPosition(); + this.game.updateBalance(); + } +} \ No newline at end of file diff --git a/NeuralNetwork.js b/NeuralNetwork.js new file mode 100644 index 0000000..907c972 --- /dev/null +++ b/NeuralNetwork.js @@ -0,0 +1,202 @@ +class GeneticAlgorithm { + constructor(maxUnits, topUnits, game) { + this.maxUnits = maxUnits; + this.topUnits = topUnits; + + if(this.maxUnits < this.topUnits) + this.topUnits = maxUnits; + + this.population = []; + + this.SCALE_FACTOR = 200; + + this.game = game; + } + + reset() { + this.iteration = 1; + this.mutateRate = 1; + + this.bestPopulation = 0; + this.bestFitness = 0; + this.bestScore = 0; + } + + createPopulation() { + this.population.slice(0, this.population.length); + + for(let i = 0; i < this.maxUnits; i++) { + const newUnit = new synaptic.Architect.Perceptron(6 * this.game.fieldSize.x * this.game.fieldSize.y, 35, 5); + newUnit.index = i; + newUnit.fitness = 0; + newUnit.score = 0; + newUnit.isWinner = false; + + this.population.push(newUnit); + } + } + + activateBrain(civilization) { + // TODO: Inputs + const inputs = [civilization.balance, civilization.getBalanceChange()]; + for(let x = 0; x < this.game.fieldSize.x; x++) { + for(let y = 0; y < this.game.fieldSize.y; y++) { + const tile = this.game.field[x][y]; + + const isFriendly = tile.claimedBy === civilization ? 1 : 0; + const hasObject = !!tile.object ? 1 : 0; + const canAttack = tile.object instanceof Warrior ? 1 : 0; + const rank = !!tile.object ? tile.object.rank / 4 : 0; + + inputs.push(x, y, isFriendly, hasObject, canAttack, rank); + } + } + + console.log(civilization.index); + const outputs = this.population[civilization.index].activate(inputs); + + console.log(outputs); + const type = outputs[0]; + const x = Math.round(outputs[1] * (this.game.fieldSize.x - 1)); + const y = Math.round(outputs[2] * (this.game.fieldSize.y - 1)); + const tile = this.game.field[x][y]; + + if(type < .5) { + if(tile.object && tile.claimedBy === civilization && tile.object instanceof Warrior) { + const deltaX = Math.round(outputs[3] * 4 - 2) / 4; + const deltaY = Math.round(outputs[4] * 4 - 2) / 4; + + const targetX = x + deltaX; + const targetY = y + deltaY; + + tile.object.move(targetX, targetY); + + console.log(deltaX, deltaY); + } + } else { + const itemType = outputs[3]; + console.log('hey'); + + if(itemType > .5) { + console.log('ho'); + if(!tile.getAdjacentTiles().find(tile => tile.claimedBy === civilization)) + return; + + const troopType = Math.round(outputs[4] * 3); + const troopTypes = [Farmer, Hoplite, Servant, Knight]; + this.game.addWarrior(x, y, civilization, troopTypes[troopType]); + } else { + if(tile.claimedBy !== civilization) + return; + + const buildingType = Math.round(outputs[4] * 2); + const buildingTypes = [Farm, SmallTower, BigTower]; + console.log('ho2', buildingType, tile); + this.game.addBuilding(x, y, civilization, buildingTypes[buildingType]); + } + } + } + + evolvePopulation() { + const winners = this.selection(); + + if(this.mutateRate === 1 && winners[0].fitness < 0) { + this.createPopulation(); + } else { + this.mutateRate = .2; + } + + for(let i = this.topUnits; i < this.maxUnits; i++) { + let parentA, parentB, offspring; + + if(i === this.topUnits) { + parentA = winners[0].toJSON(); + parentB = winners[1].toJSON(); + offspring = this.crossOver(parentA, parentB); + } else if(i < this.maxUnits - 2) { + parentA = this.getRandomUnit(winners).toJSON(); + parentB = this.getRandomUnit(winners).toJSON(); + offspring = this.crossOver(parentA, parentB); + } else { + offspring = this.getRandomUnit(winners).toJSON(); + } + + offspring = this.mutation(offspring); + + const newUnit = synaptic.Network.fromJSON(offspring); + newUnit.index = this.population[i].index; + newUnit.fitness = 0; + newUnit.score = 0; + newUnit.isWinner = false; + + this.population[i] = newUnit; + } + + if(winners[0].fitness > this.bestFitness) { + this.bestPopulation = this.iteration; + this.bestFitness = winners[0].fitness; + this.bestScore = winners[0].score; + } + + this.population.sort((unitA, unitB) => unitA.index - unitB.index); + } + + selection() { + const sortedPopulation = this.population.sort((unitA, unitB) => unitB.fitness - unitA.fitness); + + for(let i = 0; i < this.topUnits; i++) { + this.population[i].isWinner = true; + } + + return sortedPopulation.slice(0, this.topUnits); + } + + crossOver(parentA, parentB) { + const cutPoint = this.random(0, parentA.neurons.length - 19); + + for(let i = cutPoint; i < parentA.neurons.length; i++) { + const biasFromParentA = parentA.neurons[i]['bias']; + parentA.neurons[i]['bias'] = parentB.neurons[i]['bias']; + parentB.neurons[i]['bias'] = biasFromParentA; + } + + return this.random(0, 1) === 1 ? parentA : parentB; + } + + mutation(offspring) { + for(let i = 0; i < offspring.neurons.length; i++) { + offspring.neurons[i]['bias'] = this.mutate(offspring.neurons[i]['bias']); + } + + for(let i = 0; i < offspring.connections.length; i++) { + offspring.connections[i]['weight'] = this.mutate(offspring.connections[i]['weight']); + } + + return offspring; + } + + mutate(gene) { + if(Math.random() < this.mutateRate) { + gene *= 1 + ((Math.random() - .5) * 3 + (Math.random() - .5)); + } + + return gene; + } + + random(min, max) { + return Math.floor(Math.random() * (max - min + 1) + min); + } + + getRandomUnit(array) { + return array[this.random(0, array.length - 1)]; + } + + normalize(value, max) { + if(value < -max) + value = -max; + else if(value > max) + value = max; + + return value / max; + } +} \ No newline at end of file diff --git a/Tile.js b/Tile.js new file mode 100644 index 0000000..a2e00d8 --- /dev/null +++ b/Tile.js @@ -0,0 +1,104 @@ +class Tile { + constructor(game, element, x, y) { + this.game = game; + this.element = element; + this.pos = {x: x, y: y}; + this.claimedBy = null; + this.object = null; + + this.addClickListener(); + } + + addClickListener() { + this.element.addEventListener('click', () => this.clickHandler()); + } + + clickHandler() { + // Checks whether there is an object selected at the moment and if it belongs to the human player + if (!!this.game.selectedObject && this.game.selectedObject.civilization.player === player) { + // Checks whether this tile is a possible destination for the object based on the object type + if (this.game.selectedObject instanceof Warrior) { + this.game.selectedObject.move(this.pos.x, this.pos.y); + return; + } + + if (this.game.selectedObject.type === 'new') { + const obj = this.game.selectedObject; + const allowMoveAfterCreation = this.claimedBy === obj.civilization && this.object === null; + const result = this.game[obj.function](this.pos.x, this.pos.y, obj.civilization, obj.class); + if (!result) { + this.game.deselectObject(); + return; + } + if (!allowMoveAfterCreation) { + this.object.setNeedsAction(false); + } + } else { + this.object = this.game.selectedObject; + } + + this.object.x = this.pos.x; + this.object.y = this.pos.y; + + this.game.deselectObject(); + + this.object.update(); + } else if(this.object instanceof Warrior) { + this.object.onClick(); + } + + this.game.selectCivilization(this.claimedBy); + } + + getAdjacentTiles() { + const surroundings = [ + [-1, 0], + [0, -1], + [0, 1], + [1, 0], + ]; + + if (this.pos.y % 2 === 0) { + surroundings.push([-1, -1], [-1, 1]); + } else { + surroundings.push([1, -1], [1, 1]); + } + + const adjacentTiles = []; + surroundings.forEach(modification => { + const x = this.pos.x + modification[0]; + const y = this.pos.y + modification[1]; + if (x >= 0 && x < this.game.fieldSize.x && y >= 0 && y < this.game.fieldSize.y) + adjacentTiles.push(this.game.field[x][y]); + }); + + return adjacentTiles; + } + + getAdjacentCivilizationTiles(civilization, excluded = []) { + const surroundings = this.getAdjacentTiles().filter(tile => !excluded.includes(tile)); + const matches = []; + surroundings.forEach(tile => { + if(!!tile.claimedBy && tile.claimedBy.uuid === civilization.uuid) { + matches.push(tile); + matches.push(...tile.getAdjacentCivilizationTiles(civilization, [this, ...surroundings, ...matches, ...excluded])); + } + }); + return matches; + } + + getAvailableAdjacentTiles() { + return this.getAdjacentTiles().filter(tile => !tile.object); + } + + overtake(civilization) { + const prevColor = this.getColor(); + this.claimedBy = civilization; + this.element.childNodes[0].classList.remove(prevColor); + this.element.childNodes[0].classList.add(this.getColor()); + } + + getColor() { + return this.claimedBy != null ? this.claimedBy.color : 'grey'; + } +} \ No newline at end of file diff --git a/Warrior.js b/Warrior.js new file mode 100644 index 0000000..63f5cfb --- /dev/null +++ b/Warrior.js @@ -0,0 +1,247 @@ +class Warrior extends GameElement { + constructor(x, y, game, civilization, asset) { + super(x, y, game, asset, false); + + this.civilization = civilization; + this.isSelected = false; + + this.civilization.balance -= this.constructor.initialCosts; + + this.setNeedsAction(true); + } + + onClick() { + if (this.civilization.player !== player) + return; + + this.game.selectCivilization(this.civilization); + + if (!this.game.selectedObject && this.needsAction) { + this.isSelected = true; + this.game.selectWarrior(this); + + this.game.draggingElement = this.constructor.getDraggingObject(this.game); + } + } + + static getDraggingObject(game, className = 'farmer', image = 'knight.svg') { + return super.getDraggingObject(game, className, image); + } + + getPossibleTiles(player) { + return this.checkSurroundings(this.x, this.y, player); + } + + merge(warrior) { + this.rank += warrior.rank || 1; + this.rank = Math.min(this.rank, 4); + + warrior.kill(); + this.kill(); + + const ranks = [Farmer, Hoplite, Servant, Knight]; + + this.civilization.balance += warrior.constructor.initialCosts + this.constructor.initialCosts; + this.game.addWarrior(this.x, this.y, this.civilization, ranks[this.rank - 1]); + // TODO: Switch image + } + + move(x, y) { + if (!this.needsAction) + return false; + + if (!this.isPossibleDestination(x, y, this.civilization.player)) + return false; + + this.game.field[this.x][this.y].object = null; + const tile = this.game.field[x][y]; + + if (!tile.claimedBy || tile.claimedBy.player === this.civilization.player) { // Friendly territory + if (tile.object instanceof Tree) { // Tree + tile.object.cutDown(); + } else if (tile.object instanceof Warrior) { // Obstacle is a friendly warrior + tile.object.merge(this); + this.game.deselectObject(); + return true; + } + } else { // Hostile territory + // Check surrounding tiles + if(tile.getAdjacentTiles().find(adjTile => adjTile.claimedBy !== this.civilization && + !!adjTile.object && + (adjTile.object instanceof Warrior || adjTile.object instanceof Tower) && + adjTile.object.rank >= this.rank)) { + return false; + } + + if (tile.object instanceof Tree) { // No object/troop on tile + tile.object.cutDown(); + } else if (tile.object instanceof Warrior) { // Obstacle is an hostile warrior + if (tile.object.rank < this.rank) { // Hostile warrior/tower is weaker than own troop + tile.object.kill(); + } + } else if (tile.object instanceof Building) { + if (tile.object.rank < this.rank) { + tile.object.destroy(); + } + } + } + + this.x = x; + this.y = y; + this.game.deselectObject(); + this.setNeedsAction(false); + + const prevCiv = tile.claimedBy; + + tile.object = this; + tile.overtake(this.civilization); + + if(prevCiv instanceof Civilization && prevCiv !== this.civilization) { + console.log(prevCiv.uuid); + prevCiv.split(); + } + + this.update(); + } + + checkSurroundings(x, y, player, step = 0, excluded = []) { + const field = this.game.field; + const possibleSurroundings = []; + const neighbours = field[x][y].getAdjacentTiles().filter(tile => !excluded.includes(tile)); + + neighbours.forEach(neighbour => { + const {x, y} = neighbour.pos; + + if (x < 0 || x >= this.game.fieldSize.x || y < 0 || y >= this.game.fieldSize.y) + return; + + if (this.isPossibleTile(x, y)) { + possibleSurroundings.push(field[x][y]); + + if (step < 4 && !!field[x][y].claimedBy && field[x][y].claimedBy.player === player) + possibleSurroundings.push(...this.checkSurroundings(x, y, player, ++step, [...excluded, ...neighbours, ...possibleSurroundings])); + } + }); + + return possibleSurroundings; + } + + isPossibleTile(x, y) { + const tile = this.game.field[x][y]; + if (!!tile.claimedBy && tile.claimedBy.player === this.civilization.player) { // Friendly territory + if (!tile.object || tile.object instanceof Tree) { // No object/troop on tile + return true; + } + + if (tile.object instanceof Warrior) { // Obstacle is a friendly warrior + if (tile.object.rank <= 4 - this.rank) { // Warrior isn't yet on max level + return true; + } + } + + // Otherwise there is an unavoidable obstacle + } else { // Hostile territory + if (!tile.object || tile.object instanceof Tree) { // No object/troop on tile + // TODO: Check surrounding tiles + return true; + } + + if (tile.object instanceof Warrior || tile.object instanceof Building) { // Obstacle is an hostile warrior + if (tile.object.rank < this.rank) { // Hostile warrior/tower is weaker than own troop + // TODO: Check surrounding tiles + return true; + } + } + + // Otherwise there is an unavoidable obstacle + } + + return false; + } + + isPossibleDestination(x, y, player) { + return this.getPossibleTiles(player).includes(this.game.field[x][y]); + } + + kill() { + delete this.game.warriors[this.uuid]; + this.element.parentNode.removeChild(this.element); + this.game.field[this.x][this.y].object = null; + + // TODO: Show gravestone + } + + setNeedsAction(needsAction) { + this.needsAction = needsAction; + + if (this.civilization.player === player && needsAction) { + this.element.classList.add('waiting'); + } else { + this.element.classList.remove('waiting'); + } + } +} + +class Farmer extends Warrior { + constructor(x, y, game, civilization) { + super(x, y, game, civilization, 'farmer.svg'); + this.rank = 1; + this.costsPerRound = 2; + } + + static getDraggingObject(game) { + return super.getDraggingObject(game, 'farmer', 'farmer.svg'); + } + + static get initialCosts() { + return 10; + } +} + +class Hoplite extends Warrior { + constructor(x, y, game, civilization) { + super(x, y, game, civilization, 'hoplite.svg'); + this.rank = 2; + this.costsPerRound = 6; + } + + static getDraggingObject(game) { + return super.getDraggingObject(game, 'hoplite', 'hoplite.svg'); + } + + static get initialCosts() { + return 20; + } +} + +class Servant extends Warrior { + constructor(x, y, game, civilization) { + super(x, y, game, civilization, ''); + this.rank = 3; + this.costsPerRound = 18; + } + + static getDraggingObject(game) { + return super.getDraggingObject(game, 'servant', 'servant.svg'); + } + + static get initialCosts() { + return 30; + } +} + +class Knight extends Warrior { + constructor(x, y, game, civilization) { + super(x, y, game, civilization, 'knight.svg'); + this.rank = 4; + this.costsPerRound = 36; + } + + static getDraggingObject(game) { + return super.getDraggingObject(game, 'knight', 'knight.svg'); + } + + static get initialCosts() { + return 40; + } +} \ No newline at end of file diff --git a/assets/farm.svg b/assets/farm.svg new file mode 100644 index 0000000..c4cf42d --- /dev/null +++ b/assets/farm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/farmer.svg b/assets/farmer.svg new file mode 100644 index 0000000..45865dc --- /dev/null +++ b/assets/farmer.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/hoplite.svg b/assets/hoplite.svg new file mode 100644 index 0000000..c1cbbc2 --- /dev/null +++ b/assets/hoplite.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/knight.svg b/assets/knight.svg new file mode 100644 index 0000000..0d6b612 --- /dev/null +++ b/assets/knight.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + diff --git a/assets/servant.svg b/assets/servant.svg new file mode 100644 index 0000000..1a459f5 --- /dev/null +++ b/assets/servant.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/tower_big.svg b/assets/tower_big.svg new file mode 100644 index 0000000..522091e --- /dev/null +++ b/assets/tower_big.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/tower_small.svg b/assets/tower_small.svg new file mode 100644 index 0000000..8198edd --- /dev/null +++ b/assets/tower_small.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/townhall.svg b/assets/townhall.svg new file mode 100644 index 0000000..c6b8f6a --- /dev/null +++ b/assets/townhall.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/tree.svg b/assets/tree.svg new file mode 100644 index 0000000..e1d8818 --- /dev/null +++ b/assets/tree.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..1a5b12a --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + + Antiyoy + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..fc72dba --- /dev/null +++ b/index.js @@ -0,0 +1,28 @@ +const brain = require('brain.js'); + +const net = new brain.NeuralNetwork(); + +const out = net.train([ + { + input: + {x: 0, y: 0.5, isFriendly: 1, isAccessible: 1, danger: 0, canAttack: 0, rank: .5, isTree: 0}, + // {x: 0.25, y: 0.5, isFriendly: 1, isAccessible: 1, danger: 0, canAttack: 0, rank: .5, isTree: 0}, + // {x: 0.75, y: 0.5, isFriendly: 1, isAccessible: 1, danger: 0, canAttack: 0, rank: .5, isTree: 0}, + // {x: 1, y: 0.5, isFriendly: 1, isAccessible: 1, danger: 0, canAttack: 0, rank: .5, isTree: 0}, + // ], output: 0 + output: {x: 0, y: 0.5} + } +]); + +console.log(out); +console.log('test'); +console.log(net.toJSON()); + +const test = net.run([ + {x: 0, y: 0.5, isFriendly: 1, isAccessible: 1, danger: 0, canAttack: 0, rank: .5, isTree: 0}, + {x: 0.25, y: 0.5, isFriendly: 1, isAccessible: 1, danger: 0, canAttack: 0, rank: .5, isTree: 0}, + {x: 0.75, y: 0.5, isFriendly: 1, isAccessible: 1, danger: 0, canAttack: 0, rank: .5, isTree: 0}, + {x: 1, y: 0.5, isFriendly: 1, isAccessible: 1, danger: 0, canAttack: 0, rank: .5, isTree: 0}, + ]); + +console.log(test); \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..2873b2b --- /dev/null +++ b/main.js @@ -0,0 +1,196 @@ +const body = document.querySelector('body'); + +let prevMouseEvent; +let timeout; + +Node.prototype.createChild = function(tagName, classNames = null, text = null, src = null) { + const element = document.createElement(tagName); + + if(!!classNames) + if(classNames instanceof Array) + element.classList.add(...classNames); + else + element.classList.add(classNames); + + if(!!text) + element.innerText = text; + + if(!!src) + element.src = src; + + this.appendChild(element); + return element; +}; + +function createExpandBtn(parent, items) { + const container = parent.createChild('div', ['expandBtnContainer', 'expandBtnContainer' + items.length, 'disabled']); + const expandBtn = container.createChild('div', 'expandBtn'); + expandBtn.addEventListener('click', () => { + container.classList.toggle('isActive'); + }, false); + expandBtn.createChild('img', null, null, 'assets/' + items[0]); + + const btns = []; + items.forEach((item, index) => { + const btn = container.createChild('div', 'btn' + (index + 1)); + btn.createChild('img', null, null, 'assets/' + item); + btns.push(btn); + }); + + return [container, ...btns]; +} + +function createTroopsBtn(parent) { + const btn = createExpandBtn(parent, ['farmer.svg', 'hoplite.svg', 'servant.svg', 'knight.svg']); + btn[0].classList.add('btn-troops'); + return btn; +} + +function createTowersBtn(parent) { + const btn = createExpandBtn(parent, ['farm.svg', 'tower_small.svg', 'tower_big.svg']); + btn[0].classList.add('btn-buildings'); + return btn; +} + +class Building extends GameElement { + constructor(x, y, game, civilization, asset) { + super(x, y, game, asset); + + this.civilization = civilization; + this.rank = 0; + } + + adjustPosition() { + const tile = this.game.field[this.x][this.y].element; + this.displayWidth = Math.round(this.game.hexagonSize.width * .9); + let offsetX = tile.offsetLeft; + let offsetY = tile.offsetTop; + + if (this.y % 2 !== 0) + offsetX += Math.round(this.displayWidth * .5); + + this.element.style.width = this.displayWidth + 'px'; + this.element.style.left = offsetX + 'px'; + this.element.style.top = offsetY + 'px'; + } + + destroy() { + this.game.field[this.x][this.y].object = null; + this.element.parentNode.removeChild(this.element); + } +} + +class Farm extends Building { + constructor(x, y, game, civilization) { + super(x, y, game, civilization, 'farm.svg'); + } + + static getDraggingObject(game) { + return super.getDraggingObject(game, 'farm', 'farm.svg'); + } + + static get initialCosts() { + return 10; // TODO: dynamic price + } +} + +class Tower extends Building { + constructor(x, y, game, civilization, asset) { + super(x, y, game, civilization, asset); + } +} + +class SmallTower extends Tower { + constructor(x, y, game, civilization) { + super(x, y, game, civilization, 'tower_small.svg'); + } + + static getDraggingObject(game) { + return super.getDraggingObject(game, 'towerSmall', 'tower_small.svg'); + } + + static get initialCosts() { + return 20; + } +} + +class BigTower extends Tower { + constructor(x, y, game, civilization) { + super(x, y, game, civilization, 'tower_big.svg'); + } + + static getDraggingObject(game) { + return super.getDraggingObject(game, 'towerBig', 'tower_big.svg'); + } + + static get initialCosts() { + return 30; + } +} + +class Townhall extends Building { + constructor(civilization, game) { + super(civilization.origin.x, civilization.origin.y, game, civilization, 'townhall.svg'); + } + + destroy() { + super.destroy(); + // TODO: New townhall + console.log('hallo'); + this.civilization.townhall = null; + this.civilization.split(); + } +} + +class Tree extends GameElement { + constructor(x, y, game) { + super(x, y, game, 'tree.svg'); + } + + breed() { + if(Math.random() < .25) { + const tiles = this.game.field[this.x][this.y].getAvailableAdjacentTiles(); + if(tiles.length === 0) + return; + + const index = Math.floor(Math.random() * (tiles.length - 1)); + const {x, y} = tiles[index].pos; + this.game.addTree(x, y); + } + } + + cutDown() { + delete this.game.trees[this.uuid]; + this.element.parentNode.removeChild(this.element); + } +} + +class Player { + constructor(color) { + this.color = color; + this.uuid = guid(); + } +} + +function guid() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); +} + +const player = new Player('red'); +// let game = new GameNN(); +let game = new Game(); +const civilization = game.addCivilization(player, 2, 4, true); + +// const anotherOne = new Player('yellow'); +// const another = game.addCivilization(anotherOne, 5, 3); +// game.addWarrior(1, 4, civilization, Farmer); +// game.addWarrior(4, 2, another, Farmer); +// +// game.addTree(1,4); +// game.addTree(1,5); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3c15b90 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,40 @@ +{ + "name": "antiyoy", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" + }, + "brain.js": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/brain.js/-/brain.js-1.6.0.tgz", + "integrity": "sha512-HMHK7ueavtlONnpa+qDDJNs4K5gegGxQkKir+TeBoXgyHjI84y4vUh44q9qOOv2LwtVlZf1h5gEp676OWnkCbA==", + "requires": { + "gpu.js": "^1.10.4", + "thaw.js": "^2.0.0" + } + }, + "gpu.js": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/gpu.js/-/gpu.js-1.10.4.tgz", + "integrity": "sha512-qh5O1LXwwkhGOpEicR9SnQB7xduUDY5rKGPWj9yOFLNI90bX+mReooGQjnL3iZ08btBOT0zQGu9wKA+gGy2BhQ==", + "requires": { + "acorn": "^5.1.1" + } + }, + "synaptic": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/synaptic/-/synaptic-1.1.4.tgz", + "integrity": "sha512-JFtU98VDFI0uBTowgySo+UUEkc7rSVHaxx9D6dR5BK6u81G7uAxcp32Gi2SHciLpFbvJVd1Pfz7aqSNQO4EH9Q==" + }, + "thaw.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/thaw.js/-/thaw.js-2.0.0.tgz", + "integrity": "sha1-RSvF1+4s4bb5IDewW1BsvWVikdA=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a0f6f33 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "antiyoy", + "version": "1.0.0", + "description": "", + "main": "index.js", + "dependencies": { + "brain.js": "^1.6.0", + "synaptic": "^1.1.4" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..3ed24d9 --- /dev/null +++ b/style.css @@ -0,0 +1,389 @@ +body { + background-color: #171d1c; + margin: 0; + padding: 0; + overflow: hidden; + font-family: Roboto, Arial, sans-serif; +} + +.clear:after { + content: ""; + display: block; + clear: both; +} + +.grey { + background-color: #9a9a9a; +} + +.red { + background-color: #f93943; +} + +.yellow { + background-color: #fde74c; +} + +.blue { + background-color: #5fbff9; +} + +.green { + background-color: #45cb85; +} + +.purple { + background-color: #5c2751; +} + +.text-grey { + color: #9a9a9a; +} + +.gameContainer { + position: relative; + width: 80%; + margin: 0 auto; + padding: 0; + height: 100vh; +} + +@media (max-width: 500px) { + .gameContainer { + width: 100%; + } +} + +.viewContainer { + position: relative; + overflow: hidden; + width: 100%; + height: calc(100vh - 2 * 84px); +} + +.view { + position: absolute; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + width: 100%; + transition: transform .2s; + +} + +.view.zooming { + transition: transform .2s, left .2s, top .2s; +} + +.playground { + position: relative; + margin: 0 auto; + padding: 0; +} + +.playground > li { + list-style-type: none; + position: relative; + float: left; + width: 27.85714285714286%; + padding: 0 0 32.16760145166612% 0; + -o-transform: rotate(-60deg) skewY(30deg); + -moz-transform: rotate(-60deg) skewY(30deg); + -webkit-transform: rotate(-60deg) skewY(30deg); + -ms-transform: rotate(-60deg) skewY(30deg); + transform: rotate(-60deg) skewY(30deg); + overflow: hidden; + visibility: hidden; + margin-right: .33%; +} + +.playground > li.even-row { + -o-transform: translateX(50%) rotate(-60deg) skewY(30deg); + -moz-transform: translateX(50%) rotate(-60deg) skewY(30deg); + -webkit-transform: translateX(50%) rotate(-60deg) skewY(30deg); + -ms-transform: translateX(50%) rotate(-60deg) skewY(30deg); + transform: translateX(50%) rotate(-60deg) skewY(30deg); +} + +.playground > li .hexagon { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + -o-transform: skewY(-30deg) rotate(60deg); + -moz-transform: skewY(-30deg) rotate(60deg); + -webkit-transform: skewY(-30deg) rotate(60deg); + -ms-transform: skewY(-30deg) rotate(60deg); + transform: skewY(-30deg) rotate(60deg); + visibility: visible; + overflow: hidden; +} + +.gameContainer .gameElement { + position: absolute; + top: 0; + left: 0; + pointer-events: none; +} + +.gameContainer .townhall { + transform: translate(6%, 8%); +} + +.gameContainer .tree { + transform: translate(8%, 12%) scale(.8); +} + +.gameContainer .farmer { + transform: translate(5%, 12%); + transition: top .5s, left .5s, transform .5s; +} + +.gameContainer .hoplite { + transform: translate(12%, 10%); + transition: top .5s, left .5s, transform .5s; +} + +.gameContainer .gameElement.waiting { + animation: waiting .7s infinite; + transition: margin-top .5s; +} + +@keyframes waiting { + 0% { + /*transform: translate(14%, 10%);*/ + margin-top: 0; + } + + 60% { + /*transform: translate(14%, 0);*/ + margin-top: -4px; + } + + 90% { + /*transform: translate(14%, 10%);*/ + margin-top: 0; + } + + 100% { + margin-top: 0; + } +} + +.draggingContainer { + position: absolute; + transform: translateX(-50%); + cursor: none; + pointer-events: none; +} + +.draggingContainer > .dragging { + pointer-events: none; + -webkit-transform-origin: 50% 5%; + -moz-transform-origin: 50% 5%; + -ms-transform-origin: 50% 5%; + -o-transform-origin: 50% 5%; + transform-origin: 50% 5%; + transition: transform .25s, width .2s, height .2s; +} + +.gameStats, .controls { + position: relative; + height: 64px; + padding: 10px 20px; +} + +.gameStats > .gameStat { + position: absolute; + color: #fff; + font-weight: normal; +} + +.gameStats > .gameStat.center { + left: 50%; + transform: translateX(-50%); +} + +.gameStats > .gameStat.right { + right: 0; +} + +.gameStats > .gameStat > .count { + font-weight: bold; + margin: 0 5px; +} + +.btn { + font-size: 18px; + border: none; + border-radius: 4px; + padding: 8px 16px; + cursor: pointer; + transition: color .2s, background-color .2s, box-shadow .2s; +} + +.btn-primary { + color: #fff; + background-color: #5c2751; +} + +.btn-primary:hover { + color: #5c2751; + background: #fff; + -webkit-box-shadow: 0 0 10px #5c2751; + -moz-box-shadow: 0 0 10px #5c2751; + box-shadow: 0 0 10px #5c2751; +} + +.btn-img { + padding: 8px; + width: 22px; + height: 22px; + background-color: #fff; + border-radius: 100%; + transform: scale(1.75); + transition: background-color .5s, opacity .5s, transform .5s cubic-bezier(.68, -0.55, .27, 1.55), box-shadow .2s; +} + +.btn-img:hover { + -webkit-box-shadow: 0 0 10px #5c2751; + -moz-box-shadow: 0 0 10px #5c2751; + box-shadow: 0 0 10px #5c2751; +} + +.btn-img.disabled { + background-color: #ccc; + opacity: .8; + box-shadow: none; + transform: none; +} + +.btn-nextRound { + position: absolute; + left: 50%; + transform: translateX(-50%); + margin: 12px 0; +} + +.expandBtnContainer { + position: absolute; + width: 64px; + height: 64px; + transition: transform .5s cubic-bezier(.68, -0.55, .27, 1.55); +} + +.btn-troops { + left: 20px; +} + +.btn-buildings { + right: 20px; +} + +.expandBtnContainer.disabled { + transform: scale(.5); + pointer-events: none; +} + +.expandBtnContainer > div, +.expandBtnContainer > div, +.expandBtnContainer > div, +.expandBtnContainer > div, +.expandBtnContainer > div{ + width: 100%; + height: 100%; + background: #fff; + border-radius: 50%; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); +} + +.expandBtnContainer .expandBtn { + z-index: 1; +} + +.expandBtnContainer > div > img { + position: absolute; + height: 60%; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.expandBtnContainer .expandBtn:before { + content: ''; + display: block; + width: 100%; + height: 100%; + border-radius: 50%; + position: absolute; + background: rgba(255, 72, 120, 0.5); + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); +} + +.expandBtnContainer.isActive .expandBtn:before { + -webkit-transform: translate(-50%, -50%) scale(1.4); + transform: translate(-50%, -50%) scale(1.4); +} + +.expandBtnContainer > div:not(.expandBtn), +.expandBtnContainer > div:not(.expandBtn), +.expandBtnContainer > div:not(.expandBtn), +.expandBtnContainer > div:not(.expandBtn) { + width: 75%; + height: 75%; + background: #19d1e7; + transition-delay: 0s; +} + +.expandBtnContainer.expandBtnContainer4.isActive .btn1 { + top: 0; + left: -60%; + transition-delay: .1s; +} + +.expandBtnContainer.expandBtnContainer4.isActive .btn2 { + top: -60%; + left: 12.5%; + transition-delay: .2s; +} + +.expandBtnContainer.expandBtnContainer4.isActive .btn3 { + top: -60%; + left: 97.5%; + transition-delay: .3s; +} + +.expandBtnContainer.expandBtnContainer4.isActive .btn4 { + top: 0; + left: 160%; + transition-delay: 0s; +} + +.expandBtnContainer.expandBtnContainer3.isActive .btn1 { + top: -20%; + left: -50%; + transition-delay: .1s; +} + +.expandBtnContainer.expandBtnContainer3.isActive .btn2 { + top: -75%; + left: 50%; + transition-delay: .2s; +} + +.expandBtnContainer.expandBtnContainer3.isActive .btn3 { + top: -20%; + left: 150%; + transition-delay: 0s; +} diff --git a/synaptic.min.js b/synaptic.min.js new file mode 100644 index 0000000..2d68f4e --- /dev/null +++ b/synaptic.min.js @@ -0,0 +1,3048 @@ +/*! + * The MIT License (MIT) + * + * Copyright (c) 2017 Juan Cazala - https://caza.la + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE + * + * + * + * ******************************************************************************************** + * SYNAPTIC (v1.1.4) + * ******************************************************************************************** + * + * Synaptic is a javascript neural network library for node.js and the browser, its generalized + * algorithm is architecture-free, so you can build and train basically any type of first order + * or even second order neural network architectures. + * + * http://en.wikipedia.org/wiki/Recurrent_neural_network#Second_Order_Recurrent_Neural_Network + * + * The library includes a few built-in architectures like multilayer perceptrons, multilayer + * long-short term memory networks (LSTM) or liquid state machines, and a trainer capable of + * training any given network, and includes built-in training tasks/tests like solving an XOR, + * passing a Distracted Sequence Recall test or an Embeded Reber Grammar test. + * + * The algorithm implemented by this library has been taken from Derek D. Monner's paper: + * + * + * A generalized LSTM-like training algorithm for second-order recurrent neural networks + * http://www.overcomplete.net/papers/nn2012.pdf + * + * There are references to the equations in that paper commented through the source code. + * + */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["synaptic"] = factory(); + else + root["synaptic"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 4); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _LayerConnection = __webpack_require__(6); + +var _LayerConnection2 = _interopRequireDefault(_LayerConnection); + +var _Neuron = __webpack_require__(2); + +var _Neuron2 = _interopRequireDefault(_Neuron); + +var _Network = __webpack_require__(1); + +var _Network2 = _interopRequireDefault(_Network); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +// types of connections +var connectionType = { + ALL_TO_ALL: "ALL TO ALL", + ONE_TO_ONE: "ONE TO ONE", + ALL_TO_ELSE: "ALL TO ELSE" +}; + +// types of gates +var gateType = { + INPUT: "INPUT", + OUTPUT: "OUTPUT", + ONE_TO_ONE: "ONE TO ONE" +}; + +var Layer = function () { + function Layer(size) { + _classCallCheck(this, Layer); + + this.size = size | 0; + this.list = []; + + this.connectedTo = []; + + while (size--) { + var neuron = new _Neuron2.default(); + this.list.push(neuron); + } + } + + // activates all the neurons in the layer + + + _createClass(Layer, [{ + key: 'activate', + value: function activate(input) { + + var activations = []; + + if (typeof input != 'undefined') { + if (input.length != this.size) throw new Error('INPUT size and LAYER size must be the same to activate!'); + + for (var id in this.list) { + var neuron = this.list[id]; + var activation = neuron.activate(input[id]); + activations.push(activation); + } + } else { + for (var id in this.list) { + var neuron = this.list[id]; + var activation = neuron.activate(); + activations.push(activation); + } + } + return activations; + } + + // propagates the error on all the neurons of the layer + + }, { + key: 'propagate', + value: function propagate(rate, target) { + + if (typeof target != 'undefined') { + if (target.length != this.size) throw new Error('TARGET size and LAYER size must be the same to propagate!'); + + for (var id = this.list.length - 1; id >= 0; id--) { + var neuron = this.list[id]; + neuron.propagate(rate, target[id]); + } + } else { + for (var id = this.list.length - 1; id >= 0; id--) { + var neuron = this.list[id]; + neuron.propagate(rate); + } + } + } + + // projects a connection from this layer to another one + + }, { + key: 'project', + value: function project(layer, type, weights) { + + if (layer instanceof _Network2.default) layer = layer.layers.input; + + if (layer instanceof Layer) { + if (!this.connected(layer)) return new _LayerConnection2.default(this, layer, type, weights); + } else throw new Error('Invalid argument, you can only project connections to LAYERS and NETWORKS!'); + } + + // gates a connection betwenn two layers + + }, { + key: 'gate', + value: function gate(connection, type) { + + if (type == Layer.gateType.INPUT) { + if (connection.to.size != this.size) throw new Error('GATER layer and CONNECTION.TO layer must be the same size in order to gate!'); + + for (var id in connection.to.list) { + var neuron = connection.to.list[id]; + var gater = this.list[id]; + for (var input in neuron.connections.inputs) { + var gated = neuron.connections.inputs[input]; + if (gated.ID in connection.connections) gater.gate(gated); + } + } + } else if (type == Layer.gateType.OUTPUT) { + if (connection.from.size != this.size) throw new Error('GATER layer and CONNECTION.FROM layer must be the same size in order to gate!'); + + for (var id in connection.from.list) { + var neuron = connection.from.list[id]; + var gater = this.list[id]; + for (var projected in neuron.connections.projected) { + var gated = neuron.connections.projected[projected]; + if (gated.ID in connection.connections) gater.gate(gated); + } + } + } else if (type == Layer.gateType.ONE_TO_ONE) { + if (connection.size != this.size) throw new Error('The number of GATER UNITS must be the same as the number of CONNECTIONS to gate!'); + + for (var id in connection.list) { + var gater = this.list[id]; + var gated = connection.list[id]; + gater.gate(gated); + } + } + connection.gatedfrom.push({ layer: this, type: type }); + } + + // true or false whether the whole layer is self-connected or not + + }, { + key: 'selfconnected', + value: function selfconnected() { + + for (var id in this.list) { + var neuron = this.list[id]; + if (!neuron.selfconnected()) return false; + } + return true; + } + + // true of false whether the layer is connected to another layer (parameter) or not + + }, { + key: 'connected', + value: function connected(layer) { + // Check if ALL to ALL connection + var connections = 0; + for (var here in this.list) { + for (var there in layer.list) { + var from = this.list[here]; + var to = layer.list[there]; + var connected = from.connected(to); + if (connected.type == 'projected') connections++; + } + } + if (connections == this.size * layer.size) return Layer.connectionType.ALL_TO_ALL; + + // Check if ONE to ONE connection + connections = 0; + for (var neuron in this.list) { + var from = this.list[neuron]; + var to = layer.list[neuron]; + var connected = from.connected(to); + if (connected.type == 'projected') connections++; + } + if (connections == this.size) return Layer.connectionType.ONE_TO_ONE; + } + + // clears all the neuorns in the layer + + }, { + key: 'clear', + value: function clear() { + for (var id in this.list) { + var neuron = this.list[id]; + neuron.clear(); + } + } + + // resets all the neurons in the layer + + }, { + key: 'reset', + value: function reset() { + for (var id in this.list) { + var neuron = this.list[id]; + neuron.reset(); + } + } + + // returns all the neurons in the layer (array) + + }, { + key: 'neurons', + value: function neurons() { + return this.list; + } + + // adds a neuron to the layer + + }, { + key: 'add', + value: function add(neuron) { + neuron = neuron || new _Neuron2.default(); + this.list.push(neuron); + this.size++; + } + }, { + key: 'set', + value: function set(options) { + options = options || {}; + + for (var i in this.list) { + var neuron = this.list[i]; + if (options.label) neuron.label = options.label + '_' + neuron.ID; + if (options.squash) neuron.squash = options.squash; + if (options.bias) neuron.bias = options.bias; + } + return this; + } + }]); + + return Layer; +}(); + +Layer.connectionType = connectionType; +Layer.gateType = gateType; +exports.default = Layer; + +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _Neuron = __webpack_require__(2); + +var _Neuron2 = _interopRequireDefault(_Neuron); + +var _Layer = __webpack_require__(0); + +var _Layer2 = _interopRequireDefault(_Layer); + +var _Trainer = __webpack_require__(3); + +var _Trainer2 = _interopRequireDefault(_Trainer); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var Network = function () { + function Network(layers) { + _classCallCheck(this, Network); + + if (typeof layers != 'undefined') { + this.layers = { + input: layers.input || null, + hidden: layers.hidden || [], + output: layers.output || null + }; + this.optimized = null; + } + } + + // feed-forward activation of all the layers to produce an ouput + + + _createClass(Network, [{ + key: 'activate', + value: function activate(input) { + if (this.optimized === false) { + this.layers.input.activate(input); + for (var i = 0; i < this.layers.hidden.length; i++) { + this.layers.hidden[i].activate(); + }return this.layers.output.activate(); + } else { + if (this.optimized == null) this.optimize(); + return this.optimized.activate(input); + } + } + + // back-propagate the error thru the network + + }, { + key: 'propagate', + value: function propagate(rate, target) { + if (this.optimized === false) { + this.layers.output.propagate(rate, target); + for (var i = this.layers.hidden.length - 1; i >= 0; i--) { + this.layers.hidden[i].propagate(rate); + } + } else { + if (this.optimized == null) this.optimize(); + this.optimized.propagate(rate, target); + } + } + + // project a connection to another unit (either a network or a layer) + + }, { + key: 'project', + value: function project(unit, type, weights) { + if (this.optimized) this.optimized.reset(); + + if (unit instanceof Network) return this.layers.output.project(unit.layers.input, type, weights); + + if (unit instanceof _Layer2.default) return this.layers.output.project(unit, type, weights); + + throw new Error('Invalid argument, you can only project connections to LAYERS and NETWORKS!'); + } + + // let this network gate a connection + + }, { + key: 'gate', + value: function gate(connection, type) { + if (this.optimized) this.optimized.reset(); + this.layers.output.gate(connection, type); + } + + // clear all elegibility traces and extended elegibility traces (the network forgets its context, but not what was trained) + + }, { + key: 'clear', + value: function clear() { + this.restore(); + + var inputLayer = this.layers.input, + outputLayer = this.layers.output; + + inputLayer.clear(); + for (var i = 0; i < this.layers.hidden.length; i++) { + this.layers.hidden[i].clear(); + } + outputLayer.clear(); + + if (this.optimized) this.optimized.reset(); + } + + // reset all weights and clear all traces (ends up like a new network) + + }, { + key: 'reset', + value: function reset() { + this.restore(); + + var inputLayer = this.layers.input, + outputLayer = this.layers.output; + + inputLayer.reset(); + for (var i = 0; i < this.layers.hidden.length; i++) { + this.layers.hidden[i].reset(); + } + outputLayer.reset(); + + if (this.optimized) this.optimized.reset(); + } + + // hardcodes the behaviour of the whole network into a single optimized function + + }, { + key: 'optimize', + value: function optimize() { + var that = this; + var optimized = {}; + var neurons = this.neurons(); + + for (var i = 0; i < neurons.length; i++) { + var neuron = neurons[i].neuron; + var layer = neurons[i].layer; + while (neuron.neuron) { + neuron = neuron.neuron; + }optimized = neuron.optimize(optimized, layer); + } + + for (var i = 0; i < optimized.propagation_sentences.length; i++) { + optimized.propagation_sentences[i].reverse(); + }optimized.propagation_sentences.reverse(); + + var hardcode = ''; + hardcode += 'var F = Float64Array ? new Float64Array(' + optimized.memory + ') : []; '; + for (var i in optimized.variables) { + hardcode += 'F[' + optimized.variables[i].id + '] = ' + (optimized.variables[i].value || 0) + '; '; + }hardcode += 'var activate = function(input){\n'; + for (var i = 0; i < optimized.inputs.length; i++) { + hardcode += 'F[' + optimized.inputs[i] + '] = input[' + i + ']; '; + }for (var i = 0; i < optimized.activation_sentences.length; i++) { + if (optimized.activation_sentences[i].length > 0) { + for (var j = 0; j < optimized.activation_sentences[i].length; j++) { + hardcode += optimized.activation_sentences[i][j].join(' '); + hardcode += optimized.trace_sentences[i][j].join(' '); + } + } + } + hardcode += ' var output = []; '; + for (var i = 0; i < optimized.outputs.length; i++) { + hardcode += 'output[' + i + '] = F[' + optimized.outputs[i] + ']; '; + }hardcode += 'return output; }; '; + hardcode += 'var propagate = function(rate, target){\n'; + hardcode += 'F[' + optimized.variables.rate.id + '] = rate; '; + for (var i = 0; i < optimized.targets.length; i++) { + hardcode += 'F[' + optimized.targets[i] + '] = target[' + i + ']; '; + }for (var i = 0; i < optimized.propagation_sentences.length; i++) { + for (var j = 0; j < optimized.propagation_sentences[i].length; j++) { + hardcode += optimized.propagation_sentences[i][j].join(' ') + ' '; + } + }hardcode += ' };\n'; + hardcode += 'var ownership = function(memoryBuffer){\nF = memoryBuffer;\nthis.memory = F;\n};\n'; + hardcode += 'return {\nmemory: F,\nactivate: activate,\npropagate: propagate,\nownership: ownership\n};'; + hardcode = hardcode.split(';').join(';\n'); + + var constructor = new Function(hardcode); + + var network = constructor(); + network.data = { + variables: optimized.variables, + activate: optimized.activation_sentences, + propagate: optimized.propagation_sentences, + trace: optimized.trace_sentences, + inputs: optimized.inputs, + outputs: optimized.outputs, + check_activation: this.activate, + check_propagation: this.propagate + }; + + network.reset = function () { + if (that.optimized) { + that.optimized = null; + that.activate = network.data.check_activation; + that.propagate = network.data.check_propagation; + } + }; + + this.optimized = network; + this.activate = network.activate; + this.propagate = network.propagate; + } + + // restores all the values from the optimized network the their respective objects in order to manipulate the network + + }, { + key: 'restore', + value: function restore() { + if (!this.optimized) return; + + var optimized = this.optimized; + + var getValue = function getValue() { + var args = Array.prototype.slice.call(arguments); + + var unit = args.shift(); + var prop = args.pop(); + + var id = prop + '_'; + for (var property in args) { + id += args[property] + '_'; + }id += unit.ID; + + var memory = optimized.memory; + var variables = optimized.data.variables; + + if (id in variables) return memory[variables[id].id]; + return 0; + }; + + var list = this.neurons(); + + // link id's to positions in the array + for (var i = 0; i < list.length; i++) { + var neuron = list[i].neuron; + while (neuron.neuron) { + neuron = neuron.neuron; + }neuron.state = getValue(neuron, 'state'); + neuron.old = getValue(neuron, 'old'); + neuron.activation = getValue(neuron, 'activation'); + neuron.bias = getValue(neuron, 'bias'); + + for (var input in neuron.trace.elegibility) { + neuron.trace.elegibility[input] = getValue(neuron, 'trace', 'elegibility', input); + }for (var gated in neuron.trace.extended) { + for (var input in neuron.trace.extended[gated]) { + neuron.trace.extended[gated][input] = getValue(neuron, 'trace', 'extended', gated, input); + } + } // get connections + for (var j in neuron.connections.projected) { + var connection = neuron.connections.projected[j]; + connection.weight = getValue(connection, 'weight'); + connection.gain = getValue(connection, 'gain'); + } + } + } + + // returns all the neurons in the network + + }, { + key: 'neurons', + value: function neurons() { + var neurons = []; + + var inputLayer = this.layers.input.neurons(), + outputLayer = this.layers.output.neurons(); + + for (var i = 0; i < inputLayer.length; i++) { + neurons.push({ + neuron: inputLayer[i], + layer: 'input' + }); + } + + for (var i = 0; i < this.layers.hidden.length; i++) { + var hiddenLayer = this.layers.hidden[i].neurons(); + for (var j = 0; j < hiddenLayer.length; j++) { + neurons.push({ + neuron: hiddenLayer[j], + layer: i + }); + } + } + + for (var i = 0; i < outputLayer.length; i++) { + neurons.push({ + neuron: outputLayer[i], + layer: 'output' + }); + } + + return neurons; + } + + // returns number of inputs of the network + + }, { + key: 'inputs', + value: function inputs() { + return this.layers.input.size; + } + + // returns number of outputs of hte network + + }, { + key: 'outputs', + value: function outputs() { + return this.layers.output.size; + } + + // sets the layers of the network + + }, { + key: 'set', + value: function set(layers) { + this.layers = { + input: layers.input || null, + hidden: layers.hidden || [], + output: layers.output || null + }; + if (this.optimized) this.optimized.reset(); + } + }, { + key: 'setOptimize', + value: function setOptimize(bool) { + this.restore(); + if (this.optimized) this.optimized.reset(); + this.optimized = bool ? null : false; + } + + // returns a json that represents all the neurons and connections of the network + + }, { + key: 'toJSON', + value: function toJSON(ignoreTraces) { + this.restore(); + + var list = this.neurons(); + var neurons = []; + var connections = []; + + // link id's to positions in the array + var ids = {}; + for (var i = 0; i < list.length; i++) { + var neuron = list[i].neuron; + while (neuron.neuron) { + neuron = neuron.neuron; + }ids[neuron.ID] = i; + + var copy = { + trace: { + elegibility: {}, + extended: {} + }, + state: neuron.state, + old: neuron.old, + activation: neuron.activation, + bias: neuron.bias, + layer: list[i].layer + }; + + copy.squash = neuron.squash == _Neuron2.default.squash.LOGISTIC ? 'LOGISTIC' : neuron.squash == _Neuron2.default.squash.TANH ? 'TANH' : neuron.squash == _Neuron2.default.squash.IDENTITY ? 'IDENTITY' : neuron.squash == _Neuron2.default.squash.HLIM ? 'HLIM' : neuron.squash == _Neuron2.default.squash.RELU ? 'RELU' : null; + + neurons.push(copy); + } + + for (var i = 0; i < list.length; i++) { + var neuron = list[i].neuron; + while (neuron.neuron) { + neuron = neuron.neuron; + }for (var j in neuron.connections.projected) { + var connection = neuron.connections.projected[j]; + connections.push({ + from: ids[connection.from.ID], + to: ids[connection.to.ID], + weight: connection.weight, + gater: connection.gater ? ids[connection.gater.ID] : null + }); + } + if (neuron.selfconnected()) { + connections.push({ + from: ids[neuron.ID], + to: ids[neuron.ID], + weight: neuron.selfconnection.weight, + gater: neuron.selfconnection.gater ? ids[neuron.selfconnection.gater.ID] : null + }); + } + } + + return { + neurons: neurons, + connections: connections + }; + } + + // export the topology into dot language which can be visualized as graphs using dot + /* example: ... console.log(net.toDotLang()); + $ node example.js > example.dot + $ dot example.dot -Tpng > out.png + */ + + }, { + key: 'toDot', + value: function toDot(edgeConnection) { + if (!(typeof edgeConnection === 'undefined' ? 'undefined' : _typeof(edgeConnection))) edgeConnection = false; + var code = 'digraph nn {\n rankdir = BT\n'; + var layers = [this.layers.input].concat(this.layers.hidden, this.layers.output); + for (var i = 0; i < layers.length; i++) { + for (var j = 0; j < layers[i].connectedTo.length; j++) { + // projections + var connection = layers[i].connectedTo[j]; + var layerTo = connection.to; + var size = connection.size; + var layerID = layers.indexOf(layers[i]); + var layerToID = layers.indexOf(layerTo); + /* http://stackoverflow.com/questions/26845540/connect-edges-with-graph-dot + * DOT does not support edge-to-edge connections + * This workaround produces somewhat weird graphs ... + */ + if (edgeConnection) { + if (connection.gatedfrom.length) { + var fakeNode = 'fake' + layerID + '_' + layerToID; + code += ' ' + fakeNode + ' [label = "", shape = point, width = 0.01, height = 0.01]\n'; + code += ' ' + layerID + ' -> ' + fakeNode + ' [label = ' + size + ', arrowhead = none]\n'; + code += ' ' + fakeNode + ' -> ' + layerToID + '\n'; + } else code += ' ' + layerID + ' -> ' + layerToID + ' [label = ' + size + ']\n'; + for (var from in connection.gatedfrom) { + // gatings + var layerfrom = connection.gatedfrom[from].layer; + var layerfromID = layers.indexOf(layerfrom); + code += ' ' + layerfromID + ' -> ' + fakeNode + ' [color = blue]\n'; + } + } else { + code += ' ' + layerID + ' -> ' + layerToID + ' [label = ' + size + ']\n'; + for (var from in connection.gatedfrom) { + // gatings + var layerfrom = connection.gatedfrom[from].layer; + var layerfromID = layers.indexOf(layerfrom); + code += ' ' + layerfromID + ' -> ' + layerToID + ' [color = blue]\n'; + } + } + } + } + code += '}\n'; + return { + code: code, + link: 'https://chart.googleapis.com/chart?chl=' + escape(code.replace('/ /g', '+')) + '&cht=gv' + }; + } + + // returns a function that works as the activation of the network and can be used without depending on the library + + }, { + key: 'standalone', + value: function standalone() { + if (!this.optimized) this.optimize(); + + var data = this.optimized.data; + + // build activation function + var activation = 'function (input) {\n'; + + // build inputs + for (var i = 0; i < data.inputs.length; i++) { + activation += 'F[' + data.inputs[i] + '] = input[' + i + '];\n'; + } // build network activation + for (var i = 0; i < data.activate.length; i++) { + // shouldn't this be layer? + for (var j = 0; j < data.activate[i].length; j++) { + activation += data.activate[i][j].join('') + '\n'; + } + } + + // build outputs + activation += 'var output = [];\n'; + for (var i = 0; i < data.outputs.length; i++) { + activation += 'output[' + i + '] = F[' + data.outputs[i] + '];\n'; + }activation += 'return output;\n}'; + + // reference all the positions in memory + var memory = activation.match(/F\[(\d+)\]/g); + var dimension = 0; + var ids = {}; + + for (var i = 0; i < memory.length; i++) { + var tmp = memory[i].match(/\d+/)[0]; + if (!(tmp in ids)) { + ids[tmp] = dimension++; + } + } + var hardcode = 'F = {\n'; + + for (var i in ids) { + hardcode += ids[i] + ': ' + this.optimized.memory[i] + ',\n'; + }hardcode = hardcode.substring(0, hardcode.length - 2) + '\n};\n'; + hardcode = 'var run = ' + activation.replace(/F\[(\d+)]/g, function (index) { + return 'F[' + ids[index.match(/\d+/)[0]] + ']'; + }).replace('{\n', '{\n' + hardcode + '') + ';\n'; + hardcode += 'return run'; + + // return standalone function + return new Function(hardcode)(); + } + + // Return a HTML5 WebWorker specialized on training the network stored in `memory`. + // Train based on the given dataSet and options. + // The worker returns the updated `memory` when done. + + }, { + key: 'worker', + value: function worker(memory, set, options) { + // Copy the options and set defaults (options might be different for each worker) + var workerOptions = {}; + if (options) workerOptions = options; + workerOptions.rate = workerOptions.rate || .2; + workerOptions.iterations = workerOptions.iterations || 100000; + workerOptions.error = workerOptions.error || .005; + workerOptions.cost = workerOptions.cost || null; + workerOptions.crossValidate = workerOptions.crossValidate || null; + + // Cost function might be different for each worker + var costFunction = '// REPLACED BY WORKER\nvar cost = ' + (options && options.cost || this.cost || _Trainer2.default.cost.MSE) + ';\n'; + var workerFunction = Network.getWorkerSharedFunctions(); + workerFunction = workerFunction.replace(/var cost = options && options\.cost \|\| this\.cost \|\| Trainer\.cost\.MSE;/g, costFunction); + + // Set what we do when training is finished + workerFunction = workerFunction.replace('return results;', 'postMessage({action: "done", message: results, memoryBuffer: F}, [F.buffer]);'); + + // Replace log with postmessage + workerFunction = workerFunction.replace('console.log(\'iterations\', iterations, \'error\', error, \'rate\', currentRate)', 'postMessage({action: \'log\', message: {\n' + 'iterations: iterations,\n' + 'error: error,\n' + 'rate: currentRate\n' + '}\n' + '})'); + + // Replace schedule with postmessage + workerFunction = workerFunction.replace('abort = this.schedule.do({ error: error, iterations: iterations, rate: currentRate })', 'postMessage({action: \'schedule\', message: {\n' + 'iterations: iterations,\n' + 'error: error,\n' + 'rate: currentRate\n' + '}\n' + '})'); + + if (!this.optimized) this.optimize(); + + var hardcode = 'var inputs = ' + this.optimized.data.inputs.length + ';\n'; + hardcode += 'var outputs = ' + this.optimized.data.outputs.length + ';\n'; + hardcode += 'var F = new Float64Array([' + this.optimized.memory.toString() + ']);\n'; + hardcode += 'var activate = ' + this.optimized.activate.toString() + ';\n'; + hardcode += 'var propagate = ' + this.optimized.propagate.toString() + ';\n'; + hardcode += 'onmessage = function(e) {\n' + 'if (e.data.action == \'startTraining\') {\n' + 'train(' + JSON.stringify(set) + ',' + JSON.stringify(workerOptions) + ');\n' + '}\n' + '}'; + + var workerSourceCode = workerFunction + '\n' + hardcode; + var blob = new Blob([workerSourceCode]); + var blobURL = window.URL.createObjectURL(blob); + + return new Worker(blobURL); + } + + // returns a copy of the network + + }, { + key: 'clone', + value: function clone() { + return Network.fromJSON(this.toJSON()); + } + + /** + * Creates a static String to store the source code of the functions + * that are identical for all the workers (train, _trainSet, test) + * + * @return {String} Source code that can train a network inside a worker. + * @static + */ + + }], [{ + key: 'getWorkerSharedFunctions', + value: function getWorkerSharedFunctions() { + // If we already computed the source code for the shared functions + if (typeof Network._SHARED_WORKER_FUNCTIONS !== 'undefined') return Network._SHARED_WORKER_FUNCTIONS; + + // Otherwise compute and return the source code + // We compute them by simply copying the source code of the train, _trainSet and test functions + // using the .toString() method + + // Load and name the train function + var train_f = _Trainer2.default.prototype.train.toString(); + train_f = train_f.replace(/this._trainSet/g, '_trainSet'); + train_f = train_f.replace(/this.test/g, 'test'); + train_f = train_f.replace(/this.crossValidate/g, 'crossValidate'); + train_f = train_f.replace('crossValidate = true', '// REMOVED BY WORKER'); + + // Load and name the _trainSet function + var _trainSet_f = _Trainer2.default.prototype._trainSet.toString().replace(/this.network./g, ''); + + // Load and name the test function + var test_f = _Trainer2.default.prototype.test.toString().replace(/this.network./g, ''); + + return Network._SHARED_WORKER_FUNCTIONS = train_f + '\n' + _trainSet_f + '\n' + test_f; + } + }, { + key: 'fromJSON', + value: function fromJSON(json) { + var neurons = []; + + var layers = { + input: new _Layer2.default(), + hidden: [], + output: new _Layer2.default() + }; + + for (var i = 0; i < json.neurons.length; i++) { + var config = json.neurons[i]; + + var neuron = new _Neuron2.default(); + neuron.trace.elegibility = {}; + neuron.trace.extended = {}; + neuron.state = config.state; + neuron.old = config.old; + neuron.activation = config.activation; + neuron.bias = config.bias; + neuron.squash = config.squash in _Neuron2.default.squash ? _Neuron2.default.squash[config.squash] : _Neuron2.default.squash.LOGISTIC; + neurons.push(neuron); + + if (config.layer == 'input') layers.input.add(neuron);else if (config.layer == 'output') layers.output.add(neuron);else { + if (typeof layers.hidden[config.layer] == 'undefined') layers.hidden[config.layer] = new _Layer2.default(); + layers.hidden[config.layer].add(neuron); + } + } + + for (var i = 0; i < json.connections.length; i++) { + var config = json.connections[i]; + var from = neurons[config.from]; + var to = neurons[config.to]; + var weight = config.weight; + var gater = neurons[config.gater]; + + var connection = from.project(to, weight); + if (gater) gater.gate(connection); + } + + return new Network(layers); + } + }]); + + return Network; +}(); + +exports.default = Network; + +/***/ }), +/* 2 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _Connection = __webpack_require__(5); + +var _Connection2 = _interopRequireDefault(_Connection); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var neurons = 0; + +// squashing functions +var squash = { + // eq. 5 & 5' + LOGISTIC: function LOGISTIC(x, derivate) { + var fx = 1 / (1 + Math.exp(-x)); + if (!derivate) return fx; + return fx * (1 - fx); + }, + TANH: function TANH(x, derivate) { + if (derivate) return 1 - Math.pow(Math.tanh(x), 2); + return Math.tanh(x); + }, + IDENTITY: function IDENTITY(x, derivate) { + return derivate ? 1 : x; + }, + HLIM: function HLIM(x, derivate) { + return derivate ? 1 : x > 0 ? 1 : 0; + }, + RELU: function RELU(x, derivate) { + if (derivate) return x > 0 ? 1 : 0; + return x > 0 ? x : 0; + } +}; + +var Neuron = function () { + function Neuron() { + _classCallCheck(this, Neuron); + + this.ID = Neuron.uid(); + + this.connections = { + inputs: {}, + projected: {}, + gated: {} + }; + this.error = { + responsibility: 0, + projected: 0, + gated: 0 + }; + this.trace = { + elegibility: {}, + extended: {}, + influences: {} + }; + this.state = 0; + this.old = 0; + this.activation = 0; + this.selfconnection = new _Connection2.default(this, this, 0); // weight = 0 -> not connected + this.squash = Neuron.squash.LOGISTIC; + this.neighboors = {}; + this.bias = Math.random() * .2 - .1; + } + + // activate the neuron + + + _createClass(Neuron, [{ + key: 'activate', + value: function activate(input) { + // activation from enviroment (for input neurons) + if (typeof input != 'undefined') { + this.activation = input; + this.derivative = 0; + this.bias = 0; + return this.activation; + } + + // old state + this.old = this.state; + + // eq. 15 + this.state = this.selfconnection.gain * this.selfconnection.weight * this.state + this.bias; + + for (var i in this.connections.inputs) { + var input = this.connections.inputs[i]; + this.state += input.from.activation * input.weight * input.gain; + } + + // eq. 16 + this.activation = this.squash(this.state); + + // f'(s) + this.derivative = this.squash(this.state, true); + + // update traces + var influences = []; + for (var id in this.trace.extended) { + // extended elegibility trace + var neuron = this.neighboors[id]; + + // if gated neuron's selfconnection is gated by this unit, the influence keeps track of the neuron's old state + var influence = neuron.selfconnection.gater == this ? neuron.old : 0; + + // index runs over all the incoming connections to the gated neuron that are gated by this unit + for (var incoming in this.trace.influences[neuron.ID]) { + // captures the effect that has an input connection to this unit, on a neuron that is gated by this unit + influence += this.trace.influences[neuron.ID][incoming].weight * this.trace.influences[neuron.ID][incoming].from.activation; + } + influences[neuron.ID] = influence; + } + + for (var i in this.connections.inputs) { + var input = this.connections.inputs[i]; + + // elegibility trace - Eq. 17 + this.trace.elegibility[input.ID] = this.selfconnection.gain * this.selfconnection.weight * this.trace.elegibility[input.ID] + input.gain * input.from.activation; + + for (var id in this.trace.extended) { + // extended elegibility trace + var xtrace = this.trace.extended[id]; + var neuron = this.neighboors[id]; + var influence = influences[neuron.ID]; + + // eq. 18 + xtrace[input.ID] = neuron.selfconnection.gain * neuron.selfconnection.weight * xtrace[input.ID] + this.derivative * this.trace.elegibility[input.ID] * influence; + } + } + + // update gated connection's gains + for (var connection in this.connections.gated) { + this.connections.gated[connection].gain = this.activation; + } + + return this.activation; + } + + // back-propagate the error + + }, { + key: 'propagate', + value: function propagate(rate, target) { + // error accumulator + var error = 0; + + // whether or not this neuron is in the output layer + var isOutput = typeof target != 'undefined'; + + // output neurons get their error from the enviroment + if (isOutput) this.error.responsibility = this.error.projected = target - this.activation; // Eq. 10 + + else // the rest of the neuron compute their error responsibilities by backpropagation + { + // error responsibilities from all the connections projected from this neuron + for (var id in this.connections.projected) { + var connection = this.connections.projected[id]; + var neuron = connection.to; + // Eq. 21 + error += neuron.error.responsibility * connection.gain * connection.weight; + } + + // projected error responsibility + this.error.projected = this.derivative * error; + + error = 0; + // error responsibilities from all the connections gated by this neuron + for (var id in this.trace.extended) { + var neuron = this.neighboors[id]; // gated neuron + var influence = neuron.selfconnection.gater == this ? neuron.old : 0; // if gated neuron's selfconnection is gated by this neuron + + // index runs over all the connections to the gated neuron that are gated by this neuron + for (var input in this.trace.influences[id]) { + // captures the effect that the input connection of this neuron have, on a neuron which its input/s is/are gated by this neuron + influence += this.trace.influences[id][input].weight * this.trace.influences[neuron.ID][input].from.activation; + } + // eq. 22 + error += neuron.error.responsibility * influence; + } + + // gated error responsibility + this.error.gated = this.derivative * error; + + // error responsibility - Eq. 23 + this.error.responsibility = this.error.projected + this.error.gated; + } + + // learning rate + rate = rate || .1; + + // adjust all the neuron's incoming connections + for (var id in this.connections.inputs) { + var input = this.connections.inputs[id]; + + // Eq. 24 + var gradient = this.error.projected * this.trace.elegibility[input.ID]; + for (var id in this.trace.extended) { + var neuron = this.neighboors[id]; + gradient += neuron.error.responsibility * this.trace.extended[neuron.ID][input.ID]; + } + input.weight += rate * gradient; // adjust weights - aka learn + } + + // adjust bias + this.bias += rate * this.error.responsibility; + } + }, { + key: 'project', + value: function project(neuron, weight) { + // self-connection + if (neuron == this) { + this.selfconnection.weight = 1; + return this.selfconnection; + } + + // check if connection already exists + var connected = this.connected(neuron); + if (connected && connected.type == 'projected') { + // update connection + if (typeof weight != 'undefined') connected.connection.weight = weight; + // return existing connection + return connected.connection; + } else { + // create a new connection + var connection = new _Connection2.default(this, neuron, weight); + } + + // reference all the connections and traces + this.connections.projected[connection.ID] = connection; + this.neighboors[neuron.ID] = neuron; + neuron.connections.inputs[connection.ID] = connection; + neuron.trace.elegibility[connection.ID] = 0; + + for (var id in neuron.trace.extended) { + var trace = neuron.trace.extended[id]; + trace[connection.ID] = 0; + } + + return connection; + } + }, { + key: 'gate', + value: function gate(connection) { + // add connection to gated list + this.connections.gated[connection.ID] = connection; + + var neuron = connection.to; + if (!(neuron.ID in this.trace.extended)) { + // extended trace + this.neighboors[neuron.ID] = neuron; + var xtrace = this.trace.extended[neuron.ID] = {}; + for (var id in this.connections.inputs) { + var input = this.connections.inputs[id]; + xtrace[input.ID] = 0; + } + } + + // keep track + if (neuron.ID in this.trace.influences) this.trace.influences[neuron.ID].push(connection);else this.trace.influences[neuron.ID] = [connection]; + + // set gater + connection.gater = this; + } + + // returns true or false whether the neuron is self-connected or not + + }, { + key: 'selfconnected', + value: function selfconnected() { + return this.selfconnection.weight !== 0; + } + + // returns true or false whether the neuron is connected to another neuron (parameter) + + }, { + key: 'connected', + value: function connected(neuron) { + var result = { + type: null, + connection: false + }; + + if (this == neuron) { + if (this.selfconnected()) { + result.type = 'selfconnection'; + result.connection = this.selfconnection; + return result; + } else return false; + } + + for (var type in this.connections) { + for (var connection in this.connections[type]) { + var connection = this.connections[type][connection]; + if (connection.to == neuron) { + result.type = type; + result.connection = connection; + return result; + } else if (connection.from == neuron) { + result.type = type; + result.connection = connection; + return result; + } + } + } + + return false; + } + + // clears all the traces (the neuron forgets it's context, but the connections remain intact) + + }, { + key: 'clear', + value: function clear() { + for (var trace in this.trace.elegibility) { + this.trace.elegibility[trace] = 0; + } + + for (var trace in this.trace.extended) { + for (var extended in this.trace.extended[trace]) { + this.trace.extended[trace][extended] = 0; + } + } + + this.error.responsibility = this.error.projected = this.error.gated = 0; + } + + // all the connections are randomized and the traces are cleared + + }, { + key: 'reset', + value: function reset() { + this.clear(); + + for (var type in this.connections) { + for (var connection in this.connections[type]) { + this.connections[type][connection].weight = Math.random() * .2 - .1; + } + } + + this.bias = Math.random() * .2 - .1; + this.old = this.state = this.activation = 0; + } + + // hardcodes the behaviour of the neuron into an optimized function + + }, { + key: 'optimize', + value: function optimize(optimized, layer) { + + optimized = optimized || {}; + var store_activation = []; + var store_trace = []; + var store_propagation = []; + var varID = optimized.memory || 0; + var neurons = optimized.neurons || 1; + var inputs = optimized.inputs || []; + var targets = optimized.targets || []; + var outputs = optimized.outputs || []; + var variables = optimized.variables || {}; + var activation_sentences = optimized.activation_sentences || []; + var trace_sentences = optimized.trace_sentences || []; + var propagation_sentences = optimized.propagation_sentences || []; + var layers = optimized.layers || { __count: 0, __neuron: 0 }; + + // allocate sentences + var allocate = function allocate(store) { + var allocated = layer in layers && store[layers.__count]; + if (!allocated) { + layers.__count = store.push([]) - 1; + layers[layer] = layers.__count; + } + }; + allocate(activation_sentences); + allocate(trace_sentences); + allocate(propagation_sentences); + var currentLayer = layers.__count; + + // get/reserve space in memory by creating a unique ID for a variablel + var getVar = function getVar() { + var args = Array.prototype.slice.call(arguments); + + if (args.length == 1) { + if (args[0] == 'target') { + var id = 'target_' + targets.length; + targets.push(varID); + } else var id = args[0]; + if (id in variables) return variables[id]; + return variables[id] = { + value: 0, + id: varID++ + }; + } else { + var extended = args.length > 2; + if (extended) var value = args.pop(); + + var unit = args.shift(); + var prop = args.pop(); + + if (!extended) var value = unit[prop]; + + var id = prop + '_'; + for (var i = 0; i < args.length; i++) { + id += args[i] + '_'; + }id += unit.ID; + if (id in variables) return variables[id]; + + return variables[id] = { + value: value, + id: varID++ + }; + } + }; + + // build sentence + var buildSentence = function buildSentence() { + var args = Array.prototype.slice.call(arguments); + var store = args.pop(); + var sentence = ''; + for (var i = 0; i < args.length; i++) { + if (typeof args[i] == 'string') sentence += args[i];else sentence += 'F[' + args[i].id + ']'; + }store.push(sentence + ';'); + }; + + // helper to check if an object is empty + var isEmpty = function isEmpty(obj) { + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) return false; + } + return true; + }; + + // characteristics of the neuron + var noProjections = isEmpty(this.connections.projected); + var noGates = isEmpty(this.connections.gated); + var isInput = layer == 'input' ? true : isEmpty(this.connections.inputs); + var isOutput = layer == 'output' ? true : noProjections && noGates; + + // optimize neuron's behaviour + var rate = getVar('rate'); + var activation = getVar(this, 'activation'); + if (isInput) inputs.push(activation.id);else { + activation_sentences[currentLayer].push(store_activation); + trace_sentences[currentLayer].push(store_trace); + propagation_sentences[currentLayer].push(store_propagation); + var old = getVar(this, 'old'); + var state = getVar(this, 'state'); + var bias = getVar(this, 'bias'); + if (this.selfconnection.gater) var self_gain = getVar(this.selfconnection, 'gain'); + if (this.selfconnected()) var self_weight = getVar(this.selfconnection, 'weight'); + buildSentence(old, ' = ', state, store_activation); + if (this.selfconnected()) { + if (this.selfconnection.gater) buildSentence(state, ' = ', self_gain, ' * ', self_weight, ' * ', state, ' + ', bias, store_activation);else buildSentence(state, ' = ', self_weight, ' * ', state, ' + ', bias, store_activation); + } else buildSentence(state, ' = ', bias, store_activation); + for (var i in this.connections.inputs) { + var input = this.connections.inputs[i]; + var input_activation = getVar(input.from, 'activation'); + var input_weight = getVar(input, 'weight'); + if (input.gater) var input_gain = getVar(input, 'gain'); + if (this.connections.inputs[i].gater) buildSentence(state, ' += ', input_activation, ' * ', input_weight, ' * ', input_gain, store_activation);else buildSentence(state, ' += ', input_activation, ' * ', input_weight, store_activation); + } + var derivative = getVar(this, 'derivative'); + switch (this.squash) { + case Neuron.squash.LOGISTIC: + buildSentence(activation, ' = (1 / (1 + Math.exp(-', state, ')))', store_activation); + buildSentence(derivative, ' = ', activation, ' * (1 - ', activation, ')', store_activation); + break; + case Neuron.squash.TANH: + var eP = getVar('aux'); + var eN = getVar('aux_2'); + buildSentence(eP, ' = Math.exp(', state, ')', store_activation); + buildSentence(eN, ' = 1 / ', eP, store_activation); + buildSentence(activation, ' = (', eP, ' - ', eN, ') / (', eP, ' + ', eN, ')', store_activation); + buildSentence(derivative, ' = 1 - (', activation, ' * ', activation, ')', store_activation); + break; + case Neuron.squash.IDENTITY: + buildSentence(activation, ' = ', state, store_activation); + buildSentence(derivative, ' = 1', store_activation); + break; + case Neuron.squash.HLIM: + buildSentence(activation, ' = +(', state, ' > 0)', store_activation); + buildSentence(derivative, ' = 1', store_activation); + break; + case Neuron.squash.RELU: + buildSentence(activation, ' = ', state, ' > 0 ? ', state, ' : 0', store_activation); + buildSentence(derivative, ' = ', state, ' > 0 ? 1 : 0', store_activation); + break; + } + + for (var id in this.trace.extended) { + // calculate extended elegibility traces in advance + var neuron = this.neighboors[id]; + var influence = getVar('influences[' + neuron.ID + ']'); + var neuron_old = getVar(neuron, 'old'); + var initialized = false; + if (neuron.selfconnection.gater == this) { + buildSentence(influence, ' = ', neuron_old, store_trace); + initialized = true; + } + for (var incoming in this.trace.influences[neuron.ID]) { + var incoming_weight = getVar(this.trace.influences[neuron.ID][incoming], 'weight'); + var incoming_activation = getVar(this.trace.influences[neuron.ID][incoming].from, 'activation'); + + if (initialized) buildSentence(influence, ' += ', incoming_weight, ' * ', incoming_activation, store_trace);else { + buildSentence(influence, ' = ', incoming_weight, ' * ', incoming_activation, store_trace); + initialized = true; + } + } + } + + for (var i in this.connections.inputs) { + var input = this.connections.inputs[i]; + if (input.gater) var input_gain = getVar(input, 'gain'); + var input_activation = getVar(input.from, 'activation'); + var trace = getVar(this, 'trace', 'elegibility', input.ID, this.trace.elegibility[input.ID]); + if (this.selfconnected()) { + if (this.selfconnection.gater) { + if (input.gater) buildSentence(trace, ' = ', self_gain, ' * ', self_weight, ' * ', trace, ' + ', input_gain, ' * ', input_activation, store_trace);else buildSentence(trace, ' = ', self_gain, ' * ', self_weight, ' * ', trace, ' + ', input_activation, store_trace); + } else { + if (input.gater) buildSentence(trace, ' = ', self_weight, ' * ', trace, ' + ', input_gain, ' * ', input_activation, store_trace);else buildSentence(trace, ' = ', self_weight, ' * ', trace, ' + ', input_activation, store_trace); + } + } else { + if (input.gater) buildSentence(trace, ' = ', input_gain, ' * ', input_activation, store_trace);else buildSentence(trace, ' = ', input_activation, store_trace); + } + for (var id in this.trace.extended) { + // extended elegibility trace + var neuron = this.neighboors[id]; + var influence = getVar('influences[' + neuron.ID + ']'); + + var trace = getVar(this, 'trace', 'elegibility', input.ID, this.trace.elegibility[input.ID]); + var xtrace = getVar(this, 'trace', 'extended', neuron.ID, input.ID, this.trace.extended[neuron.ID][input.ID]); + if (neuron.selfconnected()) var neuron_self_weight = getVar(neuron.selfconnection, 'weight'); + if (neuron.selfconnection.gater) var neuron_self_gain = getVar(neuron.selfconnection, 'gain'); + if (neuron.selfconnected()) { + if (neuron.selfconnection.gater) buildSentence(xtrace, ' = ', neuron_self_gain, ' * ', neuron_self_weight, ' * ', xtrace, ' + ', derivative, ' * ', trace, ' * ', influence, store_trace);else buildSentence(xtrace, ' = ', neuron_self_weight, ' * ', xtrace, ' + ', derivative, ' * ', trace, ' * ', influence, store_trace); + } else buildSentence(xtrace, ' = ', derivative, ' * ', trace, ' * ', influence, store_trace); + } + } + for (var connection in this.connections.gated) { + var gated_gain = getVar(this.connections.gated[connection], 'gain'); + buildSentence(gated_gain, ' = ', activation, store_activation); + } + } + if (!isInput) { + var responsibility = getVar(this, 'error', 'responsibility', this.error.responsibility); + if (isOutput) { + var target = getVar('target'); + buildSentence(responsibility, ' = ', target, ' - ', activation, store_propagation); + for (var id in this.connections.inputs) { + var input = this.connections.inputs[id]; + var trace = getVar(this, 'trace', 'elegibility', input.ID, this.trace.elegibility[input.ID]); + var input_weight = getVar(input, 'weight'); + buildSentence(input_weight, ' += ', rate, ' * (', responsibility, ' * ', trace, ')', store_propagation); + } + outputs.push(activation.id); + } else { + if (!noProjections && !noGates) { + var error = getVar('aux'); + for (var id in this.connections.projected) { + var connection = this.connections.projected[id]; + var neuron = connection.to; + var connection_weight = getVar(connection, 'weight'); + var neuron_responsibility = getVar(neuron, 'error', 'responsibility', neuron.error.responsibility); + if (connection.gater) { + var connection_gain = getVar(connection, 'gain'); + buildSentence(error, ' += ', neuron_responsibility, ' * ', connection_gain, ' * ', connection_weight, store_propagation); + } else buildSentence(error, ' += ', neuron_responsibility, ' * ', connection_weight, store_propagation); + } + var projected = getVar(this, 'error', 'projected', this.error.projected); + buildSentence(projected, ' = ', derivative, ' * ', error, store_propagation); + buildSentence(error, ' = 0', store_propagation); + for (var id in this.trace.extended) { + var neuron = this.neighboors[id]; + var influence = getVar('aux_2'); + var neuron_old = getVar(neuron, 'old'); + if (neuron.selfconnection.gater == this) buildSentence(influence, ' = ', neuron_old, store_propagation);else buildSentence(influence, ' = 0', store_propagation); + for (var input in this.trace.influences[neuron.ID]) { + var connection = this.trace.influences[neuron.ID][input]; + var connection_weight = getVar(connection, 'weight'); + var neuron_activation = getVar(connection.from, 'activation'); + buildSentence(influence, ' += ', connection_weight, ' * ', neuron_activation, store_propagation); + } + var neuron_responsibility = getVar(neuron, 'error', 'responsibility', neuron.error.responsibility); + buildSentence(error, ' += ', neuron_responsibility, ' * ', influence, store_propagation); + } + var gated = getVar(this, 'error', 'gated', this.error.gated); + buildSentence(gated, ' = ', derivative, ' * ', error, store_propagation); + buildSentence(responsibility, ' = ', projected, ' + ', gated, store_propagation); + for (var id in this.connections.inputs) { + var input = this.connections.inputs[id]; + var gradient = getVar('aux'); + var trace = getVar(this, 'trace', 'elegibility', input.ID, this.trace.elegibility[input.ID]); + buildSentence(gradient, ' = ', projected, ' * ', trace, store_propagation); + for (var id in this.trace.extended) { + var neuron = this.neighboors[id]; + var neuron_responsibility = getVar(neuron, 'error', 'responsibility', neuron.error.responsibility); + var xtrace = getVar(this, 'trace', 'extended', neuron.ID, input.ID, this.trace.extended[neuron.ID][input.ID]); + buildSentence(gradient, ' += ', neuron_responsibility, ' * ', xtrace, store_propagation); + } + var input_weight = getVar(input, 'weight'); + buildSentence(input_weight, ' += ', rate, ' * ', gradient, store_propagation); + } + } else if (noGates) { + buildSentence(responsibility, ' = 0', store_propagation); + for (var id in this.connections.projected) { + var connection = this.connections.projected[id]; + var neuron = connection.to; + var connection_weight = getVar(connection, 'weight'); + var neuron_responsibility = getVar(neuron, 'error', 'responsibility', neuron.error.responsibility); + if (connection.gater) { + var connection_gain = getVar(connection, 'gain'); + buildSentence(responsibility, ' += ', neuron_responsibility, ' * ', connection_gain, ' * ', connection_weight, store_propagation); + } else buildSentence(responsibility, ' += ', neuron_responsibility, ' * ', connection_weight, store_propagation); + } + buildSentence(responsibility, ' *= ', derivative, store_propagation); + for (var id in this.connections.inputs) { + var input = this.connections.inputs[id]; + var trace = getVar(this, 'trace', 'elegibility', input.ID, this.trace.elegibility[input.ID]); + var input_weight = getVar(input, 'weight'); + buildSentence(input_weight, ' += ', rate, ' * (', responsibility, ' * ', trace, ')', store_propagation); + } + } else if (noProjections) { + buildSentence(responsibility, ' = 0', store_propagation); + for (var id in this.trace.extended) { + var neuron = this.neighboors[id]; + var influence = getVar('aux'); + var neuron_old = getVar(neuron, 'old'); + if (neuron.selfconnection.gater == this) buildSentence(influence, ' = ', neuron_old, store_propagation);else buildSentence(influence, ' = 0', store_propagation); + for (var input in this.trace.influences[neuron.ID]) { + var connection = this.trace.influences[neuron.ID][input]; + var connection_weight = getVar(connection, 'weight'); + var neuron_activation = getVar(connection.from, 'activation'); + buildSentence(influence, ' += ', connection_weight, ' * ', neuron_activation, store_propagation); + } + var neuron_responsibility = getVar(neuron, 'error', 'responsibility', neuron.error.responsibility); + buildSentence(responsibility, ' += ', neuron_responsibility, ' * ', influence, store_propagation); + } + buildSentence(responsibility, ' *= ', derivative, store_propagation); + for (var id in this.connections.inputs) { + var input = this.connections.inputs[id]; + var gradient = getVar('aux'); + buildSentence(gradient, ' = 0', store_propagation); + for (var id in this.trace.extended) { + var neuron = this.neighboors[id]; + var neuron_responsibility = getVar(neuron, 'error', 'responsibility', neuron.error.responsibility); + var xtrace = getVar(this, 'trace', 'extended', neuron.ID, input.ID, this.trace.extended[neuron.ID][input.ID]); + buildSentence(gradient, ' += ', neuron_responsibility, ' * ', xtrace, store_propagation); + } + var input_weight = getVar(input, 'weight'); + buildSentence(input_weight, ' += ', rate, ' * ', gradient, store_propagation); + } + } + } + buildSentence(bias, ' += ', rate, ' * ', responsibility, store_propagation); + } + return { + memory: varID, + neurons: neurons + 1, + inputs: inputs, + outputs: outputs, + targets: targets, + variables: variables, + activation_sentences: activation_sentences, + trace_sentences: trace_sentences, + propagation_sentences: propagation_sentences, + layers: layers + }; + } + }], [{ + key: 'uid', + value: function uid() { + return neurons++; + } + }, { + key: 'quantity', + value: function quantity() { + return { + neurons: neurons, + connections: _Connection.connections + }; + } + }]); + + return Neuron; +}(); + +Neuron.squash = squash; +exports.default = Neuron; + +/***/ }), +/* 3 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +//+ Jonas Raoni Soares Silva +//@ http://jsfromhell.com/array/shuffle [v1.0] +function shuffleInplace(o) { + //v1.0 + for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x) {} + return o; +}; + +// Built-in cost functions +var cost = { + // Eq. 9 + CROSS_ENTROPY: function CROSS_ENTROPY(target, output) { + var crossentropy = 0; + for (var i in output) { + crossentropy -= target[i] * Math.log(output[i] + 1e-15) + (1 - target[i]) * Math.log(1 + 1e-15 - output[i]); + } // +1e-15 is a tiny push away to avoid Math.log(0) + return crossentropy; + }, + MSE: function MSE(target, output) { + var mse = 0; + for (var i = 0; i < output.length; i++) { + mse += Math.pow(target[i] - output[i], 2); + }return mse / output.length; + }, + BINARY: function BINARY(target, output) { + var misses = 0; + for (var i = 0; i < output.length; i++) { + misses += Math.round(target[i] * 2) != Math.round(output[i] * 2); + }return misses; + } +}; + +var Trainer = function () { + function Trainer(network, options) { + _classCallCheck(this, Trainer); + + options = options || {}; + this.network = network; + this.rate = options.rate || .2; + this.iterations = options.iterations || 100000; + this.error = options.error || .005; + this.cost = options.cost || null; + this.crossValidate = options.crossValidate || null; + } + + // trains any given set to a network + + + _createClass(Trainer, [{ + key: 'train', + value: function train(set, options) { + var error = 1; + var iterations = bucketSize = 0; + var abort = false; + var currentRate; + var cost = options && options.cost || this.cost || Trainer.cost.MSE; + var crossValidate = false, + testSet, + trainSet; + + var start = Date.now(); + + if (options) { + if (options.iterations) this.iterations = options.iterations; + if (options.error) this.error = options.error; + if (options.rate) this.rate = options.rate; + if (options.cost) this.cost = options.cost; + if (options.schedule) this.schedule = options.schedule; + if (options.customLog) { + // for backward compatibility with code that used customLog + console.log('Deprecated: use schedule instead of customLog'); + this.schedule = options.customLog; + } + if (this.crossValidate || options.crossValidate) { + if (!this.crossValidate) this.crossValidate = {}; + crossValidate = true; + if (options.crossValidate.testSize) this.crossValidate.testSize = options.crossValidate.testSize; + if (options.crossValidate.testError) this.crossValidate.testError = options.crossValidate.testError; + } + } + + currentRate = this.rate; + if (Array.isArray(this.rate)) { + var bucketSize = Math.floor(this.iterations / this.rate.length); + } + + if (crossValidate) { + var numTrain = Math.ceil((1 - this.crossValidate.testSize) * set.length); + trainSet = set.slice(0, numTrain); + testSet = set.slice(numTrain); + } + + var lastError = 0; + while (!abort && iterations < this.iterations && error > this.error) { + if (crossValidate && error <= this.crossValidate.testError) { + break; + } + + var currentSetSize = set.length; + error = 0; + iterations++; + + if (bucketSize > 0) { + var currentBucket = Math.floor(iterations / bucketSize); + currentRate = this.rate[currentBucket] || currentRate; + } + + if (typeof this.rate === 'function') { + currentRate = this.rate(iterations, lastError); + } + + if (crossValidate) { + this._trainSet(trainSet, currentRate, cost); + error += this.test(testSet).error; + currentSetSize = 1; + } else { + error += this._trainSet(set, currentRate, cost); + currentSetSize = set.length; + } + + // check error + error /= currentSetSize; + lastError = error; + + if (options) { + if (this.schedule && this.schedule.every && iterations % this.schedule.every == 0) abort = this.schedule.do({ error: error, iterations: iterations, rate: currentRate });else if (options.log && iterations % options.log == 0) { + console.log('iterations', iterations, 'error', error, 'rate', currentRate); + } + ; + if (options.shuffle) shuffleInplace(set); + } + } + + var results = { + error: error, + iterations: iterations, + time: Date.now() - start + }; + + return results; + } + + // trains any given set to a network, using a WebWorker (only for the browser). Returns a Promise of the results. + + }, { + key: 'trainAsync', + value: function trainAsync(set, options) { + var train = this.workerTrain.bind(this); + return new Promise(function (resolve, reject) { + try { + train(set, resolve, options, true); + } catch (e) { + reject(e); + } + }); + } + + // preforms one training epoch and returns the error (private function used in this.train) + + }, { + key: '_trainSet', + value: function _trainSet(set, currentRate, costFunction) { + var errorSum = 0; + for (var i = 0; i < set.length; i++) { + var input = set[i].input; + var target = set[i].output; + + var output = this.network.activate(input); + this.network.propagate(currentRate, target); + + errorSum += costFunction(target, output); + } + return errorSum; + } + + // tests a set and returns the error and elapsed time + + }, { + key: 'test', + value: function test(set, options) { + var error = 0; + var input, output, target; + var cost = options && options.cost || this.cost || Trainer.cost.MSE; + + var start = Date.now(); + + for (var i = 0; i < set.length; i++) { + input = set[i].input; + target = set[i].output; + output = this.network.activate(input); + error += cost(target, output); + } + + error /= set.length; + + var results = { + error: error, + time: Date.now() - start + }; + + return results; + } + + // trains any given set to a network using a WebWorker [deprecated: use trainAsync instead] + + }, { + key: 'workerTrain', + value: function workerTrain(set, callback, options, suppressWarning) { + if (!suppressWarning) { + console.warn('Deprecated: do not use `workerTrain`, use `trainAsync` instead.'); + } + var that = this; + + if (!this.network.optimized) this.network.optimize(); + + // Create a new worker + var worker = this.network.worker(this.network.optimized.memory, set, options); + + // train the worker + worker.onmessage = function (e) { + switch (e.data.action) { + case 'done': + var iterations = e.data.message.iterations; + var error = e.data.message.error; + var time = e.data.message.time; + + that.network.optimized.ownership(e.data.memoryBuffer); + + // Done callback + callback({ + error: error, + iterations: iterations, + time: time + }); + + // Delete the worker and all its associated memory + worker.terminate(); + break; + + case 'log': + console.log(e.data.message); + + case 'schedule': + if (options && options.schedule && typeof options.schedule.do === 'function') { + var scheduled = options.schedule.do; + scheduled(e.data.message); + } + break; + } + }; + + // Start the worker + worker.postMessage({ action: 'startTraining' }); + } + + // trains an XOR to the network + + }, { + key: 'XOR', + value: function XOR(options) { + if (this.network.inputs() != 2 || this.network.outputs() != 1) throw new Error('Incompatible network (2 inputs, 1 output)'); + + var defaults = { + iterations: 100000, + log: false, + shuffle: true, + cost: Trainer.cost.MSE + }; + + if (options) for (var i in options) { + defaults[i] = options[i]; + }return this.train([{ + input: [0, 0], + output: [0] + }, { + input: [1, 0], + output: [1] + }, { + input: [0, 1], + output: [1] + }, { + input: [1, 1], + output: [0] + }], defaults); + } + + // trains the network to pass a Distracted Sequence Recall test + + }, { + key: 'DSR', + value: function DSR(options) { + options = options || {}; + + var targets = options.targets || [2, 4, 7, 8]; + var distractors = options.distractors || [3, 5, 6, 9]; + var prompts = options.prompts || [0, 1]; + var length = options.length || 24; + var criterion = options.success || 0.95; + var iterations = options.iterations || 100000; + var rate = options.rate || .1; + var log = options.log || 0; + var schedule = options.schedule || {}; + var cost = options.cost || this.cost || Trainer.cost.CROSS_ENTROPY; + + var trial, correct, i, j, success; + trial = correct = i = j = success = 0; + var error = 1, + symbols = targets.length + distractors.length + prompts.length; + + var noRepeat = function noRepeat(range, avoid) { + var number = Math.random() * range | 0; + var used = false; + for (var i in avoid) { + if (number == avoid[i]) used = true; + }return used ? noRepeat(range, avoid) : number; + }; + + var equal = function equal(prediction, output) { + for (var i in prediction) { + if (Math.round(prediction[i]) != output[i]) return false; + }return true; + }; + + var start = Date.now(); + + while (trial < iterations && (success < criterion || trial % 1000 != 0)) { + // generate sequence + var sequence = [], + sequenceLength = length - prompts.length; + for (i = 0; i < sequenceLength; i++) { + var any = Math.random() * distractors.length | 0; + sequence.push(distractors[any]); + } + var indexes = [], + positions = []; + for (i = 0; i < prompts.length; i++) { + indexes.push(Math.random() * targets.length | 0); + positions.push(noRepeat(sequenceLength, positions)); + } + positions = positions.sort(); + for (i = 0; i < prompts.length; i++) { + sequence[positions[i]] = targets[indexes[i]]; + sequence.push(prompts[i]); + } + + //train sequence + var distractorsCorrect; + var targetsCorrect = distractorsCorrect = 0; + error = 0; + for (i = 0; i < length; i++) { + // generate input from sequence + var input = []; + for (j = 0; j < symbols; j++) { + input[j] = 0; + }input[sequence[i]] = 1; + + // generate target output + var output = []; + for (j = 0; j < targets.length; j++) { + output[j] = 0; + }if (i >= sequenceLength) { + var index = i - sequenceLength; + output[indexes[index]] = 1; + } + + // check result + var prediction = this.network.activate(input); + + if (equal(prediction, output)) { + if (i < sequenceLength) distractorsCorrect++;else targetsCorrect++; + } else { + this.network.propagate(rate, output); + } + + error += cost(output, prediction); + + if (distractorsCorrect + targetsCorrect == length) correct++; + } + + // calculate error + if (trial % 1000 == 0) correct = 0; + trial++; + var divideError = trial % 1000; + divideError = divideError == 0 ? 1000 : divideError; + success = correct / divideError; + error /= length; + + // log + if (log && trial % log == 0) console.log('iterations:', trial, ' success:', success, ' correct:', correct, ' time:', Date.now() - start, ' error:', error); + if (schedule.do && schedule.every && trial % schedule.every == 0) schedule.do({ + iterations: trial, + success: success, + error: error, + time: Date.now() - start, + correct: correct + }); + } + + return { + iterations: trial, + success: success, + error: error, + time: Date.now() - start + }; + } + + // train the network to learn an Embeded Reber Grammar + + }, { + key: 'ERG', + value: function ERG(options) { + + options = options || {}; + var iterations = options.iterations || 150000; + var criterion = options.error || .05; + var rate = options.rate || .1; + var log = options.log || 500; + var cost = options.cost || this.cost || Trainer.cost.CROSS_ENTROPY; + + // gramar node + var Node = function Node() { + this.paths = []; + }; + Node.prototype = { + connect: function connect(node, value) { + this.paths.push({ + node: node, + value: value + }); + return this; + }, + any: function any() { + if (this.paths.length == 0) return false; + var index = Math.random() * this.paths.length | 0; + return this.paths[index]; + }, + test: function test(value) { + for (var i in this.paths) { + if (this.paths[i].value == value) return this.paths[i]; + }return false; + } + }; + + var reberGrammar = function reberGrammar() { + + // build a reber grammar + var output = new Node(); + var n1 = new Node().connect(output, 'E'); + var n2 = new Node().connect(n1, 'S'); + var n3 = new Node().connect(n1, 'V').connect(n2, 'P'); + var n4 = new Node().connect(n2, 'X'); + n4.connect(n4, 'S'); + var n5 = new Node().connect(n3, 'V'); + n5.connect(n5, 'T'); + n2.connect(n5, 'X'); + var n6 = new Node().connect(n4, 'T').connect(n5, 'P'); + var input = new Node().connect(n6, 'B'); + + return { + input: input, + output: output + }; + }; + + // build an embeded reber grammar + var embededReberGrammar = function embededReberGrammar() { + var reber1 = reberGrammar(); + var reber2 = reberGrammar(); + + var output = new Node(); + var n1 = new Node().connect(output, 'E'); + reber1.output.connect(n1, 'T'); + reber2.output.connect(n1, 'P'); + var n2 = new Node().connect(reber1.input, 'P').connect(reber2.input, 'T'); + var input = new Node().connect(n2, 'B'); + + return { + input: input, + output: output + }; + }; + + // generate an ERG sequence + var generate = function generate() { + var node = embededReberGrammar().input; + var next = node.any(); + var str = ''; + while (next) { + str += next.value; + next = next.node.any(); + } + return str; + }; + + // test if a string matches an embeded reber grammar + var test = function test(str) { + var node = embededReberGrammar().input; + var i = 0; + var ch = str.charAt(i); + while (i < str.length) { + var next = node.test(ch); + if (!next) return false; + node = next.node; + ch = str.charAt(++i); + } + return true; + }; + + // helper to check if the output and the target vectors match + var different = function different(array1, array2) { + var max1 = 0; + var i1 = -1; + var max2 = 0; + var i2 = -1; + for (var i in array1) { + if (array1[i] > max1) { + max1 = array1[i]; + i1 = i; + } + if (array2[i] > max2) { + max2 = array2[i]; + i2 = i; + } + } + + return i1 != i2; + }; + + var iteration = 0; + var error = 1; + var table = { + 'B': 0, + 'P': 1, + 'T': 2, + 'X': 3, + 'S': 4, + 'E': 5 + }; + + var start = Date.now(); + while (iteration < iterations && error > criterion) { + var i = 0; + error = 0; + + // ERG sequence to learn + var sequence = generate(); + + // input + var read = sequence.charAt(i); + // target + var predict = sequence.charAt(i + 1); + + // train + while (i < sequence.length - 1) { + var input = []; + var target = []; + for (var j = 0; j < 6; j++) { + input[j] = 0; + target[j] = 0; + } + input[table[read]] = 1; + target[table[predict]] = 1; + + var output = this.network.activate(input); + + if (different(output, target)) this.network.propagate(rate, target); + + read = sequence.charAt(++i); + predict = sequence.charAt(i + 1); + + error += cost(target, output); + } + error /= sequence.length; + iteration++; + if (iteration % log == 0) { + console.log('iterations:', iteration, ' time:', Date.now() - start, ' error:', error); + } + } + + return { + iterations: iteration, + error: error, + time: Date.now() - start, + test: test, + generate: generate + }; + } + }, { + key: 'timingTask', + value: function timingTask(options) { + + if (this.network.inputs() != 2 || this.network.outputs() != 1) throw new Error('Invalid Network: must have 2 inputs and one output'); + + if (typeof options == 'undefined') options = {}; + + // helper + function getSamples(trainingSize, testSize) { + + // sample size + var size = trainingSize + testSize; + + // generate samples + var t = 0; + var set = []; + for (var i = 0; i < size; i++) { + set.push({ input: [0, 0], output: [0] }); + } + while (t < size - 20) { + var n = Math.round(Math.random() * 20); + set[t].input[0] = 1; + for (var j = t; j <= t + n; j++) { + set[j].input[1] = n / 20; + set[j].output[0] = 0.5; + } + t += n; + n = Math.round(Math.random() * 20); + for (var k = t + 1; k <= t + n && k < size; k++) { + set[k].input[1] = set[t].input[1]; + }t += n; + } + + // separate samples between train and test sets + var trainingSet = []; + var testSet = []; + for (var l = 0; l < size; l++) { + (l < trainingSize ? trainingSet : testSet).push(set[l]); + } // return samples + return { + train: trainingSet, + test: testSet + }; + } + + var iterations = options.iterations || 200; + var error = options.error || .005; + var rate = options.rate || [.03, .02]; + var log = options.log === false ? false : options.log || 10; + var cost = options.cost || this.cost || Trainer.cost.MSE; + var trainingSamples = options.trainSamples || 7000; + var testSamples = options.trainSamples || 1000; + + // samples for training and testing + var samples = getSamples(trainingSamples, testSamples); + + // train + var result = this.train(samples.train, { + rate: rate, + log: log, + iterations: iterations, + error: error, + cost: cost + }); + + return { + train: result, + test: this.test(samples.test) + }; + } + }]); + + return Trainer; +}(); + +Trainer.cost = cost; +exports.default = Trainer; + +/***/ }), +/* 4 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.Architect = exports.Network = exports.Trainer = exports.Layer = exports.Neuron = undefined; + +var _Neuron = __webpack_require__(2); + +Object.defineProperty(exports, 'Neuron', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_Neuron).default; + } +}); + +var _Layer = __webpack_require__(0); + +Object.defineProperty(exports, 'Layer', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_Layer).default; + } +}); + +var _Trainer = __webpack_require__(3); + +Object.defineProperty(exports, 'Trainer', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_Trainer).default; + } +}); + +var _Network = __webpack_require__(1); + +Object.defineProperty(exports, 'Network', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_Network).default; + } +}); + +var _architect = __webpack_require__(7); + +var Architect = _interopRequireWildcard(_architect); + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.Architect = Architect; + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var connections = exports.connections = 0; + +var Connection = function () { + function Connection(from, to, weight) { + _classCallCheck(this, Connection); + + if (!from || !to) throw new Error("Connection Error: Invalid neurons"); + + this.ID = Connection.uid(); + this.from = from; + this.to = to; + this.weight = typeof weight == 'undefined' ? Math.random() * .2 - .1 : weight; + this.gain = 1; + this.gater = null; + } + + _createClass(Connection, null, [{ + key: "uid", + value: function uid() { + return exports.connections = connections += 1, connections - 1; + } + }]); + + return Connection; +}(); + +exports.default = Connection; + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.connections = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _Layer = __webpack_require__(0); + +var _Layer2 = _interopRequireDefault(_Layer); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +// represents a connection from one layer to another, and keeps track of its weight and gain +var connections = exports.connections = 0; + +var LayerConnection = function () { + function LayerConnection(fromLayer, toLayer, type, weights) { + _classCallCheck(this, LayerConnection); + + this.ID = LayerConnection.uid(); + this.from = fromLayer; + this.to = toLayer; + this.selfconnection = toLayer == fromLayer; + this.type = type; + this.connections = {}; + this.list = []; + this.size = 0; + this.gatedfrom = []; + + if (typeof this.type == 'undefined') { + if (fromLayer == toLayer) this.type = _Layer2.default.connectionType.ONE_TO_ONE;else this.type = _Layer2.default.connectionType.ALL_TO_ALL; + } + + if (this.type == _Layer2.default.connectionType.ALL_TO_ALL || this.type == _Layer2.default.connectionType.ALL_TO_ELSE) { + for (var here in this.from.list) { + for (var there in this.to.list) { + var from = this.from.list[here]; + var to = this.to.list[there]; + if (this.type == _Layer2.default.connectionType.ALL_TO_ELSE && from == to) continue; + var connection = from.project(to, weights); + + this.connections[connection.ID] = connection; + this.size = this.list.push(connection); + } + } + } else if (this.type == _Layer2.default.connectionType.ONE_TO_ONE) { + + for (var neuron in this.from.list) { + var from = this.from.list[neuron]; + var to = this.to.list[neuron]; + var connection = from.project(to, weights); + + this.connections[connection.ID] = connection; + this.size = this.list.push(connection); + } + } + + fromLayer.connectedTo.push(this); + } + + _createClass(LayerConnection, null, [{ + key: 'uid', + value: function uid() { + return exports.connections = connections += 1, connections - 1; + } + }]); + + return LayerConnection; +}(); + +exports.default = LayerConnection; + +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _Perceptron = __webpack_require__(8); + +Object.defineProperty(exports, 'Perceptron', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_Perceptron).default; + } +}); + +var _LSTM = __webpack_require__(9); + +Object.defineProperty(exports, 'LSTM', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_LSTM).default; + } +}); + +var _Liquid = __webpack_require__(10); + +Object.defineProperty(exports, 'Liquid', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_Liquid).default; + } +}); + +var _Hopfield = __webpack_require__(11); + +Object.defineProperty(exports, 'Hopfield', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_Hopfield).default; + } +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _Network2 = __webpack_require__(1); + +var _Network3 = _interopRequireDefault(_Network2); + +var _Layer = __webpack_require__(0); + +var _Layer2 = _interopRequireDefault(_Layer); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var Perceptron = function (_Network) { + _inherits(Perceptron, _Network); + + function Perceptron() { + _classCallCheck(this, Perceptron); + + var _this = _possibleConstructorReturn(this, (Perceptron.__proto__ || Object.getPrototypeOf(Perceptron)).call(this)); + + var args = Array.prototype.slice.call(arguments); // convert arguments to Array + if (args.length < 3) throw new Error('not enough layers (minimum 3) !!'); + + var inputs = args.shift(); // first argument + var outputs = args.pop(); // last argument + var layers = args; // all the arguments in the middle + + var input = new _Layer2.default(inputs); + var hidden = []; + var output = new _Layer2.default(outputs); + + var previous = input; + + // generate hidden layers + for (var i = 0; i < layers.length; i++) { + var size = layers[i]; + var layer = new _Layer2.default(size); + hidden.push(layer); + previous.project(layer); + previous = layer; + } + previous.project(output); + + // set layers of the neural network + _this.set({ + input: input, + hidden: hidden, + output: output + }); + return _this; + } + + return Perceptron; +}(_Network3.default); + +exports.default = Perceptron; + +/***/ }), +/* 9 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _Network2 = __webpack_require__(1); + +var _Network3 = _interopRequireDefault(_Network2); + +var _Layer = __webpack_require__(0); + +var _Layer2 = _interopRequireDefault(_Layer); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var LSTM = function (_Network) { + _inherits(LSTM, _Network); + + function LSTM() { + _classCallCheck(this, LSTM); + + var _this = _possibleConstructorReturn(this, (LSTM.__proto__ || Object.getPrototypeOf(LSTM)).call(this)); + + var args = Array.prototype.slice.call(arguments); // convert arguments to array + if (args.length < 3) throw new Error("not enough layers (minimum 3) !!"); + + var last = args.pop(); + var option = { + peepholes: _Layer2.default.connectionType.ALL_TO_ALL, + hiddenToHidden: false, + outputToHidden: false, + outputToGates: false, + inputToOutput: true + }; + if (typeof last != 'number') { + var outputs = args.pop(); + if (last.hasOwnProperty('peepholes')) option.peepholes = last.peepholes; + if (last.hasOwnProperty('hiddenToHidden')) option.hiddenToHidden = last.hiddenToHidden; + if (last.hasOwnProperty('outputToHidden')) option.outputToHidden = last.outputToHidden; + if (last.hasOwnProperty('outputToGates')) option.outputToGates = last.outputToGates; + if (last.hasOwnProperty('inputToOutput')) option.inputToOutput = last.inputToOutput; + } else { + var outputs = last; + } + + var inputs = args.shift(); + var layers = args; + + var inputLayer = new _Layer2.default(inputs); + var hiddenLayers = []; + var outputLayer = new _Layer2.default(outputs); + + var previous = null; + + // generate layers + for (var i = 0; i < layers.length; i++) { + // generate memory blocks (memory cell and respective gates) + var size = layers[i]; + + var inputGate = new _Layer2.default(size).set({ + bias: 1 + }); + var forgetGate = new _Layer2.default(size).set({ + bias: 1 + }); + var memoryCell = new _Layer2.default(size); + var outputGate = new _Layer2.default(size).set({ + bias: 1 + }); + + hiddenLayers.push(inputGate); + hiddenLayers.push(forgetGate); + hiddenLayers.push(memoryCell); + hiddenLayers.push(outputGate); + + // connections from input layer + var input = inputLayer.project(memoryCell); + inputLayer.project(inputGate); + inputLayer.project(forgetGate); + inputLayer.project(outputGate); + + // connections from previous memory-block layer to this one + if (previous != null) { + var cell = previous.project(memoryCell); + previous.project(inputGate); + previous.project(forgetGate); + previous.project(outputGate); + } + + // connections from memory cell + var output = memoryCell.project(outputLayer); + + // self-connection + var self = memoryCell.project(memoryCell); + + // hidden to hidden recurrent connection + if (option.hiddenToHidden) memoryCell.project(memoryCell, _Layer2.default.connectionType.ALL_TO_ELSE); + + // out to hidden recurrent connection + if (option.outputToHidden) outputLayer.project(memoryCell); + + // out to gates recurrent connection + if (option.outputToGates) { + outputLayer.project(inputGate); + outputLayer.project(outputGate); + outputLayer.project(forgetGate); + } + + // peepholes + memoryCell.project(inputGate, option.peepholes); + memoryCell.project(forgetGate, option.peepholes); + memoryCell.project(outputGate, option.peepholes); + + // gates + inputGate.gate(input, _Layer2.default.gateType.INPUT); + forgetGate.gate(self, _Layer2.default.gateType.ONE_TO_ONE); + outputGate.gate(output, _Layer2.default.gateType.OUTPUT); + if (previous != null) inputGate.gate(cell, _Layer2.default.gateType.INPUT); + + previous = memoryCell; + } + + // input to output direct connection + if (option.inputToOutput) inputLayer.project(outputLayer); + + // set the layers of the neural network + _this.set({ + input: inputLayer, + hidden: hiddenLayers, + output: outputLayer + }); + return _this; + } + + return LSTM; +}(_Network3.default); + +exports.default = LSTM; + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _Network2 = __webpack_require__(1); + +var _Network3 = _interopRequireDefault(_Network2); + +var _Layer = __webpack_require__(0); + +var _Layer2 = _interopRequireDefault(_Layer); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var Liquid = function (_Network) { + _inherits(Liquid, _Network); + + function Liquid(inputs, hidden, outputs, connections, gates) { + _classCallCheck(this, Liquid); + + // create layers + var _this = _possibleConstructorReturn(this, (Liquid.__proto__ || Object.getPrototypeOf(Liquid)).call(this)); + + var inputLayer = new _Layer2.default(inputs); + var hiddenLayer = new _Layer2.default(hidden); + var outputLayer = new _Layer2.default(outputs); + + // make connections and gates randomly among the neurons + var neurons = hiddenLayer.neurons(); + var connectionList = []; + + for (var i = 0; i < connections; i++) { + // connect two random neurons + var from = Math.random() * neurons.length | 0; + var to = Math.random() * neurons.length | 0; + var connection = neurons[from].project(neurons[to]); + connectionList.push(connection); + } + + for (var j = 0; j < gates; j++) { + // pick a random gater neuron + var gater = Math.random() * neurons.length | 0; + // pick a random connection to gate + var connection = Math.random() * connectionList.length | 0; + // let the gater gate the connection + neurons[gater].gate(connectionList[connection]); + } + + // connect the layers + inputLayer.project(hiddenLayer); + hiddenLayer.project(outputLayer); + + // set the layers of the network + _this.set({ + input: inputLayer, + hidden: [hiddenLayer], + output: outputLayer + }); + return _this; + } + + return Liquid; +}(_Network3.default); + +exports.default = Liquid; + +/***/ }), +/* 11 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _Network2 = __webpack_require__(1); + +var _Network3 = _interopRequireDefault(_Network2); + +var _Trainer = __webpack_require__(3); + +var _Trainer2 = _interopRequireDefault(_Trainer); + +var _Layer = __webpack_require__(0); + +var _Layer2 = _interopRequireDefault(_Layer); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var Hopfield = function (_Network) { + _inherits(Hopfield, _Network); + + function Hopfield(size) { + _classCallCheck(this, Hopfield); + + var _this = _possibleConstructorReturn(this, (Hopfield.__proto__ || Object.getPrototypeOf(Hopfield)).call(this)); + + var inputLayer = new _Layer2.default(size); + var outputLayer = new _Layer2.default(size); + + inputLayer.project(outputLayer, _Layer2.default.connectionType.ALL_TO_ALL); + + _this.set({ + input: inputLayer, + hidden: [], + output: outputLayer + }); + + _this.trainer = new _Trainer2.default(_this); + return _this; + } + + _createClass(Hopfield, [{ + key: 'learn', + value: function learn(patterns) { + var set = []; + for (var p in patterns) { + set.push({ + input: patterns[p], + output: patterns[p] + }); + }return this.trainer.train(set, { + iterations: 500000, + error: .00005, + rate: 1 + }); + } + }, { + key: 'feed', + value: function feed(pattern) { + var output = this.activate(pattern); + + var pattern = []; + for (var i in output) { + pattern[i] = output[i] > .5 ? 1 : 0; + }return pattern; + } + }]); + + return Hopfield; +}(_Network3.default); + +exports.default = Hopfield; + +/***/ }) +/******/ ]); +}); \ No newline at end of file