require("./../rur.js"); require("./../translator.js"); require("./../world_api/things.js"); // why ? //TODO add overlay object (like sensor) RUR.vis_world = {}; RUR.vis_world.refresh_world_edited = function () { RUR.vis_world.draw_all(); RUR.world_get.world_info(); }; /** @function set_world_size * @memberof RUR * @instance * * @desc Change the size of the world * * @param {integer} max_x The width of the world. Internally, we use * `cols` instead of `max_x`. * @param {integer} max_y The height of the world. Internally, we use * `rows` instead of `max_y`. */ RUR.set_world_size = function (max_x, max_y) { "use strict"; var height, width, canvas, ctx, world; set_scale(); if (max_x !== undefined && max_y !== undefined) { height = (max_y + 1.5) * RUR.WALL_LENGTH; width = (max_x + 1.5) * RUR.WALL_LENGTH; RUR.MAX_Y = max_y; RUR.MAX_X = max_x; } else { height = (RUR.MAX_Y + 1.5) * RUR.WALL_LENGTH; width = (RUR.MAX_X + 1.5) * RUR.WALL_LENGTH; } world = RUR.get_current_world(); world.rows = RUR.MAX_Y; world.cols = RUR.MAX_X; if (height !== RUR.HEIGHT || width !== RUR.WIDTH) { for (canvas of RUR.CANVASES) { //jshint ignore:line canvas.width = width; canvas.height = height; } RUR.HEIGHT = height; RUR.WIDTH = width; } RUR.vis_world.draw_all(); }; function set_scale () { if (RUR.get_current_world().small_tiles) { RUR.WALL_LENGTH = RUR.DEFAULT_WALL_LENGTH/2; RUR.WALL_THICKNESS = RUR.DEFAULT_WALL_THICKNESS/2; RUR.SCALE = 0.5; RUR.BACKGROUND_CTX.font = "8px sans-serif"; } else { RUR.WALL_LENGTH = RUR.DEFAULT_WALL_LENGTH; RUR.WALL_THICKNESS = RUR.DEFAULT_WALL_THICKNESS; RUR.SCALE = 1; RUR.BACKGROUND_CTX.font = "bold 12px sans-serif"; } } // retaining compatibility with some of Vincent Maille's worlds. RUR.vis_world.compute_world_geometry = RUR.set_world_size; RUR.vis_world.draw_all = function () { "use strict"; var ctx, world = RUR.get_current_world(); if (world.blank_canvas) { // for game environment if (RUR.state.editing_world) { RUR.show_feedback("#Reeborg-shouts", RUR.translate("Editing of blank canvas is not supported.")); return; } clearTimeout(RUR.ANIMATION_FRAME_ID); RUR.ANIMATION_FRAME_ID = undefined; for (ctx of RUR.ALL_CTX) { ctx.clearRect(0, 0, RUR.WIDTH, RUR.HEIGHT); } return; } RUR.BACKGROUND_CTX.clearRect(0, 0, RUR.WIDTH, RUR.HEIGHT); if (RUR.get_current_world().background_image !== undefined) { draw_background_image(RUR.BACKGROUND_IMAGE); } else { draw_grid_walls(RUR.BACKGROUND_CTX); } draw_coordinates(); RUR.animated_images = false; RUR.vis_world.refresh(); }; RUR.vis_world.clear_all_ctx = function () { // useful for graphics.py for (var ctx of RUR.ALL_CTX) { ctx.clearRect(0, 0, RUR.WIDTH, RUR.HEIGHT); } }; RUR.vis_world.refresh = function () { "use strict"; var canvas, canvases, goal, world = RUR.get_current_world(); if (world.blank_canvas) { return; } // This is not the most efficient way to do things; ideally, one // would keep track of changes (e.g. addition or deletion of objects) // and only redraw when needed. However, it is not critical at // the moment canvases = ["TILES_CTX", "BRIDGE_CTX", "DECORATIVE_OBJECTS_CTX", "OBSTACLES_CTX", "GOAL_CTX", "OBJECTS_CTX", "PUSHABLES_CTX", "TRACE_CTX", "WALL_CTX", "ROBOT_CTX"]; for (canvas of canvases) { RUR[canvas].clearRect(0, 0, RUR.WIDTH, RUR.HEIGHT); } draw_border(RUR.WALL_CTX); draw_tiles(world.tiles, RUR.TILES_CTX); draw_tiles(world.bridge, RUR.BRIDGE_CTX); draw_tiles(world.decorative_objects, RUR.DECORATIVE_OBJECTS_CTX); draw_tiles(world.obstacles, RUR.OBSTACLES_CTX); draw_tiles(world.pushables, RUR.PUSHABLES_CTX); draw_tiles(world.walls, RUR.WALL_CTX); draw_tiles(world.objects, RUR.OBJECTS_CTX); draw_info(); // on ROBOT_CTX if (RUR.ROBOT_ANIMATION_FRAME_ID) { clearTimeout(RUR.ROBOT_ANIMATION_FRAME_ID); } draw_robots(); // on ROBOT_CTX; also draws the trace // Animated images are redrawn according to their own schedule // If we have not drawn any yet, we should see if we need to draw some if (!RUR.animated_images) { draw_animated_images(); } if (RUR.state.editing_world || RUR.state.visible_grid) { // make them appear above background and tiles but below foreground walls. draw_grid_walls(RUR.GOAL_CTX, RUR.state.editing_world); } if (world.goal !== undefined){ goal = true; if (world.goal.pushables !== undefined){ draw_tiles(world.goal.pushables, RUR.GOAL_CTX, goal); } if (world.goal.objects !== undefined){ draw_tiles(world.goal.objects, RUR.GOAL_CTX, goal); } if (world.goal.walls !== undefined){ draw_tiles(world.goal.walls, RUR.GOAL_CTX, goal); } if (world.goal.position !== undefined) { draw_goal_position(world.goal); } } }; function draw_coordinates () { "use strict"; var x, y, ctx = RUR.BACKGROUND_CTX, grid_size=RUR.WALL_LENGTH; ctx.fillStyle = RUR.COORDINATES_COLOR; y = RUR.HEIGHT + 5 - grid_size/2; for(x=1; x <= RUR.MAX_X; x++){ ctx.fillText(x, (x+0.5)*grid_size, y); } x = grid_size/2 -5; for(y=1; y <= RUR.MAX_Y; y++){ ctx.fillText(y, x, RUR.HEIGHT - (y+0.3)*grid_size); } ctx.fillStyle = RUR.AXIS_LABEL_COLOR; ctx.fillText("x", RUR.WIDTH/2, RUR.HEIGHT - 10); ctx.fillText("y", 5, RUR.HEIGHT/2 ); } function draw_grid_walls (ctx, edit){ "use strict"; var i, j, image_e, image_n, wall_e, wall_n, x_offset_e, x_offset_n, y_offset_e, y_offset_n; if (RUR.SCALE == 0.5) { // small wall, adjust grid walls to be less visible ctx.save(); ctx.globalAlpha = 0.3; } if (edit) { wall_e = RUR.THINGS["east_edit"]; wall_n = RUR.THINGS["north_edit"]; } else { wall_e = RUR.THINGS["east_grid"]; wall_n = RUR.THINGS["north_grid"]; } image_e = wall_e.image; x_offset_e = wall_e.x_offset; y_offset_e = wall_e.y_offset; image_n = wall_n.image; x_offset_n = wall_n.x_offset; y_offset_n = wall_n.y_offset; for (i = 1; i <= RUR.MAX_X; i++) { for (j = 1; j <= RUR.MAX_Y; j++) { draw_single_object(image_e, i, j, ctx, x_offset_e, y_offset_e); draw_single_object(image_n, i, j, ctx, x_offset_n, y_offset_n); } } if (RUR.SCALE == 0.5) { ctx.restore(); } } function draw_border (ctx) { "use strict"; var j, image, wall, x_offset, y_offset, world; world = RUR.get_current_world(); wall = RUR.THINGS["east_border"]; image = wall.image; x_offset = wall.x_offset; y_offset = wall.y_offset; for (j = 1; j <= RUR.MAX_Y; j++) { draw_single_object(image, 0, j, ctx, x_offset, y_offset); } for (j = 1; j <= RUR.MAX_Y; j++) { draw_single_object(image, RUR.MAX_X, j, ctx, x_offset, y_offset); } wall = RUR.THINGS["north_border"]; image = wall.image; x_offset = wall.x_offset; y_offset = wall.y_offset; for (j = 1; j <= RUR.MAX_X; j++) { draw_single_object(image, j, 0, ctx, x_offset, y_offset); } for (j = 1; j <= RUR.MAX_X; j++) { draw_single_object(image, j, RUR.MAX_Y, ctx, x_offset, y_offset); } } function draw_robots () { "use strict"; var body, robot, robots = RUR.get_current_world().robots; if (!robots || robots[0] === undefined) { return; } for (robot=0; robot < robots.length; robot++){ body = robots[robot]; if (body._orientation == RUR.RANDOM_ORIENTATION) { continue; } if (body.possible_initial_positions !== undefined && body.possible_initial_positions.length > 1){ draw_robot_clones(body); } else { RUR.vis_robot.draw(body); // draws trace automatically } } if (RUR.state.animated_robots) { RUR.ROBOT_ANIMATION_FRAME_ID = setTimeout(draw_robots, RUR.ROBOT_ANIMATION_TIME); } } function draw_random_robots (robots) { "use strict"; var body, robot; if (!robots || robots[0] === undefined) { return; } for (robot=0; robot < robots.length; robot++){ body = robots[robot]; if (body._orientation != RUR.RANDOM_ORIENTATION) { continue; } if (body.possible_initial_positions !== undefined && body.possible_initial_positions.length > 1){ draw_robot_clones(body, true); } else { RUR.vis_robot.draw_random(body); } } } function draw_robot_clones (robot, random){ "use strict"; var i, clone; if (random) { RUR.ROBOT_ANIM_CTX.save(); RUR.ROBOT_ANIM_CTX.globalAlpha = 0.4; } else { RUR.ROBOT_CTX.save(); RUR.ROBOT_CTX.globalAlpha = 0.4; } for (i=0; i < robot.possible_initial_positions.length; i++){ clone = JSON.parse(JSON.stringify(robot)); clone.x = robot.possible_initial_positions[i][0]; clone.y = robot.possible_initial_positions[i][1]; clone._prev_x = clone.x; clone._prev_y = clone.y; if (random) { RUR.vis_robot.draw_random(clone); } else { RUR.vis_robot.draw(clone); } } if (random) { RUR.ROBOT_ANIM_CTX.restore(); } else { RUR.ROBOT_CTX.restore(); } } function draw_goal_position (goal) { "use strict"; var image, i, coord, x_offset, y_offset, ctx; ctx = RUR.GOAL_CTX; if (goal.position.image !== undefined && typeof goal.position.image === 'string' && RUR.THINGS[goal.position.image] !== undefined){ image = RUR.THINGS[goal.position.image].image; x_offset = RUR.THINGS[goal.position.image].x_offset; y_offset = RUR.THINGS[goal.position.image].y_offset; } else { // For anyone wondering, this step might be needed only when using older world // files that were created when there was not a choice // of image for indicating the home position. // In that case, it is ok to have x_offset and y_offset undefined. image = RUR.THINGS["green_home_tile"].image; } if (goal.possible_final_positions !== undefined && goal.possible_final_positions.length > 1){ ctx.save(); ctx.globalAlpha = 0.5; for (i=0; i < goal.possible_final_positions.length; i++){ coord = goal.possible_final_positions[i]; draw_single_object(image, coord[0], coord[1], ctx, x_offset, y_offset); } ctx.restore(); } else { draw_single_object(image, goal.position.x, goal.position.y, ctx, x_offset, y_offset); } } function draw_tiles (tiles, ctx, goal){ "use strict"; var i, j, coords, keys, key, image, tile, colour, t, tile_array; if (tiles === undefined) { return; } keys = Object.keys(tiles); for (key=0; key < keys.length; key++){ coords = keys[key].split(","); i = parseInt(coords[0], 10); j = parseInt(coords[1], 10); if (tiles[keys[key]] !== undefined) { tile_array = tiles[keys[key]]; if (Object.prototype.toString.call(tile_array) == "[object Object]") { tile_array = Object.keys(tile_array); } for (t=0; t<tile_array.length; t++) { tile = RUR.THINGS[tile_array[t]]; if (tile === undefined || tile.color) { if (tile === undefined) { colour = tiles[keys[key]]; } else { colour = tile.color; } draw_coloured_tile(colour, i, j, ctx); continue; } else if (goal) { image = tile.goal.image; if (image === undefined){ console.warn("problem in draw_tiles; tile =", tile, ctx); throw new RUR.ReeborgError("Problem in draw_tiles; goal image not defined."); } draw_single_object(image, i, j, ctx, tile.x_offset, tile.y_offset); } else if (tile.choose_image === undefined){ image = tile.image; if (image === undefined){ console.warn("problem in draw_tiles; tile =", tile, ctx); throw new RUR.ReeborgError("Problem in draw_tiles; image not defined."); } draw_single_object(image, i, j, ctx, tile.x_offset, tile.y_offset); } } } } } function draw_animated_images (){ "use strict"; var i, flag, anims, canvas, canvases, obj, ctx, world = RUR.get_current_world(); clearTimeout(RUR.ANIMATION_FRAME_ID); canvases = ["TILES_ANIM_CTX", "BRIDGE_ANIM_CTX", "DECORATIVE_OBJECTS_ANIM_CTX", "OBSTACLES_ANIM_CTX", "GOAL_ANIM_CTX", "OBJECTS_ANIM_CTX", "PUSHABLES_ANIM_CTX", "ROBOT_ANIM_CTX"]; for (canvas of canvases) { RUR[canvas].clearRect(0, 0, RUR.WIDTH, RUR.HEIGHT); } RUR.state.random_robot = false; draw_random_robots(world.robots); flag = RUR.state.random_robot; // flag is true when animated images are drawn on a given cycle anims = [[world.tiles, RUR.TILES_ANIM_CTX], [world.bridge, RUR.BRIDGE_ANIM_CTX], [world.decorative_objects, RUR.DECORATIVE_OBJECTS_ANIM_CTX], [world.obstacles, RUR.OBSTACLES_ANIM_CTX], [world.goal, RUR.GOAL_ANIM_CTX], [world.objects, RUR.OBJECTS_ANIM_CTX], [world.pushables, RUR.PUSHABLES_ANIM_CTX]]; for (i=0; i < anims.length; i++) { obj = anims[i][0]; if (obj) { ctx = anims[i][1]; /* Important: flag must come after draw_anim to avoid short-circuit evaluation which would result in draw_anim being called only once. */ flag = draw_anim(obj, ctx) || flag; } } if (flag) { RUR.ANIMATION_FRAME_ID = setTimeout(draw_animated_images, RUR.ANIMATION_TIME); } // make it known globally for refresh() whether or not we have drawn // animated images RUR.animated_images = flag; } function draw_anim (objects, ctx) { "use strict"; var i, j, i_j, coords, flag, k, n, image, obj, obj_here, elem, recording_state, remove_flag, images_to_remove=[]; flag = false; coords = Object.keys(objects); for (k=0; k < coords.length; k++){ i_j = coords[k].split(","); i = parseInt(i_j[0], 10); j = parseInt(i_j[1], 10); obj_here = objects[coords[k]]; if (Object.prototype.toString.call(obj_here) == "[object Object]") { obj_here = Object.keys(obj_here); } if (Object.prototype.toString.call(obj_here) == "[object Array]") { for (n=0; n < obj_here.length; n++) { obj = RUR.THINGS[obj_here[n]]; if (obj === undefined) { continue; } else if (obj.choose_image !== undefined){ remove_flag = _draw_single_animated(obj, coords[k], i, j, ctx, obj.x_offset, obj.y_offset); if (remove_flag == RUR.END_CYCLE) { images_to_remove.push([i, j, obj.name, ctx]); } flag = true; } } } else { console.warn("Problem: unknown type in draw_anim; canvas =", ctx.canvas); console.warn("obj_here = ", obj_here, "objects = ", objects); } } for (k=0; k < images_to_remove.length; k++){ // removing object normally result in the recording of a // frame since we normally want the display to be updated // to reflect the removal. Here, we are updating the display, // and we do not want to trigger new frames recording: at this // stage, we are playing back the recorded frames. recording_state = RUR.state.do_not_record; RUR.state.do_not_record = true; __remove_animated_object(images_to_remove[k]); RUR.state.do_not_record = false; } return flag; } function __remove_animated_object(args) { var x, y, name, ctx; x = args[0]; y = args[1]; name = args[2]; ctx = args[3]; switch (ctx) { case RUR.TILES_ANIM_CTX: RUR.remove_background_tile(name, x, y); break; case RUR.OBSTACLES_ANIM_CTX: RUR.remove_obstacle(name, x, y); break; default: console.warn("unknown ctx in __remove_animated_object."); } } function _draw_single_animated (obj, coords, i, j, ctx, x_offset, y_offset){ var image, id = coords + ctx.canvas.id + obj.name; // each image is uniquely identified by its "id". image = obj.choose_image(id); if (image === undefined){ console.warn("problem in _draw_single_animated; obj =", obj); throw new RUR.ReeborgError("Problem in _draw_single_animated at" + coords); } else if (image == RUR.END_CYCLE) { return RUR.END_CYCLE; } draw_single_object(image, i, j, ctx, x_offset, y_offset); return false; } function draw_coloured_tile (colour, i, j, ctx) { var thick = RUR.WALL_THICKNESS, grid_size = RUR.WALL_LENGTH; var x, y; if (i > RUR.MAX_X || j > RUR.MAX_Y){ return; } x = i*grid_size + thick/2; y = RUR.HEIGHT - (j+1)*grid_size + thick/2; ctx.fillStyle = colour; ctx.fillRect(x, y, grid_size, grid_size); } function draw_single_object (image, i, j, ctx, x_offset, y_offset) { var x, y, offset=RUR.WALL_THICKNESS/2, grid_size=RUR.WALL_LENGTH, world = RUR.get_current_world(); if (x_offset === undefined) { x_offset = 0; } if (y_offset === undefined) { y_offset = 0; } x_offset *= RUR.SCALE; y_offset *= RUR.SCALE; x = i*grid_size + offset + x_offset; y = RUR.HEIGHT - (j+1)*grid_size + offset + y_offset; try{ if (RUR.SCALE == 0.5) { ctx.drawImage(image, x, y, image.width/2, image.height/2); } else { ctx.drawImage(image, x, y); } } catch (e) { console.warn("problem in draw_single_object", image, ctx, e); } } function draw_background_image (image) { // we draw the image so that it fills the entire world var thick=RUR.WALL_THICKNESS/2, grid_size=RUR.WALL_LENGTH, image_width, image_height, world_width, world_height, x, y, ctx=RUR.BACKGROUND_CTX; world_width = RUR.MAX_X*grid_size; // space to world_height = RUR.MAX_Y*grid_size; // be filled image_width = image.width; image_height = image.height; if (image_width > world_width) { image_width = world_width; // crop } if (image_height > world_height) { image_height = world_height; } y = RUR.HEIGHT - (RUR.MAX_Y+1)*grid_size + thick; // location of top ... x = grid_size + thick; // ... left corner try{ ctx.drawImage(image, 0, 0, image_width, image_height, x, y, world_width, world_height); } catch (e) { console.warn("problem in draw_background_image", image, ctx, e); } } function compile_info () { // compiles the information about objects and goal found at each // grid location, so that we can determine what should be // drawn - if anything. var coords, obj, quantity, world = RUR.get_current_world(); RUR.vis_world.information = {}; RUR.vis_world.goal_information = {}; RUR.vis_world.goal_present = false; if (world.goal !== undefined && world.goal.objects !== undefined) { compile_partial_info(RUR.get_current_world().goal.objects, RUR.vis_world.goal_information, 'goal'); RUR.vis_world.goal_present = true; } if (world.objects !== undefined) { compile_partial_info(world.objects, RUR.vis_world.information, 'objects'); } } function compile_partial_info (objects, information, type){ "use strict"; var coords, obj, quantity, color, goal_information; if (type=="objects") { color = "black"; goal_information = RUR.vis_world.goal_information; } else { color = "blue"; } for (coords in objects) { if (objects.hasOwnProperty(coords)){ // objects found here for(obj in objects[coords]){ if (objects[coords].hasOwnProperty(obj)){ if (information[coords] !== undefined){ // already at least one other object there information[coords] = [undefined, "?"]; // assign impossible object } else { quantity = objects[coords][obj]; if (quantity.toString().indexOf("-") != -1) { quantity = "?"; } else if (quantity == "all") { quantity = "?"; } else { try{ quantity = parseInt(quantity, 10); } catch (e) { quantity = "?"; console.warn("WARNING: this should not happen in compile_info"); } } if (RUR.vis_world.goal_present && typeof quantity == 'number' && goal_information !== undefined) { if ( goal_information[coords] !== undefined && goal_information[coords][1] == objects[coords][obj]) { information[coords] = [obj, objects[coords][obj], RUR.GREEN]; } else { information[coords] = [obj, objects[coords][obj], RUR.RED]; } } else { information[coords] = [obj, quantity, color]; } } } } } } } function draw_info () { var i, j, coords, keys, key, info, ctx; var scale = RUR.WALL_LENGTH, Y = RUR.HEIGHT, text_width; if (RUR.state.do_not_draw_info) { return; } compile_info(); if (RUR.vis_world.information === undefined && RUR.vis_world.goal_information === undefined) { return; } // make sure it appears on top of everything (except possibly robots) ctx = RUR.ROBOT_CTX; ctx.font = RUR.BACKGROUND_CTX.font; if (RUR.vis_world.information !== undefined) { keys = Object.keys(RUR.vis_world.information); for (key=0; key < keys.length; key++){ coords = keys[key].split(","); i = parseInt(coords[0], 10); j = parseInt(coords[1], 10); info = RUR.vis_world.information[coords][1]; if (i <= RUR.MAX_X && j <= RUR.MAX_Y){ text_width = ctx.measureText(info).width/2; ctx.fillStyle = RUR.vis_world.information[coords][2]; // information drawn to left side of object ctx.fillText(info, (i+0.2)*scale, Y - (j)*scale); } } } if (RUR.vis_world.goal_information !== undefined) { keys = Object.keys(RUR.vis_world.goal_information); for (key=0; key < keys.length; key++){ coords = keys[key].split(","); i = parseInt(coords[0], 10); j = parseInt(coords[1], 10); info = RUR.vis_world.goal_information[coords][1]; if (i <= RUR.MAX_X && j <= RUR.MAX_Y){ text_width = ctx.measureText(info).width/2; ctx.fillStyle = RUR.vis_world.goal_information[coords][2]; // information drawn to right side of object ctx.fillText(info, (i+0.8)*scale, Y - (j)*scale); } } } }