Source: programming_api/control.js

require("./../rur.js");
require("./../translator.js");
require("./../default_tiles/tiles.js");
require("./output.js");
require("./../recorder/record_frame.js");
require("./exceptions.js");
require("./../world_get/world_get.js");
require("./../utils/supplant.js");
require("./../utils/key_exist.js");

require("./../world_api/walls.js");
require("./../world_api/obstacles.js");
require("./../world_api/background_tile.js");
require("./../world_api/pushables.js");
require("./../world_api/robot.js");
require("./../world_api/composition.js");
require("./../world_api/is_fatal.js");

RUR.control = {};

RUR.control.move = function (robot) {
    "use strict";
    var position, next_x, next_y, orientation, pushable_in_the_way, tile, tiles,
        x_beyond, y_beyond, recording_state, next_position, current_x, current_y,
        message;

    if (RUR.control.wall_in_front(robot)) {
        throw new RUR.WallCollisionError(RUR.translate("Ouch! I hit a wall!"));
    }

    position = RUR.get_position_in_front(robot);
    next_x = position.x;
    next_y = position.y;

    // attempt a move, by first saving the current position
    current_x = robot.x;
    current_y = robot.y;
    robot.x = next_x;
    robot.y = next_y;

    // If we move, are we going to push something else in front of us?
    pushable_in_the_way = RUR.get_pushable(next_x, next_y);
    if (pushable_in_the_way !== null) {
        next_position = RUR.get_position_in_front(robot);
        x_beyond = next_position.x;
        y_beyond = next_position.y;

        if (RUR.control.wall_in_front(robot) ||
            RUR.get_pushable(x_beyond, y_beyond) ||
            RUR.is_solid_obstacle(x_beyond, y_beyond) ||
            RUR.is_robot(x_beyond, y_beyond)) {
            // reverse the move
            robot.x = current_x;
            robot.y = current_y;
            throw new RUR.ReeborgError(RUR.translate("Something is blocking the way!"));
        } else {
            RUR._push_pushable(pushable_in_the_way, next_x, next_y, x_beyond, y_beyond);
            RUR.transform_tile(pushable_in_the_way, x_beyond, y_beyond);
        }
    }

    // We can now complete the move
    if (robot._is_leaky !== undefined && !robot._is_leaky) {
        // avoid messing the trace if and when we resume having a leaky robot
        robot._prev_x = robot.x;
        robot._prev_y = robot.y;
    } else {
        robot._prev_x = current_x;
        robot._prev_y = current_y;
    }
    RUR.state.sound_id = "#move-sound";


    // A move has been performed ... but it may have been a fatal decision
    message = RUR.is_fatal_position(robot.x, robot.y, robot);
    if (message) {
        throw new RUR.ReeborgError(message);
    }

    RUR.record_frame("move", robot.__id);
};


// leave end of line comments below such as using += 1
// as I (indirectly) refer to these comments in the programming tutorial

RUR.control.turn_left = function(robot){
    "use strict";
    var random;
    if (robot._orientation == RUR.RANDOM_ORIENTATION) {
        random = Math.floor(Math.random() * 4);
        robot._orientation = random;
        robot._prev_orientation = random;
    } else {
        robot._prev_orientation = robot._orientation;
        robot._orientation ++;
        robot._orientation %= 4;
    }
    robot._prev_x = robot.x;
    robot._prev_y = robot.y;

    RUR.state.sound_id = "#turn-sound";
    if (robot._is_leaky !== undefined && !robot._is_leaky) {  // update to avoid drawing from previous point.
        robot._prev_orientation = robot._orientation;
    }
    RUR.record_frame("turn_left", robot.__id);
};

RUR.control.__turn_right = function(robot){
    "use strict";
    robot._prev_orientation = (robot._orientation+2)%4; // fix so that oil trace looks right
    robot._prev_x = robot.x;
    robot._prev_y = robot.y;
    robot._orientation += 3;
    robot._orientation %= 4;
    if (robot._is_leaky !== undefined && !robot._is_leaky) {  // update to avoid drawing from previous point.
        robot._prev_orientation = robot._orientation;
    }
    RUR.record_frame("__turn_right", robot.__id);
};

RUR.control.pause = function (ms) {
    RUR.record_frame("pause", {pause_time:ms});
};

RUR.control.done = function () {
    RUR.state.done_executed = true;
    throw new RUR.ReeborgError(RUR.translate("Done!"));
};

RUR.control.put = function(robot, arg){
    var arg_in_english, objects_carried, obj_type, all_objects;
    RUR.state.sound_id = "#put-sound";
    arg_in_english = confirm_object_is_known(arg);
    all_objects = get_names_of_objects_carried(robot.objects);
    put_check_for_error (arg, arg_in_english, all_objects, robot.objects);
    // no error, we can proceed
    robot_put_or_toss_object(robot, arg_in_english, "put");
};

RUR.control.toss = function(robot, arg){
    var arg_in_english, objects_carried, obj_type, all_objects;
    arg_in_english = confirm_object_is_known(arg);
    all_objects = get_names_of_objects_carried(robot.objects);
    put_check_for_error (arg, arg_in_english, all_objects, robot.objects);
    // no error, we can proceed
    robot_put_or_toss_object(robot, arg_in_english, "throw");
};

function confirm_object_is_known(arg) {
    var arg_in_english;
    if (arg !== undefined) {
        arg_in_english = RUR.translate_to_english(arg);
        if (RUR.KNOWN_THINGS.indexOf(arg_in_english) == -1){
            throw new RUR.ReeborgError(RUR.translate("Unknown object").supplant({obj: arg}));
        }
    }
    return arg_in_english;
}

function get_names_of_objects_carried(objects_carried) {
    var obj_type, all_objects = [];
    for (obj_type in objects_carried) {
        if (objects_carried.hasOwnProperty(obj_type)) {
            all_objects.push(obj_type);
        }
    }
    return all_objects;
}

function put_check_for_error (arg, arg_in_english, all_objects, carried) {
    "use strict";
    if (arg !== undefined) {
        if (all_objects.length === 0){
            throw new RUR.MissingObjectError(RUR.translate("I don't have any object to put down!").supplant({obj:arg}));
        }
        if (carried[arg_in_english] === undefined) {
            throw new RUR.MissingObjectError(RUR.translate("I don't have any object to put down!").supplant({obj:arg}));
        }
    } else {
        if (all_objects.length === 0){
            throw new RUR.MissingObjectError(RUR.translate("I don't have any object to put down!").supplant({obj: RUR.translate("object")}));
        } else if (all_objects.length > 1){
             throw new RUR.MissingObjectError(RUR.translate("I carry too many different objects. I don't know which one to put down!"));
        }
    }
}

robot_put_or_toss_object = function (robot, obj, action) {
    "use strict";
    var objects_carried, coords, obj_type, position, x, y;

    RUR.utils.ensure_key_for_obj_exists(RUR.get_current_world(), "objects");
    if (action == "put") {
        x = robot.x;
        y = robot.y;
    } else if (action == "throw") {
        position = RUR.get_position_in_front(robot);
        x = position.x;
        y = position.y;
    } else {
        throw new RUR.ReeborgError("Fatal error, unknown action in put/throw :", action);
    }
    coords = x + "," + y;

    if (obj === undefined){
        //obj = Object.keys(robot.objects)[0]; // we have already ensured that there is only one
        objects_carried = robot.objects;
        for (obj_type in objects_carried) {
            if (objects_carried.hasOwnProperty(obj_type)) {
                obj = obj_type;
            }
        }
    }
    if (robot.objects[obj] != "infinite") {
        robot.objects[obj] -= 1;
    }
    if (robot.objects[obj] === 0) {
        delete robot.objects[obj];
    }

    RUR.utils.ensure_key_for_obj_exists(RUR.get_current_world().objects, coords);
    if (RUR.get_current_world().objects[coords][obj] === undefined) {
        RUR.get_current_world().objects[coords][obj] = 1;
    } else {
        RUR.get_current_world().objects[coords][obj] += 1;
    }

    RUR.transform_tile(obj, x, y);
    RUR.record_frame(action, [robot.__id, obj]);
};

function is_fatal_thing(thing, robot) {
    var protections;

    protections = RUR.get_protections(robot);

    if (RUR.get_property(thing, "fatal")) {
        if (protections.indexOf(RUR.get_property(thing, "fatal")) === -1) {
            return true;
        }
    }
    return false;
}

RUR.control.take = function(robot, arg){
    var translated_arg, objects_here, message;
    RUR.state.sound_id = "#take-sound";
    if (arg !== undefined) {
        translated_arg = RUR.translate_to_english(arg);
        if (RUR.KNOWN_THINGS.indexOf(translated_arg) == -1){
            throw new RUR.ReeborgError(RUR.translate("Unknown object").supplant({obj: arg}));
        }
    }

    objects_here = RUR.world_get.object_at_robot_position(robot, arg);
    if (arg !== undefined) {
        if (Array.isArray(objects_here) && objects_here.length === 0) {
            throw new RUR.MissingObjectError(RUR.translate("No object found here").supplant({obj: arg}));
        }  else if(is_fatal_thing(arg, robot)) {
            message = RUR.get_property(arg, 'message');
            if (!message) {
                message = "I picked up a fatal object.";
            }
            throw new RUR.ReeborgError(RUR.translate(message));
        } else {
            take_object_and_give_to_robot(robot, arg);
        }
    }  else if (Array.isArray(objects_here) && objects_here.length === 0){
        throw new RUR.MissingObjectError(RUR.translate("No object found here").supplant({obj: RUR.translate("object")}));
    }  else if (objects_here.length > 1){
        throw new RUR.MissingObjectError(RUR.translate("Many objects are here; I do not know which one to take!"));
    }  else if(is_fatal_thing(objects_here[0], robot)) {
        message = RUR.get_property(objects_here[0], 'message');
        if (!message) {
            message = "I picked up a fatal object.";
        }
        throw new RUR.ReeborgError(RUR.translate(message));
    } else {
        take_object_and_give_to_robot(robot, objects_here[0]);
    }
};

take_object_and_give_to_robot = function (robot, obj) {
    var objects_here, coords;
    obj = RUR.translate_to_english(obj);
    coords = robot.x + "," + robot.y;
    RUR.get_current_world().objects[coords][obj] -= 1;

    if (RUR.get_current_world().objects[coords][obj] === 0){
        delete RUR.get_current_world().objects[coords][obj];
        // Testing for empty array.
        // In Javascript []==[] is false and ![] is false ...
        // Python is so much nicer than Javascript.
        objects_here = RUR.world_get.object_at_robot_position(robot);
        if (Array.isArray(objects_here) && objects_here.length === 0){
            delete RUR.get_current_world().objects[coords];
        }
    }
    RUR.utils.ensure_key_for_obj_exists(robot, "objects");
    if (robot.objects[obj] === undefined){
        robot.objects[obj] = 1;
    } else {
        if (robot.objects[obj] != "infinite") {
            robot.objects[obj]++;
        }
    }
    RUR.record_frame("take", [robot.__id, obj]);
};


RUR.control.build_wall = function (robot){
    RUR.state.sound_id = "#build-sound";
    switch (robot._orientation){
    case RUR.EAST:
        RUR.add_wall("east", robot.x, robot.y); // records automatically
        break;
    case RUR.NORTH:
        RUR.add_wall("north", robot.x, robot.y);
        break;
    case RUR.WEST:
        RUR.add_wall("west", robot.x, robot.y);
        break;
    case RUR.SOUTH:
        RUR.add_wall("south", robot.x, robot.y);
        break;
    default:
        throw new RUR.ReeborgError("Should not happen: unhandled case in RUR.control.build_wall().");
    }
};


RUR.control.wall_in_front = function (robot) {
    switch (robot._orientation){
    case RUR.EAST:
        return RUR._is_wall("east", robot.x, robot.y);
    case RUR.NORTH:
        return RUR._is_wall("north", robot.x, robot.y);
    case RUR.WEST:
        return RUR._is_wall("west", robot.x, robot.y);
    case RUR.SOUTH:
        return RUR._is_wall("south", robot.x, robot.y);
    case RUR.RANDOM_ORIENTATION:
        throw new RUR.ReeborgError(RUR.translate("I am too dizzy!"));
    default:
        throw new RUR.ReeborgError("Should not happen: unhandled case in RUR.control.wall_in_front().");
    }
};

RUR.control.wall_on_right = function (robot) {
    var result, saved_recording_state;
    saved_recording_state = RUR._recording_(false);
    RUR.control.__turn_right(robot);
    result = RUR.control.wall_in_front(robot);
    RUR.control.turn_left(robot);
    RUR._recording_(saved_recording_state);
    return result;
};


RUR.control.front_is_clear = function(robot){
    var tile, tiles, solid, name, position, next_x, next_y;
    if( RUR.control.wall_in_front(robot)) {
        return false;
    }
    position = RUR.get_position_in_front(robot);
    next_x = position.x;
    next_y = position.y;

    if (RUR.is_fatal_position(next_x, next_y, robot) &&
        RUR.is_detectable_position(next_x, next_y)) {
        return false;
    }

    return true;
};

RUR.control.right_is_clear = function(robot){
    var result, saved_recording_state;
    saved_recording_state = RUR._recording_(false);
    RUR.control.__turn_right(robot);
    result = RUR.control.front_is_clear(robot);
    RUR.control.turn_left(robot);
    RUR._recording_(saved_recording_state);
    return result;
};

RUR.control.is_facing_north = function (robot) {
    return robot._orientation === RUR.NORTH;
};

RUR.control.think = function (delay) {
    var old_delay = RUR.PLAYBACK_TIME_PER_FRAME;
    RUR.PLAYBACK_TIME_PER_FRAME = delay;
    return old_delay;
};

RUR.control.at_goal = function (robot) {
    var goal = RUR.get_current_world().goal;
    if (goal !== undefined){
        if (goal.position !== undefined) {
            return (robot.x === goal.position.x && robot.y === goal.position.y);
        }
        throw new RUR.ReeborgError(RUR.translate("There is no position as a goal in this world!"));
    }
    throw new RUR.ReeborgError(RUR.translate("There is no goal in this world!"));
};


RUR.control.carries_object = function (robot, obj) {
    var obj_type, all_objects, carried=false;

    if (robot === undefined || robot.objects === undefined) {
        return 0;
    }

    all_objects = {};

    if (obj === undefined) {
        for (obj_type in robot.objects) {
            if (robot.objects.hasOwnProperty(obj_type)) {
                all_objects[RUR.translate(obj_type)] = robot.objects[obj_type];
                carried = true;
            }
        }
        if (carried) {
            return all_objects;
        } else {
            return 0;
        }
    } else {
        obj = RUR.translate_to_english(obj);
        for (obj_type in robot.objects) {
            if (robot.objects.hasOwnProperty(obj_type) && obj_type == obj) {
                return robot.objects[obj_type];
            }
        }
        return 0;
    }
};


RUR.control.set_model = function(robot, model){
    var default_robot;
    robot.model = model;
    default_robot = RUR.get_current_world().robots[0];
    if (default_robot.__id == robot.__id) {
        RUR.user_selected_model = undefined;  // overrides the user's choice
    }
    RUR.record_frame("set_model", robot.__id);
 };

/** @function set_model
 * @memberof RUR
 * @instance
 * @summary This function, intended for world creators, allow to set the
 * model for the default robot, overriding the user's default choice.
 *
 *  @param {string} model The name of the model
 */

RUR.set_model = function(model){
    var robot;
    robot = RUR.get_current_world().robots[0];
    robot.model = model;
    RUR.user_selected_model = undefined;  // overrides the user's choice
    RUR.record_frame("RUR.set_model", robot.__id);
 };


RUR.control.set_trace_color = function(robot, color){
    robot._trace_color = color;
 };

RUR.control.set_trace_style = function(robot, style){
    robot._trace_style = style;
 };

if (RUR.state === undefined){
    RUR.state = {};
}

RUR.state.sound_on = false;
RUR.control.sound = function(on){
    if(!on){
        RUR.state.sound_on = false;
        return;
    }
    RUR.state.sound_on = true;
};