var robot = require('robotjs'); // Cache screen size var screenSize = robot.getScreenSize(); var Scanner = require ('./Scanner'); // COLOR DEFINITIONS // This is the Dino's colour, also used by Obstacles. var COLOR_DINOSAUR = '535353'; var DARK_COLOR_DINO = 'ACACAC'; 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, }, ] }; // 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 (){ // Due to dino slowly gliding over the screen after multiple restarts, its better to just reload the page GameManipulator.reloadPage(); setTimeout(function() { // Once reloaded we wait 0.5sec for it to let us start the game with a space. robot.keyTap(' '); }, 500); }, 300); // Refresh state GameManipulator.readGameState(); } else { // Wait die, and call recursive action GameManipulator.onGameEnd = function () { GameManipulator.startNewGame(next); } } } // reload the page GameManipulator.reloadPage = function () { // retrieves platform var platform = process.platform; if(/^win/.test(process.platform)) { robot.keyTap('r','control'); } else if(/^darwin/.test(process.platform)) { robot.keyTap('r','command'); } } // 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); } // Save length/size of sensor value sensor.size = Math.min(sensor.size, 1.0); startTime = Date.now(); } // Compute points GameManipulator.computePoints(); // Call sensor callback (to act) GameManipulator.onSensorData && GameManipulator.onSensorData(); } // Set action to game // Values: // 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); 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;