Following Felix's style guideline

Changed indent to spaces. Fixed comments. Fixed quotes. Added guideline
to README. Added space to while/if/for. Added bracket in all
statements. Removed extra comment lines.
Esse commit está contido em:
Ivan Seidel
2015-12-29 13:47:46 -02:00
commit b062da9296
6 arquivos alterados com 726 adições e 792 exclusões
+282 -306
Ver Arquivo
@@ -1,268 +1,246 @@
/*
Developed by Ivan Seidel [https://github.com/ivanseidel]
*/
var robot = require('robotjs');
var scanner = require ('./scanner');
// Cache screen size
var screenSize = robot.getScreenSize();
//
// Color definitions
//
var Scanner = require ('./Scanner');
// COLOR DEFINITIONS
// This is the Dino's colour, also used by Obstacles.
var COLOR_DINOSAUR = "535353";
var COLOR_DINOSAUR = '535353';
var GameManipulator = {
// Stores the game position (Globally)
offset: null,
width: null,
// Stores the game position (Globally)
offset: null,
width: null,
// Stores points (jumps)
points: 0,
// Stores points (jumps)
points: 0,
// Listners
onGameEnd: null,
onGameStart: null,
onSensorData: null,
// Listners
onGameEnd: null,
onGameStart: null,
onSensorData: null,
// Game State
gamestate: 'OVER',
// Game State
gamestate: 'OVER',
// GameOver Position
gameOverOffset: [190, -82],
// GameOver Position
gameOverOffset: [190, -82],
//
// Stores an array of "sensors" (Ray tracings)
// Positions are always relative to global "offset"
//
sensors: [
{
lastValue: 1,
// Stores an array of "sensors" (Ray tracings)
// Positions are always relative to global "offset"
sensors: [
{
lastValue: 1,
value: null,
offset: [84, -15], // 64,-15
step: [4, 0],
length: 0.3,
value: null,
offset: [84, -15], // 64,-15
step: [4, 0],
length: 0.3,
// Speed
speed: 0,
lastComputeSpeed: 0,
// Speed
speed: 0,
lastComputeSpeed: 0,
// Computes size of the object
size: 0,
computeSize: true,
},
// {
// lastValue: 1,
// value: null,
// offset: [66, -30],
// step: [5, 0],
// length: 0.5,
// }
]
// Computes size of the object
size: 0,
computeSize: true,
},
]
};
//
// Find out dinosaur (fast)
//
GameManipulator.findGamePosition = function () {
var pos, dinoPos, skipXFast = 15;
var pos, dinoPos, skipXFast = 15;
for(var x = 20; x < screenSize.width; x+= skipXFast){
for (var x = 20; x < screenSize.width; x+= skipXFast) {
dinoPos = Scanner.scanUntil(
// Start position
[x, 80],
// Skip pixels
[0, skipXFast],
// Searching Color
COLOR_DINOSAUR,
// Normal mode (not inverse)
false,
// Iteration limit
500 / skipXFast);
dinoPos = scanner.scanUntil(
// Start position
[x, 80],
// Skip pixels
[0, skipXFast],
// Searching Color
COLOR_DINOSAUR,
// Normal mode (not inverse)
false,
// Iteration limit
500 / skipXFast);
if (dinoPos) {
break;
}
}
if(dinoPos)
break;
}
if (!dinoPos) {
return null;
}
if(!dinoPos)
return null;
for (var x = dinoPos[0] - 50; x <= dinoPos[0]; x += 1) {
pos = Scanner.scanUntil(
// Start position
[x, dinoPos[1] - 2],
// Skip pixels
[0, 1],
// Searching Color
COLOR_DINOSAUR,
// Normal mode (not inverse)
false,
// Iteration limit
100);
for(var x = dinoPos[0] - 50; x <= dinoPos[0]; x += 1){
if (pos) {
break;
}
}
pos = scanner.scanUntil(
// Start position
[x, dinoPos[1] - 2],
// Skip pixels
[0, 1],
// Searching Color
COLOR_DINOSAUR,
// Normal mode (not inverse)
false,
// Iteration limit
100);
// Did actually found? If not, error!
if (!pos) {
return null;
}
if(pos)
break;
}
// Find the end of the game
var endPos = pos;
// Did actually found? If not, error!
if(!pos)
return null;
while (robot.getPixelColor(endPos[0] + 3, endPos[1]) == COLOR_DINOSAUR) {
endPos = Scanner.scanUntil(
// Start position
[endPos[0] + 2, endPos[1]],
// Skip pixels
[2, 0],
// Searching Color
COLOR_DINOSAUR,
// Invert mode
true,
// Iteration limit
600);
}
// Did actually found? If not, error!
if (!endPos) {
return null;
}
// Find the end of the game
var endPos = pos;
// Save to allow global access
GameManipulator.offset = pos;
GameManipulator.width = 600;//endPos[0] - pos[0];
while(robot.getPixelColor(endPos[0] + 3, endPos[1]) == COLOR_DINOSAUR){
endPos = scanner.scanUntil(
// Start position
[endPos[0] + 2, endPos[1]],
// Skip pixels
[2, 0],
// Searching Color
COLOR_DINOSAUR,
// Invert mode
true,
// Iteration limit
600);
}
// Did actually found? If not, error!
if(!endPos)
return null;
// Save to allow global access
GameManipulator.offset = pos;
GameManipulator.width = 600;//endPos[0] - pos[0];
return pos;
return pos;
};
//
// Read Game state
// (If game is ended or is playing)
//
GameManipulator.readGameState = function () {
// Read GameOver
var found = scanner.scanUntil(
[
GameManipulator.offset[0] + GameManipulator.gameOverOffset[0],
GameManipulator.offset[1] + GameManipulator.gameOverOffset[1]
],
// Read GameOver
var found = Scanner.scanUntil(
[
GameManipulator.offset[0] + GameManipulator.gameOverOffset[0],
GameManipulator.offset[1] + GameManipulator.gameOverOffset[1]
],
[2, 0], COLOR_DINOSAUR, false, 20);
[2, 0], COLOR_DINOSAUR, false, 20);
if(found && GameManipulator.gamestate != 'OVER'){
GameManipulator.gamestate = 'OVER';
if (found && GameManipulator.gamestate != 'OVER') {
GameManipulator.gamestate = 'OVER';
// Clear keys
GameManipulator.setGameOutput(0.5);
// Clear keys
GameManipulator.setGameOutput(0.5);
// Trigger callback and clear
GameManipulator.onGameEnd && GameManipulator.onGameEnd(GameManipulator.points);
GameManipulator.onGameEnd = null;
// Trigger callback and clear
GameManipulator.onGameEnd && GameManipulator.onGameEnd(GameManipulator.points);
GameManipulator.onGameEnd = null;
// console.log('GAME OVER: '+GameManipulator.points);
// console.log('GAME OVER: '+GameManipulator.points);
}else if(!found && GameManipulator.gamestate != 'PLAYING'){
GameManipulator.gamestate = 'PLAYING';
} else if (!found && GameManipulator.gamestate != 'PLAYING') {
GameManipulator.gamestate = 'PLAYING';
// Clear points
GameManipulator.points = 0;
GameManipulator.lastScore = 0;
// Clear points
GameManipulator.points = 0;
GameManipulator.lastScore = 0;
// Clear keys
GameManipulator.setGameOutput(0.5);
// Clear keys
GameManipulator.setGameOutput(0.5);
// Clear sensors
GameManipulator.sensors[0].lastComputeSpeed = 0;
GameManipulator.sensors[0].lastSpeeds = [];
GameManipulator.sensors[0].lastValue = 1;
GameManipulator.sensors[0].value = 1;
GameManipulator.sensors[0].speed = 0;
GameManipulator.sensors[0].size = 0;
// Clear sensors
GameManipulator.sensors[0].lastComputeSpeed = 0;
GameManipulator.sensors[0].lastSpeeds = [];
GameManipulator.sensors[0].lastValue = 1;
GameManipulator.sensors[0].value = 1;
GameManipulator.sensors[0].speed = 0;
GameManipulator.sensors[0].size = 0;
// Clar Output flags
GameManipulator.lastOutputSet = 'NONE';
// Clar Output flags
GameManipulator.lastOutputSet = 'NONE';
// Trigger callback and clear
GameManipulator.onGameStart && GameManipulator.onGameStart();
GameManipulator.onGameStart = null;
// Trigger callback and clear
GameManipulator.onGameStart && GameManipulator.onGameStart();
GameManipulator.onGameStart = null;
// console.log('GAME RUNNING '+GameManipulator.points);
}
// console.log('GAME RUNNING '+GameManipulator.points);
}
}
//
// Call this to start a fresh new game
// Will wait untill game has ended,
// and call the `next` callback
//
var _startKeyInterval;
GameManipulator.startNewGame = function (next) {
// Refresh state
GameManipulator.readGameState();
// Refresh state
GameManipulator.readGameState();
// If game is already over, press space
if(GameManipulator.gamestate == 'OVER'){
clearInterval(_startKeyInterval);
// If game is already over, press space
if (GameManipulator.gamestate == 'OVER') {
clearInterval(_startKeyInterval);
// Set start callback
GameManipulator.onGameStart = function (argument) {
clearInterval(_startKeyInterval);
next && next();
};
// Set start callback
GameManipulator.onGameStart = function (argument) {
clearInterval(_startKeyInterval);
next && next();
};
// Press space to begin game (repetidelly)
_startKeyInterval = setInterval(function (){
robot.keyTap(' ');
}, 300);
// Press space to begin game (repetidelly)
_startKeyInterval = setInterval(function (){
robot.keyTap(' ');
}, 300);
// Refresh state
GameManipulator.readGameState();
// Refresh state
GameManipulator.readGameState();
}else{
// Wait die, and call recursive action
GameManipulator.onGameEnd = function () {
GameManipulator.startNewGame(next);
}
}
} else {
// Wait die, and call recursive action
GameManipulator.onGameEnd = function () {
GameManipulator.startNewGame(next);
}
}
}
//
// Compute points based on sensors
//
// Basicaly, checks if an object has
// passed trough the sensor and the
// value is now higher than before
//
GameManipulator.computePoints = function () {
for(var k in GameManipulator.sensors){
var sensor = GameManipulator.sensors[k];
for (var k in GameManipulator.sensors) {
var sensor = GameManipulator.sensors[k];
if(sensor.value > 0.5 && sensor.lastValue < 0.3){
GameManipulator.points++;
// console.log('POINTS: '+GameManipulator.points);
}
}
if (sensor.value > 0.5 && sensor.lastValue < 0.3) {
GameManipulator.points++;
// console.log('POINTS: '+GameManipulator.points);
}
}
}
//
// Read sensors
//
// Sensors are like ray-traces:
@@ -274,183 +252,181 @@ GameManipulator.computePoints = function () {
// SIZE and it's speed
//
// Note: We currently only have a sensor.
//
GameManipulator.readSensors = function () {
var offset = GameManipulator.offset;
var offset = GameManipulator.offset;
var startTime = Date.now();
var startTime = Date.now();
for(var k in GameManipulator.sensors){
for (var k in GameManipulator.sensors) {
var sensor = GameManipulator.sensors[k];
var sensor = GameManipulator.sensors[k];
// Calculate absolute position of ray tracing
var start = [
offset[0] + sensor.offset[0],
offset[1] + sensor.offset[1],
];
// Calculate absolute position of ray tracing
var start = [
offset[0] + sensor.offset[0],
offset[1] + sensor.offset[1],
];
// Compute cursor forwarding
var forward = sensor.value * GameManipulator.width * 0.8 * sensor.length;
// Compute cursor forwarding
var forward = sensor.value * GameManipulator.width * 0.8 * sensor.length;
var end = scanner.scanUntil(
// console.log(
// Start position
[start[0], start[1]],
// Skip pixels
sensor.step,
// Searching Color
COLOR_DINOSAUR,
// Invert mode?
false,
// Iteration limit
(GameManipulator.width * sensor.length) / sensor.step[0]);
var end = Scanner.scanUntil(
// console.log(
// Start position
[start[0], start[1]],
// Skip pixels
sensor.step,
// Searching Color
COLOR_DINOSAUR,
// Invert mode?
false,
// Iteration limit
(GameManipulator.width * sensor.length) / sensor.step[0]);
// Save lastValue
sensor.lastValue = sensor.value;
// Save lastValue
sensor.lastValue = sensor.value;
// Calculate the Sensor value
if(end){
sensor.value = (end[0] - start[0]) / (GameManipulator.width * sensor.length);
// Calculate the Sensor value
if (end) {
sensor.value = (end[0] - start[0]) / (GameManipulator.width * sensor.length);
//
// Calculate size of obstacle
//
var endPoint = scanner.scanUntil(
[end[0] + 75, end[1]],
[-2, 0],
COLOR_DINOSAUR,
false,
75 / 2
);
// Calculate size of obstacle
var endPoint = Scanner.scanUntil(
[end[0] + 75, end[1]],
[-2, 0],
COLOR_DINOSAUR,
false,
75 / 2
);
// If no end point, set the start point as end
if(!endPoint)
endPoint = end;
// If no end point, set the start point as end
if (!endPoint) {
endPoint = end;
}
var sizeTmp = (endPoint[0] - end[0]) / 100.0;
if(GameManipulator.points == sensor.lastScore){
// It's the same obstacle. Set size to "max" of both
sensor.size = Math.max(sensor.size, sizeTmp);
}else{
sensor.size = sizeTmp;
}
var sizeTmp = (endPoint[0] - end[0]) / 100.0;
if (GameManipulator.points == sensor.lastScore) {
// It's the same obstacle. Set size to "max" of both
sensor.size = Math.max(sensor.size, sizeTmp);
} else {
sensor.size = sizeTmp;
}
// We use the current score to check for object equality
sensor.lastScore = GameManipulator.points;
// We use the current score to check for object equality
sensor.lastScore = GameManipulator.points;
// sensor.size = Math.max(sensor.size, endPoint[0] - end[0]);
// sensor.size = Math.max(sensor.size, endPoint[0] - end[0]);
}else{
sensor.value = 1;
sensor.size = 0;
}
} else {
sensor.value = 1;
sensor.size = 0;
}
// Compute speed
var dt = (Date.now() - sensor.lastComputeSpeed) / 1000;
sensor.lastComputeSpeed = Date.now();
// Compute speed
var dt = (Date.now() - sensor.lastComputeSpeed) / 1000;
sensor.lastComputeSpeed = Date.now();
if(sensor.value < sensor.lastValue){
// Compute speed
var newSpeed = (sensor.lastValue - sensor.value) / dt;
if (sensor.value < sensor.lastValue) {
// Compute speed
var newSpeed = (sensor.lastValue - sensor.value) / dt;
sensor.lastSpeeds.unshift(newSpeed);
sensor.lastSpeeds.unshift(newSpeed);
while(sensor.lastSpeeds.length > 10)
sensor.lastSpeeds.pop();
while (sensor.lastSpeeds.length > 10) {
sensor.lastSpeeds.pop();
}
// Take Average
var avgSpeed = 0;
for(var k in sensor.lastSpeeds)
avgSpeed += sensor.lastSpeeds[k] / sensor.lastSpeeds.length;
// Take Average
var avgSpeed = 0;
for (var k in sensor.lastSpeeds) {
avgSpeed += sensor.lastSpeeds[k] / sensor.lastSpeeds.length;
}
sensor.speed = Math.max(avgSpeed - 1.5, sensor.speed);
sensor.speed = Math.max(avgSpeed - 1.5, sensor.speed);
}
}
sensor.size = Math.min(sensor.size, 1.0);
// Save length/size of sensor value
sensor.size = Math.min(sensor.size, 1.0);
// if(GameManipulator.gamestate == 'PLAYING')
// console.log("S["+k+"]: ", sensor.value, sensor.size, Date.now() - startTime);
startTime = Date.now();
}
startTime = Date.now();
}
// Compute points
GameManipulator.computePoints();
// Compute points
GameManipulator.computePoints();
// Call sensor callback (to act)
GameManipulator.onSensorData && GameManipulator.onSensorData();
// Call sensor callback (to act)
GameManipulator.onSensorData && GameManipulator.onSensorData();
}
//
// Set action to game
// Values:
// 0.0 to 0.4: DOWN
// 0.4 to 0.6: NOTHING
// 0.6 to 1.0: UP (JUMP)
//
// 0.00 to 0.45: DOWN
// 0.45 to 0.55: NOTHING
// 0.55 to 1.00: UP (JUMP)
var PRESS = 'down';
var RELEASE = 'up';
GameManipulator.lastOutputSet = 'NONE';
GameManipulator.lastOutputSetTime = 0;
GameManipulator.setGameOutput = function (output){
GameManipulator.gameOutput = output;
GameManipulator.gameOutputString = GameManipulator.getDiscreteState(output);
GameManipulator.gameOutput = output;
GameManipulator.gameOutputString = GameManipulator.getDiscreteState(output);
if(GameManipulator.gameOutputString == 'DOWN'){
// Skew
robot.keyToggle('up', RELEASE);
robot.keyToggle('down', PRESS);
}else if(GameManipulator.gameOutputString == 'NORM'){
// DO Nothing
robot.keyToggle('up', RELEASE);
robot.keyToggle('down', RELEASE);
}else{
if (GameManipulator.gameOutputString == 'DOWN') {
// Skew
robot.keyToggle('up', RELEASE);
robot.keyToggle('down', PRESS);
} else if (GameManipulator.gameOutputString == 'NORM') {
// DO Nothing
robot.keyToggle('up', RELEASE);
robot.keyToggle('down', RELEASE);
} else {
// Filter JUMP
if(GameManipulator.lastOutputSet != 'JUMP')
GameManipulator.lastOutputSetTime = Date.now();
// Filter JUMP
if (GameManipulator.lastOutputSet != 'JUMP') {
GameManipulator.lastOutputSetTime = Date.now();
}
// JUMP
// Check if hasn't jump for more than 3 continuous secconds
if(Date.now() - GameManipulator.lastOutputSetTime < 3000){
robot.keyToggle('up', PRESS);
robot.keyToggle('down', RELEASE);
}else{
robot.keyToggle('up', RELEASE);
robot.keyToggle('down', RELEASE);
}
// JUMP
// Check if hasn't jump for more than 3 continuous secconds
if (Date.now() - GameManipulator.lastOutputSetTime < 3000) {
robot.keyToggle('up', PRESS);
robot.keyToggle('down', RELEASE);
} else {
robot.keyToggle('up', RELEASE);
robot.keyToggle('down', RELEASE);
}
}
}
GameManipulator.lastOutputSet = GameManipulator.gameOutputString;
GameManipulator.lastOutputSet = GameManipulator.gameOutputString;
}
//
// Simply maps an real number to string actions
//
GameManipulator.getDiscreteState = function (value){
if(value < 0.45){
return 'DOWN'
}else if(value > 0.55){
return 'JUMP';
}
if (value < 0.45) {
return 'DOWN'
} else if(value > 0.55) {
return 'JUMP';
}
return 'NORM';
return 'NORM';
}
//
// Click on the Starting point
// to make sure game is focused
//
GameManipulator.focusGame = function (){
robot.moveMouse(GameManipulator.offset[0], GameManipulator.offset[1]);
robot.mouseClick("left");
robot.moveMouse(GameManipulator.offset[0], GameManipulator.offset[1]);
robot.mouseClick('left');
}
module.exports = GameManipulator;
+171 -197
Ver Arquivo
@@ -1,72 +1,57 @@
/*
Developed by Ivan Seidel [https://github.com/ivanseidel]
*/
var _ = require('lodash');
var async = require('async');
var synaptic = require('synaptic');
var async = require('async');
var _ = require('lodash');
var Network = synaptic.Network;
var Architect = synaptic.Architect;
var Network = synaptic.Network;
var Learn = {
//
// Current Genomes
// In form of: {
// fitness: null,
// genome: [...],
// network: {NeuralNetworkObject}
// }
//
genomes: [],
state: 'STOP',
// Array of networks for current Genomes
// (Genomes will be added the key `fitness`)
genomes: [],
//
// Current genome tryout
//
genome: 0,
generation: 0,
// Current state of learning [STOP, LEARNING]
state: 'STOP',
//
// Set this, to verify genome action BEFORE running it
//
shouldCheckExperience: false,
// Current genome/generation tryout
genome: 0,
generation: 0,
// Set this, to verify genome experience BEFORE running it
shouldCheckExperience: false,
};
//
// Initialize the Learner
//
Learn.init = function (gameManipulator, ui, genomeUnits, selection, mutationProb) {
Learn.gm = gameManipulator;
Learn.ui = ui;
Learn.init = function (gameManip, ui, genomeUnits, selection, mutationProb) {
Learn.gm = gameManip;
Learn.ui = ui;
Learn.genome = 0;
Learn.generation = 0;
Learn.genome = 0;
Learn.generation = 0;
Learn.genomeUnits = genomeUnits;
Learn.selection = selection;
Learn.mutationProb = mutationProb;
Learn.genomeUnits = genomeUnits;
Learn.selection = selection;
Learn.mutationProb = mutationProb;
}
//
// Build genomes before calling executeGeneration.
//
Learn.startLearning = function () {
// Build genomes if needed
while(Learn.genomes.length < Learn.genomeUnits){
Learn.genomes.push(Learn.buildGenome(3, 1));
}
// Build genomes if needed
while (Learn.genomes.length < Learn.genomeUnits) {
Learn.genomes.push(Learn.buildGenome(3, 1));
}
Learn.executeGeneration();
Learn.executeGeneration();
}
//
// Given the entire generation of genomes (An array),
// applyes method `executeGenome` for each element.
// After all elements have completed executing:
@@ -75,223 +60,212 @@ Learn.startLearning = function () {
// 2) Does cross over (except for 2 genomes)
// 3) Does Mutation-only on remaining genomes
// 4) Execute generation (recursivelly)
//
Learn.executeGeneration = function (){
if(Learn.state == 'STOP')
return;
if (Learn.state == 'STOP') {
return;
}
Learn.generation++;
Learn.ui.logger.log('Executing generation '+Learn.generation);
Learn.generation++;
Learn.ui.logger.log('Executing generation '+Learn.generation);
Learn.genome = 0;
Learn.genome = 0;
async.mapSeries(Learn.genomes, Learn.executeGenome, function (argument) {
async.mapSeries(Learn.genomes, Learn.executeGenome, function (argument) {
// Kill worst genomes
Learn.genomes = Learn.selectBestGenomes(Learn.selection);
// Kill worst genomes
Learn.genomes = Learn.selectBestGenomes(Learn.selection);
// Copy best genomes
var bestGenomes = _.clone(Learn.genomes);
// Copy best genomes
var bestGenomes = _.clone(Learn.genomes);
// Cross Over ()
while(Learn.genomes.length < Learn.genomeUnits - 2){
// Get two random Genomes
var genA = _.sample(bestGenomes).toJSON();
var genB = _.sample(bestGenomes).toJSON();
// Cross Over ()
while (Learn.genomes.length < Learn.genomeUnits - 2) {
// Get two random Genomes
var genA = _.sample(bestGenomes).toJSON();
var genB = _.sample(bestGenomes).toJSON();
// Cross over and Mutate
var newGenome = Learn.mutate(Learn.crossOver(genA, genB));
// Cross over and Mutate
var newGenome = Learn.mutate(Learn.crossOver(genA, genB));
// Add to generation
Learn.genomes.push(Network.fromJSON(newGenome));
}
// Add to generation
Learn.genomes.push(Network.fromJSON(newGenome));
}
// Mutation-only
while(Learn.genomes.length < Learn.genomeUnits){
// Get two random Genomes
var gen = _.sample(bestGenomes).toJSON();
// Mutation-only
while (Learn.genomes.length < Learn.genomeUnits) {
// Get two random Genomes
var gen = _.sample(bestGenomes).toJSON();
// Cross over and Mutate
var newGenome = Learn.mutate(gen);
// Cross over and Mutate
var newGenome = Learn.mutate(gen);
// Add to generation
Learn.genomes.push(Network.fromJSON(newGenome));
}
// Add to generation
Learn.genomes.push(Network.fromJSON(newGenome));
}
Learn.ui.logger.log('Completed generation '+Learn.generation);
Learn.ui.logger.log('Completed generation '+Learn.generation);
// Execute next generation
Learn.executeGeneration();
})
// Execute next generation
Learn.executeGeneration();
})
}
//
// Sort all the genomes, and delete the worst one
// untill the genome list has selectN elements.
//
Learn.selectBestGenomes = function (selectN){
var selected = _.sortBy(Learn.genomes, 'fitness').reverse();
var selected = _.sortBy(Learn.genomes, 'fitness').reverse();
while(selected.length > selectN){
selected.pop();
}
while (selected.length > selectN) {
selected.pop();
}
Learn.ui.logger.log('Fitness: '+_.pluck(selected, 'fitness').join(','));
Learn.ui.logger.log('Fitness: '+_.pluck(selected, 'fitness').join(','));
return selected;
return selected;
}
//
// Waits the game to end, and start a new one, then:
// 1) Set's listener for sensorData
// 2) On data read, applyes the neural network, and
// set it's output
// 3) When the game has ended and compute the fitness
//
Learn.executeGenome = function (genome, next){
if(Learn.state == 'STOP')
return;
if (Learn.state == 'STOP') {
return;
}
Learn.genome = Learn.genomes.indexOf(genome) + 1;
// Learn.ui.logger.log('Executing genome '+Learn.genome);
Learn.genome = Learn.genomes.indexOf(genome) + 1;
// Learn.ui.logger.log('Executing genome '+Learn.genome);
// Check if genome has AT LEAST some experience
if(Learn.shouldCheckExperience){
if(!Learn.checkExperience(genome)){
genome.fitness = 0;
// Learn.ui.logger.log('Genome '+Learn.genome+' has no minimum experience');
return next();
}
}
// Check if genome has AT LEAST some experience
if (Learn.shouldCheckExperience) {
if (!Learn.checkExperience(genome)) {
genome.fitness = 0;
// Learn.ui.logger.log('Genome '+Learn.genome+' has no min. experience');
return next();
}
}
Learn.gm.startNewGame(function (){
Learn.gm.startNewGame(function (){
// Reads sensor data, and apply network
Learn.gm.onSensorData = function (){
var inputs = [
Learn.gm.sensors[0].value,
Learn.gm.sensors[0].size,
Learn.gm.sensors[0].speed,
];
// console.log(inputs);
// Apply to network
var outputs = genome.activate(inputs);
// Reads sensor data, and apply network
Learn.gm.onSensorData = function (){
var inputs = [
Learn.gm.sensors[0].value,
Learn.gm.sensors[0].size,
Learn.gm.sensors[0].speed,
];
// console.log(inputs);
// Apply to network
var outputs = genome.activate(inputs);
Learn.gm.setGameOutput(outputs[0]);
}
Learn.gm.setGameOutput(outputs[0]);
}
// Wait game end, and compute fitness
Learn.gm.onGameEnd = function (points){
Learn.ui.logger.log('Genome '+Learn.genome+' ended. Fitness: '+points);
// Wait game end, and compute fitness
Learn.gm.onGameEnd = function (points){
Learn.ui.logger.log('Genome '+Learn.genome+' ended. Fitness: '+points);
// Save Genome fitness
genome.fitness = points;
// Save Genome fitness
genome.fitness = points;
// Go to next genome
next();
}
});
// Go to next genome
next();
}
});
}
//
// Validate if any acction occur uppon a given input (in this case, distance).
// If genome only keeps a single activation value for any given input,
// it will return false
//
Learn.checkExperience = function (genome) {
var step = 0.1, start = 0.0, stop = 1;
var step = 0.1, start = 0.0, stop = 1;
// Inputs are default. We only want to test the first index
var inputs = [0.0, 0.3, 0.2];
var activation, state, outputs = {};
// Inputs are default. We only want to test the first index
var inputs = [0.0, 0.3, 0.2];
var activation, state, outputs = {};
for(var k = start; k < stop; k += step){
inputs[0] = k;
for (var k = start; k < stop; k += step) {
inputs[0] = k;
activation = genome.activate(inputs);
state = Learn.gm.getDiscreteState(activation);
outputs[state] = true;
}
activation = genome.activate(inputs);
state = Learn.gm.getDiscreteState(activation);
outputs[state] = true;
}
// Count states, and return true if greater than 1
return _.keys(outputs).length > 1;
// Count states, and return true if greater than 1
return _.keys(outputs).length > 1;
}
//
// Load genomes saved from JSON file
//
Learn.loadGenomes = function (genomes, deleteOthers){
if(deleteOthers)
Learn.genomes = [];
if (deleteOthers) {
Learn.genomes = [];
}
var loaded = 0;
for(var k in genomes){
Learn.genomes.push(Network.fromJSON(genomes[k]));
loaded++;
}
var loaded = 0;
for (var k in genomes) {
Learn.genomes.push(Network.fromJSON(genomes[k]));
loaded++;
}
Learn.ui.logger.log('Loaded '+loaded+' genomes!');
Learn.ui.logger.log('Loaded '+loaded+' genomes!');
}
//
// Builds a new genome based on the
// expected number of inputs and outputs
//
Learn.buildGenome = function (inputs, outputs) {
Learn.ui.logger.log('Build genome '+(Learn.genomes.length+1));
var network = new Architect.Perceptron(inputs, 4, 4, outputs);
return network;
// console.log(JSON.stringify(network.toJSON()));
// process.exit();
Learn.ui.logger.log('Build genome '+(Learn.genomes.length+1));
var network = new Architect.Perceptron(inputs, 4, 4, outputs);
return network;
}
//
// SPECIFIC to Neural Network.
// Those two methods convert from JSON to Array, and from Array to JSON
//
Learn.crossOver = function (netA, netB) {
// Swap (50% prob.)
if(Math.random() > 0.5){
var tmp = netA;
netA = netB;
netB = tmp;
}
// Swap (50% prob.)
if (Math.random() > 0.5) {
var tmp = netA;
netA = netB;
netB = tmp;
}
// Clone network
netA = _.cloneDeep(netA);
netB = _.cloneDeep(netB);
// Clone network
netA = _.cloneDeep(netA);
netB = _.cloneDeep(netB);
// Cross over data keys
Learn.crossOverDataKey(netA.neurons, netB.neurons, 'bias');
// Learn.crossOverDataKey(netA.connections, netB.connections, 'weight');
// Cross over data keys
Learn.crossOverDataKey(netA.neurons, netB.neurons, 'bias');
return netA;
return netA;
}
//
// Does random mutations across all
// the biases and weights of the Networks
// (This must be done in the JSON to
// prevent modifying the current one)
//
Learn.mutate = function (net){
// Mutate
Learn.mutateDataKeys(net.neurons, 'bias', Learn.mutationProb);
Learn.mutateDataKeys(net.connections, 'weight', Learn.mutationProb);
// Mutate
Learn.mutateDataKeys(net.neurons, 'bias', Learn.mutationProb);
Learn.mutateDataKeys(net.connections, 'weight', Learn.mutationProb);
return net;
return net;
}
//
// Given an Object A and an object B, both Arrays
// of Objects:
//
@@ -299,33 +273,33 @@ Learn.mutate = function (net){
// randomly (going from 0 to A.length)
// 2) Swap values from `key` one to another,
// starting by cutLocation
//
Learn.crossOverDataKey = function (a, b, key) {
var cutLocation = Math.round(a.length * Math.random());
var cutLocation = Math.round(a.length * Math.random());
var tmp;
for(var k = cutLocation; k < a.length; k++){
// Swap
tmp = a[k][key];
a[k][key] = b[k][key];
b[k][key] = tmp;
}
var tmp;
for (var k = cutLocation; k < a.length; k++) {
// Swap
tmp = a[k][key];
a[k][key] = b[k][key];
b[k][key] = tmp;
}
}
//
// Given an Array of objects with key `key`,
// and also a `mutationRate`, randomly Mutate
// the value of each key, if random value is
// lower than mutationRate for each element.
//
Learn.mutateDataKeys = function (a, key, mutationRate){
for(var k = 0; k < a.length; k++){
// Should mutate?
if(Math.random() > mutationRate)
continue;
for (var k = 0; k < a.length; k++) {
// Should mutate?
if (Math.random() > mutationRate) {
continue;
}
a[k][key] += a[k][key] * (Math.random() - 0.5) * 3 + (Math.random() - 0.5);
}
a[k][key] += a[k][key] * (Math.random() - 0.5) * 3 + (Math.random() - 0.5);
}
}
module.exports = Learn;
+6 -1
Ver Arquivo
@@ -66,7 +66,7 @@ There are a few files in the project:
- `index.js`: It tight all things together.
- `scanner.js`: Basic abstraction layer above RobotJs library that reads the screen like
- `Scanner.js`: Basic abstraction layer above RobotJs library that reads the screen like
ray tracing. Also have some utilities functions.
- `UI.js`: Global scope for the UI management. It initializes and also updates the screen
@@ -103,6 +103,11 @@ console in the element inspector:
setInterval(function (){Runner.instance_.tRex.xPos = 21}, 2000)
```
## Development guidelines
Please, follow the Node.js style guide from [Felix](https://github.com/felixge/node-style-guide).
It is not complex, and has a great simple pattern for things.
## Credits
- [Ivan Seidel](https://github.com/ivanseidel)
+173 -185
Ver Arquivo
@@ -1,219 +1,207 @@
/*
Developed by Ivan Seidel [https://github.com/ivanseidel]
*/
var fs = require('fs');
var blessed = require('blessed')
var contrib = require('blessed-contrib')
var blessed = require('blessed')
var fs = require('fs');
var screen = blessed.screen()
var UI = {};
//
// Initialize UI objects
//
UI.init = function (gameManipulator, learner) {
UI.gm = gameManipulator;
UI.learner = learner;
UI.gm = gameManipulator;
UI.learner = learner;
UI.grid = new contrib.grid({
rows: 12,
cols: 6,
screen: screen
});
//
// Build Sensor inputs
//
UI.uiSensors = UI.grid.set(0, 0, 3, 6, contrib.bar, {
label: 'Network Inputs',
// bg: 'white',
barWidth: 12,
barSpacing: 1,
xOffset: 0,
maxHeight: 100,
});
//
// Build Log box
//
UI.logger = UI.grid.set(3, 0, 3, 6, contrib.log, {
fg: 'green',
selectedFg: 'green',
label: 'Logs'
});
//
// Current score/time view
//
UI.uiScore = UI.grid.set(6, 0, 3, 3, blessed.Text, {
label: 'Game Stats',
// bg: 'green',
fg: 'white',
content: 'Loading...',
align: 'center',
});
//
// Current Genomes stats
//
UI.uiGenomes = UI.grid.set(6, 3, 3, 3, blessed.Text, {
label: 'Genome Stats',
// bg: 'green',
fg: 'white',
content: 'Hey!',
align: 'center',
});
UI.grid = new contrib.grid({
rows: 12,
cols: 6,
screen: screen
});
//
// Load Tree
//
UI.savesTree = UI.grid.set(9, 0, 3, 3, contrib.tree, {
label: 'Saved Genomes',
});
UI.savesTree.on('click', UI.savesTree.focus.bind(UI.savesTree));
UI.savesTree.on('select', function (item){
if(item.isFile){
var fileName = item.name;
UI.logger.log('Loading genomes from file:');
UI.logger.log(fileName);
var genomes = require('./genomes/'+fileName);
UI.learner.loadGenomes(genomes);
}else{
UI.refreshFiles();
}
});
UI.refreshFiles();
//
// Save Btn
//
UI.btnSave = UI.grid.set(9, 3, 3, 3, blessed.box, {
label: 'Save to File',
bg: 'green',
fg: 'red',
content: '\n\n\n\nSave Genomes',
align: 'center',
});
UI.btnSave.on('click', function (){
var jsonGenomes = [];
for(var k in UI.learner.genomes){
jsonGenomes.push(UI.learner.genomes[k].toJSON());
}
UI.logger.log('Saving '+jsonGenomes.length+' genomes...');
var fileName = './genomes/gen_'+UI.learner.generation+'_'+Date.now()+'.json';
fs.writeFile(fileName, JSON.stringify(jsonGenomes), function (err){
if(err)
UI.logger.log('Failed to save! '+err);
else
UI.logger.log('Saved to '+fileName);
UI.refreshFiles();
});
});
screen.key(['escape', 'q', 'C-c'], function(ch, key) {
return process.exit(0);
});
screen.key(['s'], function (ch, key){
if(learner.state == 'STOP'){
learner.state = 'LEARNING';
gameManipulator.focusGame();
learner.startLearning();
}else{
learner.state = 'STOP';
}
});
// Build Sensor inputs
UI.uiSensors = UI.grid.set(0, 0, 3, 6, contrib.bar, {
label: 'Network Inputs',
// bg: 'white',
barWidth: 12,
barSpacing: 1,
xOffset: 0,
maxHeight: 100,
});
screen.render()
// Build Log box
UI.logger = UI.grid.set(3, 0, 3, 6, contrib.log, {
fg: 'green',
selectedFg: 'green',
label: 'Logs'
});
// Current score/time view
UI.uiScore = UI.grid.set(6, 0, 3, 3, blessed.Text, {
label: 'Game Stats',
// bg: 'green',
fg: 'white',
content: 'Loading...',
align: 'center',
});
// Current Genomes stats
UI.uiGenomes = UI.grid.set(6, 3, 3, 3, blessed.Text, {
label: 'Genome Stats',
// bg: 'green',
fg: 'white',
content: 'Hey!',
align: 'center',
});
// Load Tree
UI.savesTree = UI.grid.set(9, 0, 3, 3, contrib.tree, {
label: 'Saved Genomes',
});
// Callback for Loading genomes and focusing tree
UI.savesTree.on('click', UI.savesTree.focus.bind(UI.savesTree));
UI.savesTree.on('select', function (item){
if (item.isFile) {
var fileName = item.name;
UI.logger.log('Loading genomes from file:');
UI.logger.log(fileName);
var genomes = require('./genomes/'+fileName);
UI.learner.loadGenomes(genomes);
} else {
UI.refreshFiles();
}
});
UI.refreshFiles();
// Save Btn
UI.btnSave = UI.grid.set(9, 3, 3, 3, blessed.box, {
label: 'Save to File',
bg: 'green',
fg: 'red',
content: '\n\n\n\nSave Genomes',
align: 'center',
});
UI.btnSave.on('click', function (){
var jsonGenomes = [];
for (var k in UI.learner.genomes) {
jsonGenomes.push(UI.learner.genomes[k].toJSON());
}
UI.logger.log('Saving '+jsonGenomes.length+' genomes...');
var dir = './genomes';
var fileName = dir + '/gen_'+UI.learner.generation+'_'+Date.now()+'.json';
fs.writeFile(fileName, JSON.stringify(jsonGenomes), function (err){
if (err) {
UI.logger.log('Failed to save! '+err);
} else {
UI.logger.log('Saved to '+fileName);
}
UI.refreshFiles();
});
});
screen.key(['escape', 'q', 'C-c'], function(ch, key) {
return process.exit(0);
});
screen.key(['s'], function (ch, key){
if (learner.state == 'STOP') {
learner.state = 'LEARNING';
gameManipulator.focusGame();
learner.startLearning();
} else {
learner.state = 'STOP';
}
});
screen.render()
};
//
// Read entire folder and select files that match a .json file
//
UI.refreshFiles = function (){
var files = [];
var fileData =
{
name: 'Saved Files',
extended: true,
children: [{
name: 'Refresh Folders'
}]
};
var files = [];
var fileData = {
name: 'Saved Files',
extended: true,
children: [{
name: 'Refresh Folders'
}]
};
// Populate tree
UI.logger.log('Reading genomes dir...')
var files = fs.readdirSync('./genomes');
for(var k in files){
if(files[k].indexOf('.json') >= 0){
fileData.children.push({
name: files[k],
isFile: true,
});
}
}
// Populate tree
UI.logger.log('Reading genomes dir...')
var files = fs.readdirSync('./genomes');
for (var k in files) {
if (files[k].indexOf('.json') >= 0) {
fileData.children.push({
name: files[k],
isFile: true,
});
UI.savesTree.setData(fileData);
}
}
UI.savesTree.setData(fileData);
}
//
// Updates data on the screen and render it
//
UI.render = function () {
//
// Update data
//
UI.uiSensors.setData({
titles: ['Distance', 'Size', 'Speed', 'Activation'],
data: [
Math.round(UI.gm.sensors[0].value * 100),
Math.round(UI.gm.sensors[0].size * 100),
Math.round(UI.gm.sensors[0].speed * 100),
Math.round(UI.gm.gameOutput * 100),
]
})
// Update data
UI.uiSensors.setData({
titles: ['Distance', 'Size', 'Speed', 'Activation'],
data: [
Math.round(UI.gm.sensors[0].value * 100),
Math.round(UI.gm.sensors[0].size * 100),
Math.round(UI.gm.sensors[0].speed * 100),
Math.round(UI.gm.gameOutput * 100),
]
})
//
// Set Genome stats and score
//
var learn = UI.learner;
var uiStats = 'Status: '+learn.state+'\n';
uiStats += 'Fitness: '+UI.gm.points+'\nGameStatus: '+UI.gm.gamestate + '\n';
uiStats += 'Generation: '+learn.generation+' : '+learn.genome+'/'+learn.genomes.length;
UI.uiScore.setText(uiStats);
// Set Genome stats and score
var learn = UI.learner;
var uiStats = '';
uiStats += 'Status: ' + learn.state + '\n';
uiStats += 'Fitness: ' + UI.gm.points + '\n';
uiStats += 'GameStatus: ' + UI.gm.gamestate + '\n';
uiStats += 'Generation: ' + learn.generation;
uiStats += ' : ' + learn.genome + '/' + learn.genomes.length;
UI.uiScore.setText(uiStats);
if(UI.gm.gameOutput){
var str = 'Action: '+UI.gm.gameOutputString+'\nActivation: '+UI.gm.gameOutput;
UI.uiGenomes.setText(str);
}else{
UI.uiGenomes.setText('Loading...');
}
if (UI.gm.gameOutput) {
var str = '';
str += 'Action: ' + UI.gm.gameOutputString + '\n'
str += 'Activation: ' + UI.gm.gameOutput;
UI.uiGenomes.setText(str);
} else {
UI.uiGenomes.setText('Loading...');
}
//
// Render screen
//
screen.render();
// Render screen
screen.render();
}
// Continuously render screen
setInterval(UI.render, 25);
module.exports = UI;
+25 -40
Ver Arquivo
@@ -1,76 +1,61 @@
/*
Developed by Ivan Seidel [https://github.com/ivanseidel]
*/
var UI = require('./UI');
var robot = require('robotjs');
var Learner = require('./Learner');
var scanner = require('./scanner');
var GameManipulator = require('./GameManipulator');
//
var GameManipulator = require('./GameManipulator');
var Learner = require('./Learner');
var Scanner = require('./Scanner');
var UI = require('./UI');
// Configure Robotjs
//
robot.setMouseDelay(1);
//
// Initialize Game
//
GameManipulator.findGamePosition();
//
// Check for found game
//
if(GameManipulator.offset){
// Uncomment this line to debug the
// starting point (Check if it's detecting it correcly)
if (GameManipulator.offset) {
// Uncomment this line to debug the
// starting point of sensor (Check if it's detecting it correcly)
// robot.moveMouse(GameManipulator.offset[0]+GameManipulator.sensors[0].offset[0],
// GameManipulator.offset[1] + GameManipulator.sensors[0].offset[1]);
// robot.moveMouse(GameManipulator.offset[0]+GameManipulator.sensors[0].offset[0],
// GameManipulator.offset[1] + GameManipulator.sensors[0].offset[1]);
robot.moveMouse(GameManipulator.offset[0], GameManipulator.offset[1]);
}else{
console.error('FAILED TO FIND GAME!');
process.exit();
robot.moveMouse(GameManipulator.offset[0], GameManipulator.offset[1]);
} else {
console.error('FAILED TO FIND GAME!');
process.exit();
}
//
// Initialize UI
//
UI.init(GameManipulator, Learner);
//
// Init Learner
//
Learner.init(GameManipulator, UI, 12, 4, 0.2);
//
// Start reading game state and sensors
//
setInterval(GameManipulator.readSensors, 40);
setInterval(GameManipulator.readGameState, 200);
//
// Start game (Example of API usage)
//
/*
function startGame () {
var game = Math.round(Math.random() * 100);
var game = Math.round(Math.random() * 100);
UI.logger.log('Queuing start... ', game);
UI.logger.log('Queuing start... ', game);
GameManipulator.startNewGame(function() {
UI.logger.log('Game HAS started!', game);
GameManipulator.onGameEnd = function () {
UI.logger.log('Game HAS ended!', game);
GameManipulator.startNewGame(function() {
UI.logger.log('Game HAS started!', game);
GameManipulator.onGameEnd = function () {
UI.logger.log('Game HAS ended!', game);
startGame();
}
});
startGame();
}
});
}
*/
+69 -63
Ver Arquivo
@@ -1,97 +1,103 @@
/*
Developed by Ivan Seidel [https://github.com/ivanseidel]
*/
// External Modules
var robot = require('robotjs');
// Cache screen size
var screenSize = robot.getScreenSize();
// Indexes
var X = 0;
var Y = 1;
// Create the "class" wrapper
var scanner = {};
var Scanner = {};
//
// Check if the given position is outside the Screen
//
scanner.isOutOfBound = function (pos) {
if( pos[X] < 0 || pos[Y] < 0 ||
pos[X] >= screenSize.width ||
pos[Y] >= screenSize.height)
return true;
Scanner.isOutOfBound = function (pos) {
if ( pos[X] < 0 || pos[Y] < 0 ||
pos[X] >= screenSize.width ||
pos[Y] >= screenSize.height) {
return false;
return true;
}
return false;
}
//
// Limits the x/y values of position to fit the screen
//
scanner.makeInBounds = function (pos) {
Scanner.makeInBounds = function (pos) {
if(pos[X] < 0)
pos[X] = 0;
if (pos[X] < 0) {
pos[X] = 0;
}
if(pos[X] >= screenSize.width)
pos[X] = screenSize.width - 1;
if (pos[X] >= screenSize.width) {
pos[X] = screenSize.width - 1;
}
if(pos[Y] < 0)
pos[Y] = 0;
if (pos[Y] < 0) {
pos[Y] = 0;
}
if(pos[Y] >= screenSize.height)
pos[Y] = screenSize.height - 1;
if (pos[Y] >= screenSize.height) {
pos[Y] = screenSize.height - 1;
}
return pos;
return pos;
}
/*
Given start [X, Y], and a DELTA [dX, dY],
maps from "start", adding "delta" to position,
until "matchinColor" is found OR isOutOfBounds.
If iterations reach > iterLimit:
returns null;
// Given start [X, Y], and a DELTA [dX, dY],
// maps from "start", adding "delta" to position,
// until "matchinColor" is found OR isOutOfBounds.
//
// If iterations reach > iterLimit:
// returns null;
//
// if isOutOfBounds:
// returns null
//
// otherwise:
// return that point
//
// Example: (X direction)
// scanUntil([0,0], [1, 0], "000000");
Scanner.scanUntil = function (start, delta, matchColor, inverted, iterLimit) {
var color, current, iterations = 0;
if isOutOfBounds:
returns null
// (CLONE instead of using the real one)
current = Scanner.makeInBounds([start[X], start[Y]]);
otherwise:
return that point
Example: (X direction)
scanUntil([0,0], [1, 0], "000000");
*/
scanner.scanUntil = function (start, delta, matchingColor, invertMatch, iterLimit) {
var color, current, iterations = 0;
// (CLONE instead of using the real one)
current = scanner.makeInBounds([start[X], start[Y]]);
if(delta[X] == 0 && delta[Y] == 0)
return null;
if (delta[X] == 0 && delta[Y] == 0) {
return null;
}
while(!scanner.isOutOfBound(current)){
// Check current pixel
color = robot.getPixelColor(current[X], current[Y]);
while (!Scanner.isOutOfBound(current)) {
// Check current pixel
color = robot.getPixelColor(current[X], current[Y]);
if(!invertMatch && color.toString() == matchingColor)
return current;
if (!inverted && color.toString() == matchColor) {
return current;
}
if(invertMatch && color.toString() != matchingColor)
return current;
if (inverted && color.toString() != matchColor) {
return current;
}
current[X] += delta[X];
current[Y] += delta[Y];
iterations++;
current[X] += delta[X];
current[Y] += delta[Y];
iterations++;
if(iterations > iterLimit)
return null;
}
if (iterations > iterLimit) {
return null;
}
}
return null;
return null;
};
// Export the module
module.exports = scanner;
module.exports = Scanner;