Initial release

Esse commit está contido em:
Ivan Seidel
2015-12-27 19:18:43 -02:00
commit 2e74f81092
9 arquivos alterados com 1270 adições e 0 exclusões
+11
Ver Arquivo
@@ -0,0 +1,11 @@
# Logs
logs
*.log
# Dependency directory
node_modules
robotjs
# Saved files
genomes/*
!genomes/.gitkeep
Arquivo executável
+452
Ver Arquivo
@@ -0,0 +1,452 @@
var robot = require('./robotjs');
var scanner = require ('./scanner');
var screenSize = robot.getScreenSize();
//
// Color definitions
//
// This is the Dino's colour, also used by Obstacles.
var COLOR_DINOSAUR = "535353";
var GameManipulator = {
// Stores the game position (Globally)
offset: null,
width: null,
// Stores points (jumps)
points: 0,
// Listners
onGameEnd: null,
onGameStart: null,
onSensorData: null,
// Game State
gamestate: 'OVER',
// GameOver Position
gameOverOffset: [190, -82],
//
// 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,
// 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,
// }
]
};
//
// Find out dinosaur (fast)
//
GameManipulator.findGamePosition = function () {
var pos, dinoPos, skipXFast = 15;
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);
if(dinoPos)
break;
}
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);
if(pos)
break;
}
// Did actually found? If not, error!
if(!pos)
return null;
// Find the end of the game
var endPos = pos;
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;
};
//
// 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]
],
[2, 0], COLOR_DINOSAUR, false, 20);
if(found && GameManipulator.gamestate != 'OVER'){
GameManipulator.gamestate = 'OVER';
// Clear keys
GameManipulator.setGameOutput(0.5);
// Trigger callback and clear
GameManipulator.onGameEnd && GameManipulator.onGameEnd(GameManipulator.points);
GameManipulator.onGameEnd = null;
// console.log('GAME OVER: '+GameManipulator.points);
}else if(!found && GameManipulator.gamestate != 'PLAYING'){
GameManipulator.gamestate = 'PLAYING';
// Clear points
GameManipulator.points = 0;
GameManipulator.lastScore = 0;
// 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;
// Clar Output flags
GameManipulator.lastOutputSet = 'NONE';
// Trigger callback and clear
GameManipulator.onGameStart && GameManipulator.onGameStart();
GameManipulator.onGameStart = null;
// 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();
// If game is already over, press space
if(GameManipulator.gamestate == 'OVER'){
clearInterval(_startKeyInterval);
// Set start callback
GameManipulator.onGameStart = function (argument) {
clearInterval(_startKeyInterval);
next && next();
};
// Press space to begin game (repetidelly)
_startKeyInterval = setInterval(function (){
robot.keyTap(' ');
}, 300);
// Refresh state
GameManipulator.readGameState();
}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];
if(sensor.value > 0.5 && sensor.lastValue < 0.3){
GameManipulator.points++;
// console.log('POINTS: '+GameManipulator.points);
}
}
}
//
// Read sensors
//
// Sensors are like ray-traces:
// They have a starting point,
// and a limit to search for.
//
// Each sensor can gatter data about
// the DISTANCE of the object, it's
// SIZE and it's speed
//
// Note: We currently only have a sensor.
//
GameManipulator.readSensors = function () {
var offset = GameManipulator.offset;
var startTime = Date.now();
for(var k in GameManipulator.sensors){
var sensor = GameManipulator.sensors[k];
// 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;
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;
// 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
);
// 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;
}
// We use the current score to check for object equality
sensor.lastScore = GameManipulator.points;
// sensor.size = Math.max(sensor.size, endPoint[0] - end[0]);
}else{
sensor.value = 1;
sensor.size = 0;
}
// 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;
sensor.lastSpeeds.unshift(newSpeed);
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;
sensor.speed = Math.max(avgSpeed - 1.5, sensor.speed);
}
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();
}
// Compute points
GameManipulator.computePoints();
// 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)
//
var PRESS = 'down';
var RELEASE = 'up';
GameManipulator.lastOutputSet = 'NONE';
GameManipulator.lastOutputSetTime = 0;
GameManipulator.setGameOutput = function (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{
// 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);
}
}
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';
}
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");
}
module.exports = GameManipulator;
Arquivo executável
+327
Ver Arquivo
@@ -0,0 +1,327 @@
var _ = require('lodash');
var async = require('async');
var synaptic = require('synaptic');
var Network = synaptic.Network;
var Architect = synaptic.Architect;
var Learn = {
//
// Current Genomes
// In form of: {
// fitness: null,
// genome: [...],
// network: {NeuralNetworkObject}
// }
//
genomes: [],
state: 'STOP',
//
// Current genome tryout
//
genome: 0,
generation: 0,
//
// Set this, to verify genome action BEFORE running it
//
shouldCheckExperience: false,
};
//
// Initialize the Learner
//
Learn.init = function (gameManipulator, ui, genomeUnits, selection, mutationProb) {
Learn.gm = gameManipulator;
Learn.ui = ui;
Learn.genome = 0;
Learn.generation = 0;
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));
}
Learn.executeGeneration();
}
//
// Given the entire generation of genomes (An array),
// applyes method `executeGenome` for each element.
// After all elements have completed executing:
//
// 1) Select best genomes
// 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;
Learn.generation++;
Learn.ui.logger.log('Executing generation '+Learn.generation);
Learn.genome = 0;
async.mapSeries(Learn.genomes, Learn.executeGenome, function (argument) {
// Kill worst genomes
Learn.genomes = Learn.selectBestGenomes(Learn.selection);
// 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 and Mutate
var newGenome = Learn.mutate(Learn.crossOver(genA, genB));
// 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();
// Cross over and Mutate
var newGenome = Learn.mutate(gen);
// Add to generation
Learn.genomes.push(Network.fromJSON(newGenome));
}
Learn.ui.logger.log('Completed generation '+Learn.generation);
// 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();
while(selected.length > selectN){
selected.pop();
}
Learn.ui.logger.log('Fitness: '+_.pluck(selected, 'fitness').join(','));
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;
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();
}
}
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);
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);
// Save Genome fitness
genome.fitness = points;
// 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;
// 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;
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;
}
//
// Load genomes saved from JSON file
//
Learn.loadGenomes = function (genomes, deleteOthers){
if(deleteOthers)
Learn.genomes = [];
var loaded = 0;
for(var k in genomes){
Learn.genomes.push(Network.fromJSON(genomes[k]));
loaded++;
}
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();
}
//
// 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;
}
// 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');
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);
return net;
}
//
// Given an Object A and an object B, both Arrays
// of Objects:
//
// 1) Select a cross over point (cutLocation)
// 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 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;
a[k][key] += a[k][key] * (Math.random() - 0.5) * 3 + (Math.random() - 0.5);
}
}
module.exports = Learn;
+81
Ver Arquivo
@@ -0,0 +1,81 @@
# IAMDinosaur
An simpler artificial inteligence to teach Google Chrome's offline dinosaur to
jump cactus, using Neural Networks and a simple Genetic Algorithm.
## Instalation
1. Install `Node.js` on your computer.
2. Clone/download this folder to your computer.
3. run `npm install` within this folder
4. Open Chrome's dinosaur game and put aside the terminal (It MUST be on the same screen)
**(Tip: go to developer tools, and under network, set to offline )**
5. run `node index` within this folder. If the game was located, it will move the cursor
of the mouse to the origin of the `floor` of the dino. Press `s` key in the terminal to
start learning.
## How does it work
We have 3 different inputs read from the pixels of the screen:
1. Distance from the next cactus
2. Length of the next cactus
3. Speed of the current cactus
We have also, one output with 3 possible states:
1. output < 0.45: Press DOWN key
2. output > 0.55: Press UP key
2. default: Release both keys
## Genetic Algorithm
Each Generation consists of 12 neural networks (Genomes).
Each genome is tested with the game, by constantly mapping the read
inputs from the game to the inputs of the neural network, and by getting
the output/activation from the network and applying to the keys of the
keyboard.
While testing each genome, we keep track of it's "fitness" by counting
jumped cactus in the game.
When an entire generation is completed, we remove the worst genomes until
achieving `N` genomes. With those `N` genomes, we then select two randomly,
and cross-over their values/configurations. After that, we apply random mutations
in the values/configurations of the Neural Network, creating a new genome.
We do the cross-over/mutation until we get 12 genomes again, and repeat it constantly.
## Inplementation
All the implementation was done using Node.js, with Synaptic (Neural Network library),
and RobotJs (a library to read pixels and simulate key presses).
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
ray tracing. Also have some utilities functions.
- `UI.js`: Global scope for the UI management. It initializes and also updates the screen
on changes.
- `GameManipulator.js`: Has all the necessary code to read sensors, and apply outputs
to the game. Is also responsible for computing points, getting the game state and
triggering callbacks/listeners to real implementation.
- `Learner.js`: It is the core implementation of the Genetic Algorithm. This is where
"magic" happens, by running generations, doing "natural" selection, cross-over, mutation...
## Credits
- [Tony Ngan](https://github.com/tngan) **The idea came from him**
- [João Pedro](https://github.com/joaopedrovbs)
Arquivo executável
+215
Ver Arquivo
@@ -0,0 +1,215 @@
var fs = require('fs');
var blessed = require('blessed')
var contrib = require('blessed-contrib')
var screen = blessed.screen()
var UI = {};
//
// Initialize UI objects
//
UI.init = function (gameManipulator, 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',
});
//
// 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';
}
});
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'
}]
};
// 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);
}
//
// 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),
]
})
//
// 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);
if(UI.gm.gameOutput){
var str = 'Action: '+UI.gm.gameOutputString+'\nActivation: '+UI.gm.gameOutput;
UI.uiGenomes.setText(str);
}else{
UI.uiGenomes.setText('Loading...');
}
//
// Render screen
//
screen.render();
}
setInterval(UI.render, 25);
module.exports = UI;
Ver Arquivo
Arquivo executável
+72
Ver Arquivo
@@ -0,0 +1,72 @@
var UI = require('./UI');
var robot = require('./robotjs');
var Learner = require('./Learner');
var scanner = require('./scanner');
var GameManipulator = require('./GameManipulator');
//
// 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)
// 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();
}
//
// 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);
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);
startGame();
}
});
}
*/
Arquivo executável
+19
Ver Arquivo
@@ -0,0 +1,19 @@
{
"name": "iamdinosaur",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"async": "^1.5.0",
"blessed": "^0.1.81",
"blessed-contrib": "^2.5.2",
"lodash": "^3.10.1",
"robotjs": "file:robotjs",
"synaptic": "^1.0.2"
}
}
Arquivo executável
+93
Ver Arquivo
@@ -0,0 +1,93 @@
// External Modules
var robot = require('./robotjs');
var screenSize = robot.getScreenSize();
// Indexes
var X = 0;
var Y = 1;
// Create the "class" wrapper
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;
return false;
}
//
// Limits the x/y values of position to fit the screen
//
scanner.makeInBounds = function (pos) {
if(pos[X] < 0)
pos[X] = 0;
if(pos[X] >= screenSize.width)
pos[X] = screenSize.width - 1;
if(pos[Y] < 0)
pos[Y] = 0;
if(pos[Y] >= screenSize.height)
pos[Y] = screenSize.height - 1;
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;
if isOutOfBounds:
returns null
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;
while(!scanner.isOutOfBound(current)){
// Check current pixel
color = robot.getPixelColor(current[X], current[Y]);
if(!invertMatch && color.toString() == matchingColor)
return current;
if(invertMatch && color.toString() != matchingColor)
return current;
current[X] += delta[X];
current[Y] += delta[Y];
iterations++;
if(iterations > iterLimit)
return null;
}
return null;
};
// Export the module
module.exports = scanner;