diff --git a/BaseExercices/task_4_ennemies.zip b/BaseExercices/task_4_ennemies.zip new file mode 100644 index 0000000..0b8c118 Binary files /dev/null and b/BaseExercices/task_4_ennemies.zip differ diff --git a/BaseExercices/task_app0.zip b/BaseExercices/task_app0.zip new file mode 100644 index 0000000..fa9897f Binary files /dev/null and b/BaseExercices/task_app0.zip differ diff --git a/BaseExercices/task_artiste.zip b/BaseExercices/task_artiste.zip new file mode 100644 index 0000000..5c3dc19 Binary files /dev/null and b/BaseExercices/task_artiste.zip differ diff --git a/BaseExercices/task_bee.zip b/BaseExercices/task_bee.zip new file mode 100644 index 0000000..41ad826 Binary files /dev/null and b/BaseExercices/task_bee.zip differ diff --git a/BaseExercices/task_pegman.zip b/BaseExercices/task_pegman.zip new file mode 100644 index 0000000..75f134e Binary files /dev/null and b/BaseExercices/task_pegman.zip differ diff --git a/BaseExercices/task_pvz.zip b/BaseExercices/task_pvz.zip new file mode 100644 index 0000000..6054416 Binary files /dev/null and b/BaseExercices/task_pvz.zip differ diff --git a/Cours 1/Lecon2/Artist_1/public/blocks.js b/Cours 1 Code.org/Lecon 11/Artist_01/public/blocks.js similarity index 100% rename from Cours 1/Lecon2/Artist_1/public/blocks.js rename to Cours 1 Code.org/Lecon 11/Artist_01/public/blocks.js diff --git a/Cours 1 Code.org/Lecon 11/Artist_01/public/create_img.py b/Cours 1 Code.org/Lecon 11/Artist_01/public/create_img.py new file mode 100644 index 0000000..e61067b --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_01/public/create_img.py @@ -0,0 +1,102 @@ +import Image, ImageDraw +import math +import json +import random +import os + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y - length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y + length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for i in range(3): + turnLeft(120) + for j in range(3): + moveForward(100) + turnLeft(120) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1/Lecon2/Artist_1/public/interpreter.js b/Cours 1 Code.org/Lecon 11/Artist_01/public/interpreter.js similarity index 100% rename from Cours 1/Lecon2/Artist_1/public/interpreter.js rename to Cours 1 Code.org/Lecon 11/Artist_01/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 11/Artist_01/public/solution.png b/Cours 1 Code.org/Lecon 11/Artist_01/public/solution.png new file mode 100644 index 0000000..fde0b08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 11/Artist_01/public/solution.png differ diff --git a/Cours 1 Code.org/Lecon 11/Artist_01/public/turtle.js b/Cours 1 Code.org/Lecon 11/Artist_01/public/turtle.js new file mode 100644 index 0000000..da68c5c --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_01/public/turtle.js @@ -0,0 +1,390 @@ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Turtle = {}; +window.Maze = {}; + +//Get the json file and its informations +var turtle_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + turtle_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"turtle_config.json" +}else { //When displaying the task + turtle_file = task_directory_path + "turtle_config.json"; +} +var request = new XMLHttpRequest(); +request.open("GET", turtle_file, false); +request.send(null) +var json = JSON.parse(request.responseText); + +var imagePath = "" +if(json.imageSolution) + imagePath = task_directory_path+json.imageName; + +//Code of the solution +var solution = function(){ + //Here, put the javascript corresponding to the solved exercice +} +var decoration = function(){ + //Here, put the code for any decor, not part of the exercice +} + +var randomColour = function(){ + var colour = Math.floor(Math.random()*16777215); + return "#"+colour.toString(16).toUpperCase() +} + +//Canvas size +Turtle.CANVAS_WIDTH = json.width; +Turtle.CANVAS_HEIGHT = json.height; + +//Starting position and radius of turtle +Turtle.START_X = json.startX; +Turtle.START_Y = json.startY; +Turtle.RADIUS = json.radius; + +//Current coordinates of turtle +Turtle.CURRENT_COORD = { + x:Turtle.START_X, + y:Turtle.START_Y +} + +//Current heading of turtle +Turtle.HEADING = json.startAngle; + +//Weater or not the pen is down and it's width +Turtle.PEN_DOWN = true; //At start, the pen is always down +Turtle.PEN_WIDTH = json.strokeWidth; +Turtle.PEN_COLOUR = json.strokeColour; + +//Variables used to draw on the solution canvas or the decor canvas +var sol = false; +var decor = false; + +//milisec between each frame +window.stepSpeed = json.animationRate; + +/** +* +* Animations functions +* +*/ + +Turtle.drawMap = function() { + var div = document.getElementById('visualization'); + + // Add the canvas for the turtle + var canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute("style", "border:1px solid #F1EEE7;") + canvas.setAttribute("id", "turtle-canvas"); + div.appendChild(canvas); + // Add the canvas for the user. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "user-canvas"); + div.appendChild(canvas); + // Add the canvas for the solution. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "solution-canvas"); + div.appendChild(canvas); + //Add the canvas for the decor + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none') + canvas.setAttribute("id", "decor-canvas"); + div.appendChild(canvas) + + //Draw the decor + Turtle.drawDecor(); + + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); + + //Draw the turtle + Turtle.updateImage(); + + +} + +Turtle.drawTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + + //Draw the turtle body + ctx.beginPath(); + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, Turtle.RADIUS, 0, 2 * Math.PI); + ctx.stroke(); + + // Draw the turtle head. + var WIDTH = 0.3; + var HEAD_TIP = 10; + var ARROW_TIP = 4; + var BEND = 6; + var radians = 2 * Math.PI * Turtle.HEADING / 360; + var tipX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + HEAD_TIP) * Math.sin(radians); + var tipY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + HEAD_TIP) * Math.cos(radians); + radians -= WIDTH; + var leftX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var leftY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + radians += WIDTH / 2; + var leftControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var leftControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH; + var rightControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var rightControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH / 2; + var rightX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var rightY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + + ctx.beginPath(); + ctx.moveTo(tipX, tipY); + ctx.lineTo(leftX, leftY); + ctx.bezierCurveTo(leftControlX, leftControlY, + rightControlX, rightControlY, rightX, rightY); + ctx.closePath(); + ctx.fillStyle = Turtle.PEN_COLOUR; + ctx.fill(); +} + +Turtle.resetTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + //Clear any previous turtle + ctx.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + +} + +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } + +} + + +Turtle.drawSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + sol = true; + solution(); + sol = false; + Turtle.reset(true); +} + +Turtle.drawDecor = function(){ + var c = document.getElementById("decor-canvas"); + var ctx = c.getContext("2d") + decor = true; + decoration(); + decor = false; + Turtle.reset(true); +} + +Turtle.animate = function(id) { + switch(id){ + case "move" : + Turtle.updateImage(); + break; + case "turn" : + Turtle.updateImage(); + break; + case "colour": + Turtle.updateImage(); + break; + default: + //This should not happen + console.warn("Unknown animation"); + break; + } +} + +Turtle.updateImage = function(){ + Turtle.resetTurtle(); + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d") + var c2 = document.getElementById("user-canvas"); + var c3 = document.getElementById("solution-canvas"); + var c4 = document.getElementById("decor-canvas"); + ctx1.drawImage(c4, 0, 0); //Fuse any decor + ctx1.drawImage(c3, 0, 0); //Fuse solution canvas + ctx1.drawImage(c2, 0, 0); //Fuse user canvas + Turtle.drawTurtle(); //Add the turtle +} + +Turtle.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Turtle.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + } + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,penUp,penDown,penWidth,penColour,'); + + Turtle.drawMap(); +}; + +Turtle.reset = function(bool){ + if(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + } + Turtle.updateImage(); +} + +//Workaround current plugin implementation that uses Maze as a variable +Maze.reset = function(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + if(!bool){ + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d"); + var c2 = document.getElementById("user-canvas"); + var ctx2 = c2.getContext("2d"); + ctx1.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + ctx2.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + } + Turtle.updateImage(); +} + +/** +* +* Blocks functions +* +*/ +Turtle.move = function(length){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + if (Turtle.PEN_DOWN) { + ctx.beginPath(); + ctx.moveTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + } + if(length){ + Turtle.CURRENT_COORD.x += length * Math.sin(2 * Math.PI * Turtle.HEADING / 360); + Turtle.CURRENT_COORD.y -= length * Math.cos(2 * Math.PI * Turtle.HEADING / 360); + } + if(Turtle.PEN_DOWN){ + ctx.lineWidth = Turtle.PEN_WIDTH; + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.lineTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + ctx.stroke(); + } + Turtle.animate("move"); +} + +Turtle.moveForward = function(length){ + Turtle.move(length); +} + + +Turtle.moveBackward = function(length){ + Turtle.move(-length); +} + +Turtle.jumpForward = function(length){ + Turtle.penUp(); + Turtle.moveForward(length); + Turtle.penDown(); +} + +Turtle.jumpBackward = function(length){ + Turtle.penUp(); + Turtle.moveBackward(length); + Turtle.penDown(); +} + +Turtle.circle = function(radius){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + ctx.beginPath(); + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, radius, 0,2*Math.PI); + ctx.stroke(); + Turtle.updateImage(); +} + +Turtle.turn = function(angle, direction){ + switch (direction){ + case 0: + Turtle.HEADING = (Turtle.HEADING - angle) % 360; + if (Turtle.HEADING < 0) { + Turtle.HEADING += 360; + } + break; + case 1: + Turtle.HEADING = (Turtle.HEADING + angle) % 360; + break; + } + Turtle.animate("turn"); +} + +Turtle.turnRight = function(angle){ + Turtle.turn(angle, 1); +} + +Turtle.turnLeft = function(angle){ + Turtle.turn(angle, 0); +} + +Turtle.penWidth = function(width){ + Turtle.PEN_WIDTH = width; +} + +Turtle.penUp = function(){ + Turtle.PEN_DOWN = false; +} + +Turtle.penDown = function(){ + Turtle.PEN_DOWN = true; +} + +Turtle.penColour = function(colour){ + Turtle.PEN_COLOUR = colour; + Turtle.animate("colour"); +} + +//Called to draw +if (document.getElementById('visualization') != null) { + window.addEventListener('load', Turtle.init); +} else { + console.warn('Cannot find visualization element.'); +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 11/Artist_01/public/turtle_config.json b/Cours 1 Code.org/Lecon 11/Artist_01/public/turtle_config.json new file mode 100644 index 0000000..e434a71 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_01/public/turtle_config.json @@ -0,0 +1,14 @@ +{ + "startX":150, + "startY":150, + "startAngle":270, + "strokeWidth":3, + "strokeColour":"#000000", + "colourSpecific":false, + "radius":15, + "animationRate":50, + "width":290, + "height":290, + "imageSolution":true, + "imageName":"solution.png" +} \ No newline at end of file diff --git a/Cours 1/Lecon2/Artist_1/run b/Cours 1 Code.org/Lecon 11/Artist_01/run similarity index 100% rename from Cours 1/Lecon2/Artist_1/run rename to Cours 1 Code.org/Lecon 11/Artist_01/run diff --git a/Cours 1 Code.org/Lecon 11/Artist_01/student/turtle.py b/Cours 1 Code.org/Lecon 11/Artist_01/student/turtle.py new file mode 100644 index 0000000..f99147f --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_01/student/turtle.py @@ -0,0 +1,114 @@ +import math +import json +import os + +#Reset the turtle variables to starting position +def resetTurtle(): + Turtle["x"] = start_x + Turtle["y"] = start_y + Turtle["heading"] = start_heading + Turtle["colour"] = start_pen_colour + Turtle["width"] = start_pen_width + Turtle["penDown"] = True + + +def student_code(): +@ @code@@ + +#Code of the solution +def solution(): + for i in range(3): + turnLeft(120) + for j in range(3): + moveForward(100) + turnLeft(120) + +# +# Code to "draw" +# + +# Format stored in the dictionary : +# Point x - Point y (as int) : strokewidth strokeColour + +def moveForward(length): + addPoint() + for i in range(length): + Turtle["x"] += int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] -= int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def moveBackward(length): + addPoint() + for i in range(length): + Turtle["x"] -= int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] += int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def addPoint(): + if(sol): # We are computing the solution + if(count_colours): # Colour is important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: # Colour is not important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + else: # We are computing the student output + if(count_colours): + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + +def turnRight(angle): + Turtle["heading"] = (Turtle["heading"] + angle) % 360; + +def turnLeft(angle): + Turtle["heading"] = (Turtle["heading"] - angle) % 360; + if (Turtle["heading"] < 0): + Turtle["heading"] += 360 + +def penWidth(width): + Turtle["width"] = width + +def penUp(): + Turtle["penDown"] = False + +def penDown(): + Turtle["penDown"] = True + +def penColour(colour): + Turtle["colour"] = colour + +# Get the json data +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'turtle_config.json') as f: + data = json.load(f) + +# Dictionaries to compare +sol_output = {} +user_output = {} + +# Variables to start +start_x = data["startX"] +start_y = data["startY"] +start_heading = data["startAngle"] +start_pen_width = data["strokeWidth"] +start_pen_colour = data["strokeColour"] + +count_colours = data["colourSpecific"] + +Turtle = {} # Current turtle state is stocked here +resetTurtle() # Set the variable +sol = True # We will execute the solution first. Save starting point +sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) +solution() # Execute the solution +resetTurtle() # Reset the turtle again +sol = False # We are no longer executing the solution +user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) +student_code() # Execute the student code + +if(user_output == sol_output): #If the dicts are the same, success + print("True", end='', flush=True) +else: + + print("Votre solution est incorrecte, essayez encore", end='', flush=True) \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 11/Artist_01/task.yaml b/Cours 1 Code.org/Lecon 11/Artist_01/task.yaml new file mode 100644 index 0000000..eb4af57 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_01/task.yaml @@ -0,0 +1,133 @@ +accessible: true +author: Celine Deknop +context: |- + Termine le code pour dessiner ces triangles. Le code pour dessiner un triangle est déjà fait pour toi. + Indice : les 3 triangles sont dans une rotation de 360 degrés. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + output: '2' + memory: '100' + time: '30' +name: Exercice 1 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: '45' + files: + - turtle.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + + + 3 + + + + + moveForward + 100 + + + turnLeft + 120 + + + + + + + toolbox: |- + + + + + + moveForward + 100 + + + turnRight + 90 + + + turnLeft + 90 + + + + + + + + 0 + + + + + + + + + + + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + visible: true + name: Boucles imbriquées + description: '' + id: '1' + '1': + description: Exercice faisant partie de la leçon 11 + type: 2 + visible: true + name: Lecon 11 + id: '' + '2': + description: Faisant partie du parcours normal + name: Normal + type: 2 + visible: false + id: '' + '3': + name: Facile + description: Faisant partie du parcours facile + type: 2 + visible: false + id: '' + '4': + type: 2 + description: Faisant partie du parcours challenge + name: Challenge + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1/Lecon2/Artist_2/public/blocks.js b/Cours 1 Code.org/Lecon 11/Artist_02/public/blocks.js similarity index 100% rename from Cours 1/Lecon2/Artist_2/public/blocks.js rename to Cours 1 Code.org/Lecon 11/Artist_02/public/blocks.js diff --git a/Cours 1 Code.org/Lecon 11/Artist_02/public/create_img.py b/Cours 1 Code.org/Lecon 11/Artist_02/public/create_img.py new file mode 100644 index 0000000..2411386 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_02/public/create_img.py @@ -0,0 +1,102 @@ +import Image, ImageDraw +import math +import json +import random +import os + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y - length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y + length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for i in range(10): + turnRight(36) + penColour(randomColour()) + for j in range(3): + moveForward(100) + turnLeft(120) +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1/Lecon2/Artist_2/public/interpreter.js b/Cours 1 Code.org/Lecon 11/Artist_02/public/interpreter.js similarity index 100% rename from Cours 1/Lecon2/Artist_2/public/interpreter.js rename to Cours 1 Code.org/Lecon 11/Artist_02/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 11/Artist_02/public/solution.png b/Cours 1 Code.org/Lecon 11/Artist_02/public/solution.png new file mode 100644 index 0000000..79785ca Binary files /dev/null and b/Cours 1 Code.org/Lecon 11/Artist_02/public/solution.png differ diff --git a/Cours 1 Code.org/Lecon 11/Artist_02/public/turtle.js b/Cours 1 Code.org/Lecon 11/Artist_02/public/turtle.js new file mode 100644 index 0000000..da68c5c --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_02/public/turtle.js @@ -0,0 +1,390 @@ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Turtle = {}; +window.Maze = {}; + +//Get the json file and its informations +var turtle_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + turtle_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"turtle_config.json" +}else { //When displaying the task + turtle_file = task_directory_path + "turtle_config.json"; +} +var request = new XMLHttpRequest(); +request.open("GET", turtle_file, false); +request.send(null) +var json = JSON.parse(request.responseText); + +var imagePath = "" +if(json.imageSolution) + imagePath = task_directory_path+json.imageName; + +//Code of the solution +var solution = function(){ + //Here, put the javascript corresponding to the solved exercice +} +var decoration = function(){ + //Here, put the code for any decor, not part of the exercice +} + +var randomColour = function(){ + var colour = Math.floor(Math.random()*16777215); + return "#"+colour.toString(16).toUpperCase() +} + +//Canvas size +Turtle.CANVAS_WIDTH = json.width; +Turtle.CANVAS_HEIGHT = json.height; + +//Starting position and radius of turtle +Turtle.START_X = json.startX; +Turtle.START_Y = json.startY; +Turtle.RADIUS = json.radius; + +//Current coordinates of turtle +Turtle.CURRENT_COORD = { + x:Turtle.START_X, + y:Turtle.START_Y +} + +//Current heading of turtle +Turtle.HEADING = json.startAngle; + +//Weater or not the pen is down and it's width +Turtle.PEN_DOWN = true; //At start, the pen is always down +Turtle.PEN_WIDTH = json.strokeWidth; +Turtle.PEN_COLOUR = json.strokeColour; + +//Variables used to draw on the solution canvas or the decor canvas +var sol = false; +var decor = false; + +//milisec between each frame +window.stepSpeed = json.animationRate; + +/** +* +* Animations functions +* +*/ + +Turtle.drawMap = function() { + var div = document.getElementById('visualization'); + + // Add the canvas for the turtle + var canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute("style", "border:1px solid #F1EEE7;") + canvas.setAttribute("id", "turtle-canvas"); + div.appendChild(canvas); + // Add the canvas for the user. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "user-canvas"); + div.appendChild(canvas); + // Add the canvas for the solution. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "solution-canvas"); + div.appendChild(canvas); + //Add the canvas for the decor + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none') + canvas.setAttribute("id", "decor-canvas"); + div.appendChild(canvas) + + //Draw the decor + Turtle.drawDecor(); + + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); + + //Draw the turtle + Turtle.updateImage(); + + +} + +Turtle.drawTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + + //Draw the turtle body + ctx.beginPath(); + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, Turtle.RADIUS, 0, 2 * Math.PI); + ctx.stroke(); + + // Draw the turtle head. + var WIDTH = 0.3; + var HEAD_TIP = 10; + var ARROW_TIP = 4; + var BEND = 6; + var radians = 2 * Math.PI * Turtle.HEADING / 360; + var tipX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + HEAD_TIP) * Math.sin(radians); + var tipY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + HEAD_TIP) * Math.cos(radians); + radians -= WIDTH; + var leftX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var leftY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + radians += WIDTH / 2; + var leftControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var leftControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH; + var rightControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var rightControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH / 2; + var rightX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var rightY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + + ctx.beginPath(); + ctx.moveTo(tipX, tipY); + ctx.lineTo(leftX, leftY); + ctx.bezierCurveTo(leftControlX, leftControlY, + rightControlX, rightControlY, rightX, rightY); + ctx.closePath(); + ctx.fillStyle = Turtle.PEN_COLOUR; + ctx.fill(); +} + +Turtle.resetTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + //Clear any previous turtle + ctx.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + +} + +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } + +} + + +Turtle.drawSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + sol = true; + solution(); + sol = false; + Turtle.reset(true); +} + +Turtle.drawDecor = function(){ + var c = document.getElementById("decor-canvas"); + var ctx = c.getContext("2d") + decor = true; + decoration(); + decor = false; + Turtle.reset(true); +} + +Turtle.animate = function(id) { + switch(id){ + case "move" : + Turtle.updateImage(); + break; + case "turn" : + Turtle.updateImage(); + break; + case "colour": + Turtle.updateImage(); + break; + default: + //This should not happen + console.warn("Unknown animation"); + break; + } +} + +Turtle.updateImage = function(){ + Turtle.resetTurtle(); + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d") + var c2 = document.getElementById("user-canvas"); + var c3 = document.getElementById("solution-canvas"); + var c4 = document.getElementById("decor-canvas"); + ctx1.drawImage(c4, 0, 0); //Fuse any decor + ctx1.drawImage(c3, 0, 0); //Fuse solution canvas + ctx1.drawImage(c2, 0, 0); //Fuse user canvas + Turtle.drawTurtle(); //Add the turtle +} + +Turtle.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Turtle.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + } + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,penUp,penDown,penWidth,penColour,'); + + Turtle.drawMap(); +}; + +Turtle.reset = function(bool){ + if(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + } + Turtle.updateImage(); +} + +//Workaround current plugin implementation that uses Maze as a variable +Maze.reset = function(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + if(!bool){ + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d"); + var c2 = document.getElementById("user-canvas"); + var ctx2 = c2.getContext("2d"); + ctx1.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + ctx2.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + } + Turtle.updateImage(); +} + +/** +* +* Blocks functions +* +*/ +Turtle.move = function(length){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + if (Turtle.PEN_DOWN) { + ctx.beginPath(); + ctx.moveTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + } + if(length){ + Turtle.CURRENT_COORD.x += length * Math.sin(2 * Math.PI * Turtle.HEADING / 360); + Turtle.CURRENT_COORD.y -= length * Math.cos(2 * Math.PI * Turtle.HEADING / 360); + } + if(Turtle.PEN_DOWN){ + ctx.lineWidth = Turtle.PEN_WIDTH; + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.lineTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + ctx.stroke(); + } + Turtle.animate("move"); +} + +Turtle.moveForward = function(length){ + Turtle.move(length); +} + + +Turtle.moveBackward = function(length){ + Turtle.move(-length); +} + +Turtle.jumpForward = function(length){ + Turtle.penUp(); + Turtle.moveForward(length); + Turtle.penDown(); +} + +Turtle.jumpBackward = function(length){ + Turtle.penUp(); + Turtle.moveBackward(length); + Turtle.penDown(); +} + +Turtle.circle = function(radius){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + ctx.beginPath(); + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, radius, 0,2*Math.PI); + ctx.stroke(); + Turtle.updateImage(); +} + +Turtle.turn = function(angle, direction){ + switch (direction){ + case 0: + Turtle.HEADING = (Turtle.HEADING - angle) % 360; + if (Turtle.HEADING < 0) { + Turtle.HEADING += 360; + } + break; + case 1: + Turtle.HEADING = (Turtle.HEADING + angle) % 360; + break; + } + Turtle.animate("turn"); +} + +Turtle.turnRight = function(angle){ + Turtle.turn(angle, 1); +} + +Turtle.turnLeft = function(angle){ + Turtle.turn(angle, 0); +} + +Turtle.penWidth = function(width){ + Turtle.PEN_WIDTH = width; +} + +Turtle.penUp = function(){ + Turtle.PEN_DOWN = false; +} + +Turtle.penDown = function(){ + Turtle.PEN_DOWN = true; +} + +Turtle.penColour = function(colour){ + Turtle.PEN_COLOUR = colour; + Turtle.animate("colour"); +} + +//Called to draw +if (document.getElementById('visualization') != null) { + window.addEventListener('load', Turtle.init); +} else { + console.warn('Cannot find visualization element.'); +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 11/Artist_02/public/turtle_config.json b/Cours 1 Code.org/Lecon 11/Artist_02/public/turtle_config.json new file mode 100644 index 0000000..e434a71 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_02/public/turtle_config.json @@ -0,0 +1,14 @@ +{ + "startX":150, + "startY":150, + "startAngle":270, + "strokeWidth":3, + "strokeColour":"#000000", + "colourSpecific":false, + "radius":15, + "animationRate":50, + "width":290, + "height":290, + "imageSolution":true, + "imageName":"solution.png" +} \ No newline at end of file diff --git a/Cours 1/Lecon2/Artist_2/run b/Cours 1 Code.org/Lecon 11/Artist_02/run similarity index 100% rename from Cours 1/Lecon2/Artist_2/run rename to Cours 1 Code.org/Lecon 11/Artist_02/run diff --git a/Cours 1 Code.org/Lecon 11/Artist_02/student/turtle.py b/Cours 1 Code.org/Lecon 11/Artist_02/student/turtle.py new file mode 100644 index 0000000..c364866 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_02/student/turtle.py @@ -0,0 +1,119 @@ +import math +import json +import os +import random + +#Reset the turtle variables to starting position +def resetTurtle(): + Turtle["x"] = start_x + Turtle["y"] = start_y + Turtle["heading"] = start_heading + Turtle["colour"] = start_pen_colour + Turtle["width"] = start_pen_width + Turtle["penDown"] = True + + +def student_code(): +@ @code@@ + +#Code of the solution +def solution(): + for i in range(10): + penColour(randomColour()) + for j in range(3): + moveForward(100) + turnLeft(120) + turnRight(36) + +#Generates a random colour +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +# +# Code to "draw" +# + +# Format stored in the dictionary : +# Point x - Point y (as int) : strokewidth strokeColour + +def moveForward(length): + addPoint() + for i in range(length): + Turtle["x"] += int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] -= int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def moveBackward(length): + addPoint() + for i in range(length): + Turtle["x"] -= int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] += int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def addPoint(): + if(sol): # We are computing the solution + if(count_colours): # Colour is important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: # Colour is not important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + else: # We are computing the student output + if(count_colours): + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + +def turnRight(angle): + Turtle["heading"] = (Turtle["heading"] + angle) % 360; + +def turnLeft(angle): + Turtle["heading"] = (Turtle["heading"] - angle) % 360; + if (Turtle["heading"] < 0): + Turtle["heading"] += 360 + +def penWidth(width): + Turtle["width"] = width + +def penUp(): + Turtle["penDown"] = False + +def penDown(): + Turtle["penDown"] = True + +def penColour(colour): + Turtle["colour"] = colour + +# Get the json data +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'turtle_config.json') as f: + data = json.load(f) + +# Dictionaries to compare +sol_output = {} +user_output = {} + +# Variables to start +start_x = data["startX"] +start_y = data["startY"] +start_heading = data["startAngle"] +start_pen_width = data["strokeWidth"] +start_pen_colour = data["strokeColour"] + +count_colours = data["colourSpecific"] + +Turtle = {} # Current turtle state is stocked here +resetTurtle() # Set the variable +sol = True # We will execute the solution first. +solution() # Execute the solution +resetTurtle() # Reset the turtle again +sol = False # We are no longer executing the solution +student_code() # Execute the student code + +if(user_output == sol_output): #If the dicts are the same, success + print("True", end='', flush=True) +else: + + print("Votre solution est incorrecte, essayez encore", end='', flush=True) \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 11/Artist_02/task.yaml b/Cours 1 Code.org/Lecon 11/Artist_02/task.yaml new file mode 100644 index 0000000..a75c284 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_02/task.yaml @@ -0,0 +1,150 @@ +accessible: true +author: Celine Deknop +context: |- + Maintenant imbrique cette boucle à l'intérieur d'une autre boucle pour dessiner 10 triangles. C'est ce qu'on appelle une boucle imbriquée. + Indice : les 10 triangles sont toujours dans une rotation de 360 degrés. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + output: '2' + memory: '100' + time: '30' +name: Exercice 2 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: '45' + files: + - turtle.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + + + + + + + + 3 + + + + + moveForward + 100 + + + turnLeft + 120 + + + + + + + turnRight + + + 36 + + + + + + + + + toolbox: |- + + + + + + moveForward + 100 + + + turnRight + 90 + + + turnLeft + 90 + + + + + + + + 0 + + + + + + + + + + + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + visible: true + name: Boucles imbriquées + description: '' + id: '1' + '1': + description: Exercice faisant partie de la leçon 11 + type: 2 + visible: true + name: Lecon 11 + id: '' + '2': + description: Faisant partie du parcours normal + name: Normal + type: 2 + visible: false + id: '' + '3': + name: Facile + description: Faisant partie du parcours facile + type: 2 + visible: false + id: '' + '4': + type: 2 + description: Faisant partie du parcours challenge + name: Challenge + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 11/Artist_03/public/blocks.js b/Cours 1 Code.org/Lecon 11/Artist_03/public/blocks.js new file mode 100644 index 0000000..ee4c264 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_03/public/blocks.js @@ -0,0 +1,365 @@ +/** + * Blockly Games: Turtle Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Turtle application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Turtle.Blocks = {}; + +/** + * Common HSV hue for all blocks in this category. + */ +Turtle.Blocks.HUE = 160; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['turtle_move'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + this.appendValueInput("VALUE") + .setCheck('Number') + .appendField(new Blockly.FieldDropdown( + [ + ["avancer de","moveForward"], + ["reculer de","moveBackward"]] + ), "DIR"); + this.appendDummyInput() + .appendField("pixels"); + this.setColour(Turtle.Blocks.HUE); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setInputsInline(true); + this.setTooltip("Bouger"); + } +}; + +Blockly.JavaScript['turtle_move'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_move'] = function(block) { + // Generate Python for moving forward or backwards. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_move_internal'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['avancer de', 'moveForward'], + ['reculer de', 'moveBackward']]; + var VALUES = + [['20 pixels', '20'], + ['50 pixels', '50'], + ['100 pixels', '100'], + ['150 pixels', '150']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Bouger'); + } +}; + +Blockly.JavaScript['turtle_move_internal'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ');\n'; +}; + +Blockly.Python['turtle_move_internal'] = function(block) { + // Generate Python for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite', 'turnRight'], + ['tourner à gauche', 'turnLeft']]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Turtle.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Turtle.Blocks.LEFT_TURN; + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('VALUE') + .setCheck('Number') + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourner"); + } +}; + +Blockly.JavaScript['turtle_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn'] = function(block) { + // Generate Python for turning left or right. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.Python.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_turn_internal'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite de', 'turnRight'], + ['tourner à gauche de', 'turnLeft']]; + var VALUES = + [['1\u00B0', '1'], + ['30\u00B0', '30'], + ['45\u00B0', '45'], + ['72\u00B0', '72'], + ['90\u00B0', '90'], + ['120\u00B0', '120'], + ['144\u00B0', '144']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Tourner de X degrés'); + } +}; + +Blockly.JavaScript['turtle_turn_internal'] = function(block) { + // Generate JavaScript for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn_internal'] = function(block) { + // Generate Python for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_width'] = { + /** + * Block for setting the width. + * @this Blockly.Block + */ + init: function() { + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('WIDTH') + .setCheck('Number') + .appendField('Définis l\'épaisseur de la ligne'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Définis l\'épaisseur de la ligne'); + } +}; + +Blockly.JavaScript['turtle_width'] = function(block) { + // Generate JavaScript for setting the width. + var width = Blockly.JavaScript.valueToCode(block, 'WIDTH', + Blockly.JavaScript.ORDER_NONE) || '1'; + return 'penWidth(' + width + ');\n'; +}; + +Blockly.Python['turtle_width'] = function(block) { + // Generate Python for setting the width. + var width = Blockly.Python.valueToCode(block, 'WIDTH', + Blockly.Python.ORDER_NONE) || '1'; + return 'penWidth(' + width + ')\n'; +}; + +Blockly.Blocks['turtle_pen'] = { + /** + * Block for pen up/down. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "%1", + "args0": [ + { + "type": "field_dropdown", + "name": "PEN", + "options": [ + ['lever le crayon', "penUp"], + ['poser le crayon', "penDown"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Turtle.Blocks.HUE, + "tooltip": "Lever ou poser le crayon sur la feuille" + }); + } +}; + +Blockly.JavaScript['turtle_pen'] = function(block) { + // Generate JavaScript for pen up/down. + return block.getFieldValue('PEN') + + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_pen'] = function(block) { + // Generate Python for pen up/down. + return block.getFieldValue('PEN') + + '()\n'; +}; + +Blockly.Blocks['turtle_colour'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendValueInput('COLOUR') + .setCheck('Colour') + .appendField('définis la couleur'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Changer la couleur du trait"); + } +}; + +Blockly.JavaScript['turtle_colour'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = Blockly.JavaScript.valueToCode(block, 'COLOUR', + Blockly.JavaScript.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour'] = function(block) { + // Generate Python for setting the colour. + var colour = Blockly.Python.valueToCode(block, 'COLOUR', + Blockly.Python.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ')\n'; +}; + +Blockly.Blocks['turtle_colour_internal'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendDummyInput() + .appendField('définis la couleur') + .appendField(new Blockly.FieldColour('#ff0000'), 'COLOUR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Changer la couleur du trait'); + } +}; + +Blockly.JavaScript['turtle_colour_internal'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour_internal'] = function(block) { + // Generate Python for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\')\n'; +}; + +Blockly.Blocks['turtle_repeat_internal'] = { + /** + * Block for repeat n times (internal number). + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.CONTROLS_REPEAT_TITLE, + "args0": [ + { + "type": "field_dropdown", + "name": "TIMES", + "options": [ + ["3", "3"], + ["4", "4"], + ["5", "5"], + ["360", "360"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Blockly.Blocks.loops.HUE, + "tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP, + "helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL + }); + this.appendStatementInput('DO') + .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO); + } +}; + +Blockly.JavaScript['turtle_repeat_internal'] = +Blockly.JavaScript['controls_repeat']; + +Blockly.Python['turtle_repeat_internal'] = +Blockly.Python['controls_repeat']; \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 11/Artist_03/public/create_img.py b/Cours 1 Code.org/Lecon 11/Artist_03/public/create_img.py new file mode 100644 index 0000000..6f47a53 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_03/public/create_img.py @@ -0,0 +1,102 @@ +import Image, ImageDraw +import math +import json +import random +import os + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y - length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y + length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for i in range(12): + for j in range(3): + moveForward(50) + turnLeft(120) + moveForward(50) + turnRight(30) +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1/Lecon2/Artist_3/public/interpreter.js b/Cours 1 Code.org/Lecon 11/Artist_03/public/interpreter.js similarity index 100% rename from Cours 1/Lecon2/Artist_3/public/interpreter.js rename to Cours 1 Code.org/Lecon 11/Artist_03/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 11/Artist_03/public/solution.png b/Cours 1 Code.org/Lecon 11/Artist_03/public/solution.png new file mode 100644 index 0000000..538fd7f Binary files /dev/null and b/Cours 1 Code.org/Lecon 11/Artist_03/public/solution.png differ diff --git a/Cours 1 Code.org/Lecon 11/Artist_03/public/turtle.js b/Cours 1 Code.org/Lecon 11/Artist_03/public/turtle.js new file mode 100644 index 0000000..da68c5c --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_03/public/turtle.js @@ -0,0 +1,390 @@ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Turtle = {}; +window.Maze = {}; + +//Get the json file and its informations +var turtle_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + turtle_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"turtle_config.json" +}else { //When displaying the task + turtle_file = task_directory_path + "turtle_config.json"; +} +var request = new XMLHttpRequest(); +request.open("GET", turtle_file, false); +request.send(null) +var json = JSON.parse(request.responseText); + +var imagePath = "" +if(json.imageSolution) + imagePath = task_directory_path+json.imageName; + +//Code of the solution +var solution = function(){ + //Here, put the javascript corresponding to the solved exercice +} +var decoration = function(){ + //Here, put the code for any decor, not part of the exercice +} + +var randomColour = function(){ + var colour = Math.floor(Math.random()*16777215); + return "#"+colour.toString(16).toUpperCase() +} + +//Canvas size +Turtle.CANVAS_WIDTH = json.width; +Turtle.CANVAS_HEIGHT = json.height; + +//Starting position and radius of turtle +Turtle.START_X = json.startX; +Turtle.START_Y = json.startY; +Turtle.RADIUS = json.radius; + +//Current coordinates of turtle +Turtle.CURRENT_COORD = { + x:Turtle.START_X, + y:Turtle.START_Y +} + +//Current heading of turtle +Turtle.HEADING = json.startAngle; + +//Weater or not the pen is down and it's width +Turtle.PEN_DOWN = true; //At start, the pen is always down +Turtle.PEN_WIDTH = json.strokeWidth; +Turtle.PEN_COLOUR = json.strokeColour; + +//Variables used to draw on the solution canvas or the decor canvas +var sol = false; +var decor = false; + +//milisec between each frame +window.stepSpeed = json.animationRate; + +/** +* +* Animations functions +* +*/ + +Turtle.drawMap = function() { + var div = document.getElementById('visualization'); + + // Add the canvas for the turtle + var canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute("style", "border:1px solid #F1EEE7;") + canvas.setAttribute("id", "turtle-canvas"); + div.appendChild(canvas); + // Add the canvas for the user. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "user-canvas"); + div.appendChild(canvas); + // Add the canvas for the solution. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "solution-canvas"); + div.appendChild(canvas); + //Add the canvas for the decor + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none') + canvas.setAttribute("id", "decor-canvas"); + div.appendChild(canvas) + + //Draw the decor + Turtle.drawDecor(); + + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); + + //Draw the turtle + Turtle.updateImage(); + + +} + +Turtle.drawTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + + //Draw the turtle body + ctx.beginPath(); + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, Turtle.RADIUS, 0, 2 * Math.PI); + ctx.stroke(); + + // Draw the turtle head. + var WIDTH = 0.3; + var HEAD_TIP = 10; + var ARROW_TIP = 4; + var BEND = 6; + var radians = 2 * Math.PI * Turtle.HEADING / 360; + var tipX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + HEAD_TIP) * Math.sin(radians); + var tipY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + HEAD_TIP) * Math.cos(radians); + radians -= WIDTH; + var leftX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var leftY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + radians += WIDTH / 2; + var leftControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var leftControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH; + var rightControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var rightControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH / 2; + var rightX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var rightY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + + ctx.beginPath(); + ctx.moveTo(tipX, tipY); + ctx.lineTo(leftX, leftY); + ctx.bezierCurveTo(leftControlX, leftControlY, + rightControlX, rightControlY, rightX, rightY); + ctx.closePath(); + ctx.fillStyle = Turtle.PEN_COLOUR; + ctx.fill(); +} + +Turtle.resetTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + //Clear any previous turtle + ctx.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + +} + +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } + +} + + +Turtle.drawSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + sol = true; + solution(); + sol = false; + Turtle.reset(true); +} + +Turtle.drawDecor = function(){ + var c = document.getElementById("decor-canvas"); + var ctx = c.getContext("2d") + decor = true; + decoration(); + decor = false; + Turtle.reset(true); +} + +Turtle.animate = function(id) { + switch(id){ + case "move" : + Turtle.updateImage(); + break; + case "turn" : + Turtle.updateImage(); + break; + case "colour": + Turtle.updateImage(); + break; + default: + //This should not happen + console.warn("Unknown animation"); + break; + } +} + +Turtle.updateImage = function(){ + Turtle.resetTurtle(); + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d") + var c2 = document.getElementById("user-canvas"); + var c3 = document.getElementById("solution-canvas"); + var c4 = document.getElementById("decor-canvas"); + ctx1.drawImage(c4, 0, 0); //Fuse any decor + ctx1.drawImage(c3, 0, 0); //Fuse solution canvas + ctx1.drawImage(c2, 0, 0); //Fuse user canvas + Turtle.drawTurtle(); //Add the turtle +} + +Turtle.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Turtle.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + } + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,penUp,penDown,penWidth,penColour,'); + + Turtle.drawMap(); +}; + +Turtle.reset = function(bool){ + if(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + } + Turtle.updateImage(); +} + +//Workaround current plugin implementation that uses Maze as a variable +Maze.reset = function(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + if(!bool){ + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d"); + var c2 = document.getElementById("user-canvas"); + var ctx2 = c2.getContext("2d"); + ctx1.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + ctx2.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + } + Turtle.updateImage(); +} + +/** +* +* Blocks functions +* +*/ +Turtle.move = function(length){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + if (Turtle.PEN_DOWN) { + ctx.beginPath(); + ctx.moveTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + } + if(length){ + Turtle.CURRENT_COORD.x += length * Math.sin(2 * Math.PI * Turtle.HEADING / 360); + Turtle.CURRENT_COORD.y -= length * Math.cos(2 * Math.PI * Turtle.HEADING / 360); + } + if(Turtle.PEN_DOWN){ + ctx.lineWidth = Turtle.PEN_WIDTH; + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.lineTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + ctx.stroke(); + } + Turtle.animate("move"); +} + +Turtle.moveForward = function(length){ + Turtle.move(length); +} + + +Turtle.moveBackward = function(length){ + Turtle.move(-length); +} + +Turtle.jumpForward = function(length){ + Turtle.penUp(); + Turtle.moveForward(length); + Turtle.penDown(); +} + +Turtle.jumpBackward = function(length){ + Turtle.penUp(); + Turtle.moveBackward(length); + Turtle.penDown(); +} + +Turtle.circle = function(radius){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + ctx.beginPath(); + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, radius, 0,2*Math.PI); + ctx.stroke(); + Turtle.updateImage(); +} + +Turtle.turn = function(angle, direction){ + switch (direction){ + case 0: + Turtle.HEADING = (Turtle.HEADING - angle) % 360; + if (Turtle.HEADING < 0) { + Turtle.HEADING += 360; + } + break; + case 1: + Turtle.HEADING = (Turtle.HEADING + angle) % 360; + break; + } + Turtle.animate("turn"); +} + +Turtle.turnRight = function(angle){ + Turtle.turn(angle, 1); +} + +Turtle.turnLeft = function(angle){ + Turtle.turn(angle, 0); +} + +Turtle.penWidth = function(width){ + Turtle.PEN_WIDTH = width; +} + +Turtle.penUp = function(){ + Turtle.PEN_DOWN = false; +} + +Turtle.penDown = function(){ + Turtle.PEN_DOWN = true; +} + +Turtle.penColour = function(colour){ + Turtle.PEN_COLOUR = colour; + Turtle.animate("colour"); +} + +//Called to draw +if (document.getElementById('visualization') != null) { + window.addEventListener('load', Turtle.init); +} else { + console.warn('Cannot find visualization element.'); +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 11/Artist_03/public/turtle_config.json b/Cours 1 Code.org/Lecon 11/Artist_03/public/turtle_config.json new file mode 100644 index 0000000..75c3c9e --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_03/public/turtle_config.json @@ -0,0 +1,14 @@ +{ + "startX":120, + "startY":50, + "startAngle":90, + "strokeWidth":3, + "strokeColour":"#000000", + "colourSpecific":false, + "radius":15, + "animationRate":50, + "width":290, + "height":290, + "imageSolution":true, + "imageName":"solution.png" +} \ No newline at end of file diff --git a/Cours 1/Lecon2/Artist_3/run b/Cours 1 Code.org/Lecon 11/Artist_03/run similarity index 100% rename from Cours 1/Lecon2/Artist_3/run rename to Cours 1 Code.org/Lecon 11/Artist_03/run diff --git a/Cours 1 Code.org/Lecon 11/Artist_03/student/turtle.py b/Cours 1 Code.org/Lecon 11/Artist_03/student/turtle.py new file mode 100644 index 0000000..8bcd665 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_03/student/turtle.py @@ -0,0 +1,119 @@ +import math +import json +import os +import random + +#Reset the turtle variables to starting position +def resetTurtle(): + Turtle["x"] = start_x + Turtle["y"] = start_y + Turtle["heading"] = start_heading + Turtle["colour"] = start_pen_colour + Turtle["width"] = start_pen_width + Turtle["penDown"] = True + + +def student_code(): +@ @code@@ + +#Code of the solution +def solution(): + for i in range(12): + for j in range(3): + moveForward(50) + turnLeft(120) + moveForward(50) + turnRight(30) + +#Generates a random colour +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +# +# Code to "draw" +# + +# Format stored in the dictionary : +# Point x - Point y (as int) : strokewidth strokeColour + +def moveForward(length): + addPoint() + for i in range(length): + Turtle["x"] += int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] -= int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def moveBackward(length): + addPoint() + for i in range(length): + Turtle["x"] -= int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] += int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def addPoint(): + if(sol): # We are computing the solution + if(count_colours): # Colour is important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: # Colour is not important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + else: # We are computing the student output + if(count_colours): + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + +def turnRight(angle): + Turtle["heading"] = (Turtle["heading"] + angle) % 360; + +def turnLeft(angle): + Turtle["heading"] = (Turtle["heading"] - angle) % 360; + if (Turtle["heading"] < 0): + Turtle["heading"] += 360 + +def penWidth(width): + Turtle["width"] = width + +def penUp(): + Turtle["penDown"] = False + +def penDown(): + Turtle["penDown"] = True + +def penColour(colour): + Turtle["colour"] = colour + +# Get the json data +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'turtle_config.json') as f: + data = json.load(f) + +# Dictionaries to compare +sol_output = {} +user_output = {} + +# Variables to start +start_x = data["startX"] +start_y = data["startY"] +start_heading = data["startAngle"] +start_pen_width = data["strokeWidth"] +start_pen_colour = data["strokeColour"] + +count_colours = data["colourSpecific"] + +Turtle = {} # Current turtle state is stocked here +resetTurtle() # Set the variable +sol = True # We will execute the solution first. +solution() # Execute the solution +resetTurtle() # Reset the turtle again +sol = False # We are no longer executing the solution +student_code() # Execute the student code + +if(user_output == sol_output): #If the dicts are the same, success + print("True", end='', flush=True) +else: + + print("Votre solution est incorrecte, essayez encore", end='', flush=True) \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 11/Artist_03/task.yaml b/Cours 1 Code.org/Lecon 11/Artist_03/task.yaml new file mode 100644 index 0000000..c7e05f7 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_03/task.yaml @@ -0,0 +1,124 @@ +accessible: true +author: Celine Deknop +context: Maintenant, crée un soleil avec le code d'un petit triangle, en utilisant + une boucle imbriquée. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + output: '2' + memory: '100' + time: '30' +name: Exercice 3 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: '45' + files: + - turtle.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + + + 3 + + + + + moveForward + 50 + + + turnLeft + 120 + + + + + + + toolbox: |- + + + + + + moveForward + 100 + + + turnRight + 90 + + + turnLeft + 90 + + + + + + + + 0 + + + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + visible: true + name: Boucles imbriquées + description: '' + id: '1' + '1': + description: Exercice faisant partie de la leçon 11 + type: 2 + visible: true + name: Lecon 11 + id: '' + '2': + description: Faisant partie du parcours normal + name: Normal + type: 2 + visible: false + id: '' + '3': + name: Facile + description: Faisant partie du parcours facile + type: 2 + visible: false + id: '' + '4': + type: 2 + description: Faisant partie du parcours challenge + name: Challenge + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 11/Artist_04/public/blocks.js b/Cours 1 Code.org/Lecon 11/Artist_04/public/blocks.js new file mode 100644 index 0000000..4478d0c --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_04/public/blocks.js @@ -0,0 +1,364 @@ +/** + * Blockly Games: Turtle Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Turtle application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Turtle.Blocks = {}; + +/** + * Common HSV hue for all blocks in this category. + */ +Turtle.Blocks.HUE = 160; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['turtle_move'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + this.appendValueInput("VALUE") + .setCheck('Number') + .appendField(new Blockly.FieldDropdown( + [ + ["avancer de","moveForward"], + ["reculer de","moveBackward"]] + ), "DIR"); + this.appendDummyInput() + .appendField("pixels"); + this.setColour(Turtle.Blocks.HUE); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setInputsInline(true); + this.setTooltip("Bouger"); + } +}; + +Blockly.JavaScript['turtle_move'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_move'] = function(block) { + // Generate Python for moving forward or backwards. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_move_internal'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['avancer de', 'moveForward'], + ['reculer de', 'moveBackward']]; + var VALUES = + [['20 pixels', '20'], + ['50 pixels', '50'], + ['100 pixels', '100'], + ['150 pixels', '150']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Bouger'); + } +}; + +Blockly.JavaScript['turtle_move_internal'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ');\n'; +}; + +Blockly.Python['turtle_move_internal'] = function(block) { + // Generate Python for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite', 'turnRight'], + ['tourner à gauche', 'turnLeft']]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Turtle.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Turtle.Blocks.LEFT_TURN; + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('VALUE') + .setCheck('Number') + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourner"); + } +}; + +Blockly.JavaScript['turtle_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn'] = function(block) { + // Generate Python for turning left or right. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.Python.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_turn_internal'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite de', 'turnRight'], + ['tourner à gauche de', 'turnLeft']]; + var VALUES = + [['1\u00B0', '1'], + ['30\u00B0', '30'], + ['45\u00B0', '45'], + ['72\u00B0', '72'], + ['90\u00B0', '90'], + ['120\u00B0', '120'], + ['144\u00B0', '144']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Tourner de X degrés'); + } +}; + +Blockly.JavaScript['turtle_turn_internal'] = function(block) { + // Generate JavaScript for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn_internal'] = function(block) { + // Generate Python for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_width'] = { + /** + * Block for setting the width. + * @this Blockly.Block + */ + init: function() { + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('WIDTH') + .setCheck('Number') + .appendField('Définis l\'épaisseur de la ligne'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Définis l\'épaisseur de la ligne'); + } +}; + +Blockly.JavaScript['turtle_width'] = function(block) { + // Generate JavaScript for setting the width. + var width = Blockly.JavaScript.valueToCode(block, 'WIDTH', + Blockly.JavaScript.ORDER_NONE) || '1'; + return 'penWidth(' + width + ');\n'; +}; + +Blockly.Python['turtle_width'] = function(block) { + // Generate Python for setting the width. + var width = Blockly.Python.valueToCode(block, 'WIDTH', + Blockly.Python.ORDER_NONE) || '1'; + return 'penWidth(' + width + ')\n'; +}; + +Blockly.Blocks['turtle_pen'] = { + /** + * Block for pen up/down. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "%1", + "args0": [ + { + "type": "field_dropdown", + "name": "PEN", + "options": [ + ['lever le crayon', "penUp"], + ['poser le crayon', "penDown"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Turtle.Blocks.HUE, + "tooltip": "Lever ou poser le crayon sur la feuille" + }); + } +}; + +Blockly.JavaScript['turtle_pen'] = function(block) { + // Generate JavaScript for pen up/down. + return block.getFieldValue('PEN') + + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_pen'] = function(block) { + // Generate Python for pen up/down. + return block.getFieldValue('PEN') + + '()\n'; +}; + +Blockly.Blocks['turtle_colour'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendValueInput('COLOUR') + .setCheck('Colour') + .appendField('définis la couleur'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Changer la couleur du trait"); + } +}; + +Blockly.JavaScript['turtle_colour'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = Blockly.JavaScript.valueToCode(block, 'COLOUR', + Blockly.JavaScript.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour'] = function(block) { + // Generate Python for setting the colour. + var colour = Blockly.Python.valueToCode(block, 'COLOUR', + Blockly.Python.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ')\n'; +}; + +Blockly.Blocks['turtle_colour_internal'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendDummyInput() + .appendField('définis la couleur') + .appendField(new Blockly.FieldColour('#ff0000'), 'COLOUR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Changer la couleur du trait'); + } +}; + +Blockly.JavaScript['turtle_colour_internal'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour_internal'] = function(block) { + // Generate Python for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ')\n'; +}; + +Blockly.Blocks['turtle_repeat_internal'] = { + /** + * Block for repeat n times (internal number). + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.CONTROLS_REPEAT_TITLE, + "args0": [ + { + "type": "field_dropdown", + "name": "TIMES", + "options": [ + ["3", "3"], + ["4", "4"], + ["5", "5"], + ["360", "360"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Blockly.Blocks.loops.HUE, + "tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP, + "helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL + }); + this.appendStatementInput('DO') + .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO); + } +}; + +Blockly.JavaScript['turtle_repeat_internal'] = +Blockly.JavaScript['controls_repeat']; + +Blockly.Python['turtle_repeat_internal'] = +Blockly.Python['controls_repeat']; \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 11/Artist_04/public/create_img.py b/Cours 1 Code.org/Lecon 11/Artist_04/public/create_img.py new file mode 100644 index 0000000..75c14b1 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_04/public/create_img.py @@ -0,0 +1,106 @@ +import Image, ImageDraw +import math +import json +import random +import os + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y - length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y + length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for count2 in range(6): + for count in range(3): + penColour("#843179") + moveForward(85) + turnRight(60) + penColour("#0000cd") + moveForward(50) + turnRight(60) + turnRight(60) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1/Lecon2/Artist_4/public/interpreter.js b/Cours 1 Code.org/Lecon 11/Artist_04/public/interpreter.js similarity index 100% rename from Cours 1/Lecon2/Artist_4/public/interpreter.js rename to Cours 1 Code.org/Lecon 11/Artist_04/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 11/Artist_04/public/solution.png b/Cours 1 Code.org/Lecon 11/Artist_04/public/solution.png new file mode 100644 index 0000000..d8f0db9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 11/Artist_04/public/solution.png differ diff --git a/Cours 1 Code.org/Lecon 11/Artist_04/public/turtle.js b/Cours 1 Code.org/Lecon 11/Artist_04/public/turtle.js new file mode 100644 index 0000000..da68c5c --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_04/public/turtle.js @@ -0,0 +1,390 @@ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Turtle = {}; +window.Maze = {}; + +//Get the json file and its informations +var turtle_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + turtle_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"turtle_config.json" +}else { //When displaying the task + turtle_file = task_directory_path + "turtle_config.json"; +} +var request = new XMLHttpRequest(); +request.open("GET", turtle_file, false); +request.send(null) +var json = JSON.parse(request.responseText); + +var imagePath = "" +if(json.imageSolution) + imagePath = task_directory_path+json.imageName; + +//Code of the solution +var solution = function(){ + //Here, put the javascript corresponding to the solved exercice +} +var decoration = function(){ + //Here, put the code for any decor, not part of the exercice +} + +var randomColour = function(){ + var colour = Math.floor(Math.random()*16777215); + return "#"+colour.toString(16).toUpperCase() +} + +//Canvas size +Turtle.CANVAS_WIDTH = json.width; +Turtle.CANVAS_HEIGHT = json.height; + +//Starting position and radius of turtle +Turtle.START_X = json.startX; +Turtle.START_Y = json.startY; +Turtle.RADIUS = json.radius; + +//Current coordinates of turtle +Turtle.CURRENT_COORD = { + x:Turtle.START_X, + y:Turtle.START_Y +} + +//Current heading of turtle +Turtle.HEADING = json.startAngle; + +//Weater or not the pen is down and it's width +Turtle.PEN_DOWN = true; //At start, the pen is always down +Turtle.PEN_WIDTH = json.strokeWidth; +Turtle.PEN_COLOUR = json.strokeColour; + +//Variables used to draw on the solution canvas or the decor canvas +var sol = false; +var decor = false; + +//milisec between each frame +window.stepSpeed = json.animationRate; + +/** +* +* Animations functions +* +*/ + +Turtle.drawMap = function() { + var div = document.getElementById('visualization'); + + // Add the canvas for the turtle + var canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute("style", "border:1px solid #F1EEE7;") + canvas.setAttribute("id", "turtle-canvas"); + div.appendChild(canvas); + // Add the canvas for the user. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "user-canvas"); + div.appendChild(canvas); + // Add the canvas for the solution. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "solution-canvas"); + div.appendChild(canvas); + //Add the canvas for the decor + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none') + canvas.setAttribute("id", "decor-canvas"); + div.appendChild(canvas) + + //Draw the decor + Turtle.drawDecor(); + + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); + + //Draw the turtle + Turtle.updateImage(); + + +} + +Turtle.drawTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + + //Draw the turtle body + ctx.beginPath(); + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, Turtle.RADIUS, 0, 2 * Math.PI); + ctx.stroke(); + + // Draw the turtle head. + var WIDTH = 0.3; + var HEAD_TIP = 10; + var ARROW_TIP = 4; + var BEND = 6; + var radians = 2 * Math.PI * Turtle.HEADING / 360; + var tipX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + HEAD_TIP) * Math.sin(radians); + var tipY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + HEAD_TIP) * Math.cos(radians); + radians -= WIDTH; + var leftX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var leftY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + radians += WIDTH / 2; + var leftControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var leftControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH; + var rightControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var rightControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH / 2; + var rightX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var rightY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + + ctx.beginPath(); + ctx.moveTo(tipX, tipY); + ctx.lineTo(leftX, leftY); + ctx.bezierCurveTo(leftControlX, leftControlY, + rightControlX, rightControlY, rightX, rightY); + ctx.closePath(); + ctx.fillStyle = Turtle.PEN_COLOUR; + ctx.fill(); +} + +Turtle.resetTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + //Clear any previous turtle + ctx.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + +} + +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } + +} + + +Turtle.drawSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + sol = true; + solution(); + sol = false; + Turtle.reset(true); +} + +Turtle.drawDecor = function(){ + var c = document.getElementById("decor-canvas"); + var ctx = c.getContext("2d") + decor = true; + decoration(); + decor = false; + Turtle.reset(true); +} + +Turtle.animate = function(id) { + switch(id){ + case "move" : + Turtle.updateImage(); + break; + case "turn" : + Turtle.updateImage(); + break; + case "colour": + Turtle.updateImage(); + break; + default: + //This should not happen + console.warn("Unknown animation"); + break; + } +} + +Turtle.updateImage = function(){ + Turtle.resetTurtle(); + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d") + var c2 = document.getElementById("user-canvas"); + var c3 = document.getElementById("solution-canvas"); + var c4 = document.getElementById("decor-canvas"); + ctx1.drawImage(c4, 0, 0); //Fuse any decor + ctx1.drawImage(c3, 0, 0); //Fuse solution canvas + ctx1.drawImage(c2, 0, 0); //Fuse user canvas + Turtle.drawTurtle(); //Add the turtle +} + +Turtle.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Turtle.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + } + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,penUp,penDown,penWidth,penColour,'); + + Turtle.drawMap(); +}; + +Turtle.reset = function(bool){ + if(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + } + Turtle.updateImage(); +} + +//Workaround current plugin implementation that uses Maze as a variable +Maze.reset = function(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + if(!bool){ + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d"); + var c2 = document.getElementById("user-canvas"); + var ctx2 = c2.getContext("2d"); + ctx1.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + ctx2.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + } + Turtle.updateImage(); +} + +/** +* +* Blocks functions +* +*/ +Turtle.move = function(length){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + if (Turtle.PEN_DOWN) { + ctx.beginPath(); + ctx.moveTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + } + if(length){ + Turtle.CURRENT_COORD.x += length * Math.sin(2 * Math.PI * Turtle.HEADING / 360); + Turtle.CURRENT_COORD.y -= length * Math.cos(2 * Math.PI * Turtle.HEADING / 360); + } + if(Turtle.PEN_DOWN){ + ctx.lineWidth = Turtle.PEN_WIDTH; + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.lineTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + ctx.stroke(); + } + Turtle.animate("move"); +} + +Turtle.moveForward = function(length){ + Turtle.move(length); +} + + +Turtle.moveBackward = function(length){ + Turtle.move(-length); +} + +Turtle.jumpForward = function(length){ + Turtle.penUp(); + Turtle.moveForward(length); + Turtle.penDown(); +} + +Turtle.jumpBackward = function(length){ + Turtle.penUp(); + Turtle.moveBackward(length); + Turtle.penDown(); +} + +Turtle.circle = function(radius){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + ctx.beginPath(); + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, radius, 0,2*Math.PI); + ctx.stroke(); + Turtle.updateImage(); +} + +Turtle.turn = function(angle, direction){ + switch (direction){ + case 0: + Turtle.HEADING = (Turtle.HEADING - angle) % 360; + if (Turtle.HEADING < 0) { + Turtle.HEADING += 360; + } + break; + case 1: + Turtle.HEADING = (Turtle.HEADING + angle) % 360; + break; + } + Turtle.animate("turn"); +} + +Turtle.turnRight = function(angle){ + Turtle.turn(angle, 1); +} + +Turtle.turnLeft = function(angle){ + Turtle.turn(angle, 0); +} + +Turtle.penWidth = function(width){ + Turtle.PEN_WIDTH = width; +} + +Turtle.penUp = function(){ + Turtle.PEN_DOWN = false; +} + +Turtle.penDown = function(){ + Turtle.PEN_DOWN = true; +} + +Turtle.penColour = function(colour){ + Turtle.PEN_COLOUR = colour; + Turtle.animate("colour"); +} + +//Called to draw +if (document.getElementById('visualization') != null) { + window.addEventListener('load', Turtle.init); +} else { + console.warn('Cannot find visualization element.'); +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 11/Artist_04/public/turtle_config.json b/Cours 1 Code.org/Lecon 11/Artist_04/public/turtle_config.json new file mode 100644 index 0000000..ff661da --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_04/public/turtle_config.json @@ -0,0 +1,14 @@ +{ + "startX":150, + "startY":150, + "startAngle":90, + "strokeWidth":3, + "strokeColour":"#000000", + "colourSpecific":false, + "radius":15, + "animationRate":50, + "width":290, + "height":290, + "imageSolution":true, + "imageName":"solution.png" +} \ No newline at end of file diff --git a/Cours 1/Lecon2/Artist_4/run b/Cours 1 Code.org/Lecon 11/Artist_04/run similarity index 100% rename from Cours 1/Lecon2/Artist_4/run rename to Cours 1 Code.org/Lecon 11/Artist_04/run diff --git a/Cours 1 Code.org/Lecon 11/Artist_04/student/turtle.py b/Cours 1 Code.org/Lecon 11/Artist_04/student/turtle.py new file mode 100644 index 0000000..69e1901 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_04/student/turtle.py @@ -0,0 +1,123 @@ +import math +import json +import os +import random + +#Reset the turtle variables to starting position +def resetTurtle(): + Turtle["x"] = start_x + Turtle["y"] = start_y + Turtle["heading"] = start_heading + Turtle["colour"] = start_pen_colour + Turtle["width"] = start_pen_width + Turtle["penDown"] = True + + +def student_code(): +@ @code@@ + +#Code of the solution +def solution(): + for count2 in range(6): + for count in range(3): + penColour("#843179") + moveForward(85) + turnRight(60) + penColour("#0000cd") + moveForward(50) + turnRight(60) + turnRight(60) + + +#Generates a random colour +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +# +# Code to "draw" +# + +# Format stored in the dictionary : +# Point x - Point y (as int) : strokewidth strokeColour + +def moveForward(length): + addPoint() + for i in range(length): + Turtle["x"] += int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] -= int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def moveBackward(length): + addPoint() + for i in range(length): + Turtle["x"] -= int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] += int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def addPoint(): + if(sol): # We are computing the solution + if(count_colours): # Colour is important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: # Colour is not important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + else: # We are computing the student output + if(count_colours): + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + +def turnRight(angle): + Turtle["heading"] = (Turtle["heading"] + angle) % 360; + +def turnLeft(angle): + Turtle["heading"] = (Turtle["heading"] - angle) % 360; + if (Turtle["heading"] < 0): + Turtle["heading"] += 360 + +def penWidth(width): + Turtle["width"] = width + +def penUp(): + Turtle["penDown"] = False + +def penDown(): + Turtle["penDown"] = True + +def penColour(colour): + Turtle["colour"] = colour + +# Get the json data +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'turtle_config.json') as f: + data = json.load(f) + +# Dictionaries to compare +sol_output = {} +user_output = {} + +# Variables to start +start_x = data["startX"] +start_y = data["startY"] +start_heading = data["startAngle"] +start_pen_width = data["strokeWidth"] +start_pen_colour = data["strokeColour"] + +count_colours = data["colourSpecific"] + +Turtle = {} # Current turtle state is stocked here +resetTurtle() # Set the variable +sol = True # We will execute the solution first. +solution() # Execute the solution +resetTurtle() # Reset the turtle again +sol = False # We are no longer executing the solution +student_code() # Execute the student code + +if(user_output == sol_output): #If the dicts are the same, success + print("True", end='', flush=True) +else: + + print("Votre solution est incorrecte, essayez encore", end='', flush=True) \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 11/Artist_04/task.yaml b/Cours 1 Code.org/Lecon 11/Artist_04/task.yaml new file mode 100644 index 0000000..ea375f8 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_04/task.yaml @@ -0,0 +1,171 @@ +accessible: true +author: Celine Deknop +context: |- + Boucle ce dessin 6 fois. De combien as-tu besoin de tourner à chaque fois ? + + Indice : Combien font 360 divisé par 6 ? +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + output: '2' + memory: '100' + time: '30' +name: Exercice 4 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: '45' + files: + - turtle.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + + + 3 + + + + + #843179 + + + moveForward + + + 85 + + + + + turnRight + + + 60 + + + + + #0000cd + + + moveForward + + + 50 + + + + + turnRight + + + 60 + + + + + + + + + + + + + + + + + toolbox: |- + + + + + + moveForward + 100 + + + 0 + + + turnRight + + + + + + + + 0 + + + + + + + + #843179 + + + #0000cd + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + visible: true + name: Boucles imbriquées + description: '' + id: '1' + '1': + description: Exercice faisant partie de la leçon 11 + type: 2 + visible: true + name: Lecon 11 + id: '' + '2': + description: Faisant partie du parcours normal + name: Normal + type: 2 + visible: false + id: '' + '3': + name: Facile + description: Faisant partie du parcours facile + type: 2 + visible: false + id: '' + '4': + type: 2 + description: Faisant partie du parcours challenge + name: Challenge + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 11/Artist_05/public/blocks.js b/Cours 1 Code.org/Lecon 11/Artist_05/public/blocks.js new file mode 100644 index 0000000..4478d0c --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_05/public/blocks.js @@ -0,0 +1,364 @@ +/** + * Blockly Games: Turtle Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Turtle application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Turtle.Blocks = {}; + +/** + * Common HSV hue for all blocks in this category. + */ +Turtle.Blocks.HUE = 160; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['turtle_move'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + this.appendValueInput("VALUE") + .setCheck('Number') + .appendField(new Blockly.FieldDropdown( + [ + ["avancer de","moveForward"], + ["reculer de","moveBackward"]] + ), "DIR"); + this.appendDummyInput() + .appendField("pixels"); + this.setColour(Turtle.Blocks.HUE); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setInputsInline(true); + this.setTooltip("Bouger"); + } +}; + +Blockly.JavaScript['turtle_move'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_move'] = function(block) { + // Generate Python for moving forward or backwards. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_move_internal'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['avancer de', 'moveForward'], + ['reculer de', 'moveBackward']]; + var VALUES = + [['20 pixels', '20'], + ['50 pixels', '50'], + ['100 pixels', '100'], + ['150 pixels', '150']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Bouger'); + } +}; + +Blockly.JavaScript['turtle_move_internal'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ');\n'; +}; + +Blockly.Python['turtle_move_internal'] = function(block) { + // Generate Python for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite', 'turnRight'], + ['tourner à gauche', 'turnLeft']]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Turtle.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Turtle.Blocks.LEFT_TURN; + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('VALUE') + .setCheck('Number') + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourner"); + } +}; + +Blockly.JavaScript['turtle_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn'] = function(block) { + // Generate Python for turning left or right. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.Python.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_turn_internal'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite de', 'turnRight'], + ['tourner à gauche de', 'turnLeft']]; + var VALUES = + [['1\u00B0', '1'], + ['30\u00B0', '30'], + ['45\u00B0', '45'], + ['72\u00B0', '72'], + ['90\u00B0', '90'], + ['120\u00B0', '120'], + ['144\u00B0', '144']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Tourner de X degrés'); + } +}; + +Blockly.JavaScript['turtle_turn_internal'] = function(block) { + // Generate JavaScript for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn_internal'] = function(block) { + // Generate Python for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_width'] = { + /** + * Block for setting the width. + * @this Blockly.Block + */ + init: function() { + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('WIDTH') + .setCheck('Number') + .appendField('Définis l\'épaisseur de la ligne'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Définis l\'épaisseur de la ligne'); + } +}; + +Blockly.JavaScript['turtle_width'] = function(block) { + // Generate JavaScript for setting the width. + var width = Blockly.JavaScript.valueToCode(block, 'WIDTH', + Blockly.JavaScript.ORDER_NONE) || '1'; + return 'penWidth(' + width + ');\n'; +}; + +Blockly.Python['turtle_width'] = function(block) { + // Generate Python for setting the width. + var width = Blockly.Python.valueToCode(block, 'WIDTH', + Blockly.Python.ORDER_NONE) || '1'; + return 'penWidth(' + width + ')\n'; +}; + +Blockly.Blocks['turtle_pen'] = { + /** + * Block for pen up/down. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "%1", + "args0": [ + { + "type": "field_dropdown", + "name": "PEN", + "options": [ + ['lever le crayon', "penUp"], + ['poser le crayon', "penDown"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Turtle.Blocks.HUE, + "tooltip": "Lever ou poser le crayon sur la feuille" + }); + } +}; + +Blockly.JavaScript['turtle_pen'] = function(block) { + // Generate JavaScript for pen up/down. + return block.getFieldValue('PEN') + + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_pen'] = function(block) { + // Generate Python for pen up/down. + return block.getFieldValue('PEN') + + '()\n'; +}; + +Blockly.Blocks['turtle_colour'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendValueInput('COLOUR') + .setCheck('Colour') + .appendField('définis la couleur'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Changer la couleur du trait"); + } +}; + +Blockly.JavaScript['turtle_colour'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = Blockly.JavaScript.valueToCode(block, 'COLOUR', + Blockly.JavaScript.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour'] = function(block) { + // Generate Python for setting the colour. + var colour = Blockly.Python.valueToCode(block, 'COLOUR', + Blockly.Python.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ')\n'; +}; + +Blockly.Blocks['turtle_colour_internal'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendDummyInput() + .appendField('définis la couleur') + .appendField(new Blockly.FieldColour('#ff0000'), 'COLOUR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Changer la couleur du trait'); + } +}; + +Blockly.JavaScript['turtle_colour_internal'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour_internal'] = function(block) { + // Generate Python for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ')\n'; +}; + +Blockly.Blocks['turtle_repeat_internal'] = { + /** + * Block for repeat n times (internal number). + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.CONTROLS_REPEAT_TITLE, + "args0": [ + { + "type": "field_dropdown", + "name": "TIMES", + "options": [ + ["3", "3"], + ["4", "4"], + ["5", "5"], + ["360", "360"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Blockly.Blocks.loops.HUE, + "tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP, + "helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL + }); + this.appendStatementInput('DO') + .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO); + } +}; + +Blockly.JavaScript['turtle_repeat_internal'] = +Blockly.JavaScript['controls_repeat']; + +Blockly.Python['turtle_repeat_internal'] = +Blockly.Python['controls_repeat']; \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 11/Artist_05/public/create_img.py b/Cours 1 Code.org/Lecon 11/Artist_05/public/create_img.py new file mode 100644 index 0000000..4686767 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_05/public/create_img.py @@ -0,0 +1,103 @@ +import Image, ImageDraw +import math +import json +import random +import os + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y - length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y + length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for count2 in range(36): + penColour(randomColour()) + for count in range(3): + moveForward(100) + turnRight(120) + turnRight(10) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1/Lecon2/Artist_5/public/interpreter.js b/Cours 1 Code.org/Lecon 11/Artist_05/public/interpreter.js similarity index 100% rename from Cours 1/Lecon2/Artist_5/public/interpreter.js rename to Cours 1 Code.org/Lecon 11/Artist_05/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 11/Artist_05/public/solution.png b/Cours 1 Code.org/Lecon 11/Artist_05/public/solution.png new file mode 100644 index 0000000..e856f74 Binary files /dev/null and b/Cours 1 Code.org/Lecon 11/Artist_05/public/solution.png differ diff --git a/Cours 1 Code.org/Lecon 11/Artist_05/public/turtle.js b/Cours 1 Code.org/Lecon 11/Artist_05/public/turtle.js new file mode 100644 index 0000000..da68c5c --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_05/public/turtle.js @@ -0,0 +1,390 @@ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Turtle = {}; +window.Maze = {}; + +//Get the json file and its informations +var turtle_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + turtle_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"turtle_config.json" +}else { //When displaying the task + turtle_file = task_directory_path + "turtle_config.json"; +} +var request = new XMLHttpRequest(); +request.open("GET", turtle_file, false); +request.send(null) +var json = JSON.parse(request.responseText); + +var imagePath = "" +if(json.imageSolution) + imagePath = task_directory_path+json.imageName; + +//Code of the solution +var solution = function(){ + //Here, put the javascript corresponding to the solved exercice +} +var decoration = function(){ + //Here, put the code for any decor, not part of the exercice +} + +var randomColour = function(){ + var colour = Math.floor(Math.random()*16777215); + return "#"+colour.toString(16).toUpperCase() +} + +//Canvas size +Turtle.CANVAS_WIDTH = json.width; +Turtle.CANVAS_HEIGHT = json.height; + +//Starting position and radius of turtle +Turtle.START_X = json.startX; +Turtle.START_Y = json.startY; +Turtle.RADIUS = json.radius; + +//Current coordinates of turtle +Turtle.CURRENT_COORD = { + x:Turtle.START_X, + y:Turtle.START_Y +} + +//Current heading of turtle +Turtle.HEADING = json.startAngle; + +//Weater or not the pen is down and it's width +Turtle.PEN_DOWN = true; //At start, the pen is always down +Turtle.PEN_WIDTH = json.strokeWidth; +Turtle.PEN_COLOUR = json.strokeColour; + +//Variables used to draw on the solution canvas or the decor canvas +var sol = false; +var decor = false; + +//milisec between each frame +window.stepSpeed = json.animationRate; + +/** +* +* Animations functions +* +*/ + +Turtle.drawMap = function() { + var div = document.getElementById('visualization'); + + // Add the canvas for the turtle + var canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute("style", "border:1px solid #F1EEE7;") + canvas.setAttribute("id", "turtle-canvas"); + div.appendChild(canvas); + // Add the canvas for the user. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "user-canvas"); + div.appendChild(canvas); + // Add the canvas for the solution. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "solution-canvas"); + div.appendChild(canvas); + //Add the canvas for the decor + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none') + canvas.setAttribute("id", "decor-canvas"); + div.appendChild(canvas) + + //Draw the decor + Turtle.drawDecor(); + + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); + + //Draw the turtle + Turtle.updateImage(); + + +} + +Turtle.drawTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + + //Draw the turtle body + ctx.beginPath(); + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, Turtle.RADIUS, 0, 2 * Math.PI); + ctx.stroke(); + + // Draw the turtle head. + var WIDTH = 0.3; + var HEAD_TIP = 10; + var ARROW_TIP = 4; + var BEND = 6; + var radians = 2 * Math.PI * Turtle.HEADING / 360; + var tipX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + HEAD_TIP) * Math.sin(radians); + var tipY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + HEAD_TIP) * Math.cos(radians); + radians -= WIDTH; + var leftX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var leftY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + radians += WIDTH / 2; + var leftControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var leftControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH; + var rightControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var rightControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH / 2; + var rightX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var rightY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + + ctx.beginPath(); + ctx.moveTo(tipX, tipY); + ctx.lineTo(leftX, leftY); + ctx.bezierCurveTo(leftControlX, leftControlY, + rightControlX, rightControlY, rightX, rightY); + ctx.closePath(); + ctx.fillStyle = Turtle.PEN_COLOUR; + ctx.fill(); +} + +Turtle.resetTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + //Clear any previous turtle + ctx.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + +} + +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } + +} + + +Turtle.drawSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + sol = true; + solution(); + sol = false; + Turtle.reset(true); +} + +Turtle.drawDecor = function(){ + var c = document.getElementById("decor-canvas"); + var ctx = c.getContext("2d") + decor = true; + decoration(); + decor = false; + Turtle.reset(true); +} + +Turtle.animate = function(id) { + switch(id){ + case "move" : + Turtle.updateImage(); + break; + case "turn" : + Turtle.updateImage(); + break; + case "colour": + Turtle.updateImage(); + break; + default: + //This should not happen + console.warn("Unknown animation"); + break; + } +} + +Turtle.updateImage = function(){ + Turtle.resetTurtle(); + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d") + var c2 = document.getElementById("user-canvas"); + var c3 = document.getElementById("solution-canvas"); + var c4 = document.getElementById("decor-canvas"); + ctx1.drawImage(c4, 0, 0); //Fuse any decor + ctx1.drawImage(c3, 0, 0); //Fuse solution canvas + ctx1.drawImage(c2, 0, 0); //Fuse user canvas + Turtle.drawTurtle(); //Add the turtle +} + +Turtle.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Turtle.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + } + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,penUp,penDown,penWidth,penColour,'); + + Turtle.drawMap(); +}; + +Turtle.reset = function(bool){ + if(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + } + Turtle.updateImage(); +} + +//Workaround current plugin implementation that uses Maze as a variable +Maze.reset = function(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + if(!bool){ + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d"); + var c2 = document.getElementById("user-canvas"); + var ctx2 = c2.getContext("2d"); + ctx1.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + ctx2.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + } + Turtle.updateImage(); +} + +/** +* +* Blocks functions +* +*/ +Turtle.move = function(length){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + if (Turtle.PEN_DOWN) { + ctx.beginPath(); + ctx.moveTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + } + if(length){ + Turtle.CURRENT_COORD.x += length * Math.sin(2 * Math.PI * Turtle.HEADING / 360); + Turtle.CURRENT_COORD.y -= length * Math.cos(2 * Math.PI * Turtle.HEADING / 360); + } + if(Turtle.PEN_DOWN){ + ctx.lineWidth = Turtle.PEN_WIDTH; + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.lineTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + ctx.stroke(); + } + Turtle.animate("move"); +} + +Turtle.moveForward = function(length){ + Turtle.move(length); +} + + +Turtle.moveBackward = function(length){ + Turtle.move(-length); +} + +Turtle.jumpForward = function(length){ + Turtle.penUp(); + Turtle.moveForward(length); + Turtle.penDown(); +} + +Turtle.jumpBackward = function(length){ + Turtle.penUp(); + Turtle.moveBackward(length); + Turtle.penDown(); +} + +Turtle.circle = function(radius){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + ctx.beginPath(); + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, radius, 0,2*Math.PI); + ctx.stroke(); + Turtle.updateImage(); +} + +Turtle.turn = function(angle, direction){ + switch (direction){ + case 0: + Turtle.HEADING = (Turtle.HEADING - angle) % 360; + if (Turtle.HEADING < 0) { + Turtle.HEADING += 360; + } + break; + case 1: + Turtle.HEADING = (Turtle.HEADING + angle) % 360; + break; + } + Turtle.animate("turn"); +} + +Turtle.turnRight = function(angle){ + Turtle.turn(angle, 1); +} + +Turtle.turnLeft = function(angle){ + Turtle.turn(angle, 0); +} + +Turtle.penWidth = function(width){ + Turtle.PEN_WIDTH = width; +} + +Turtle.penUp = function(){ + Turtle.PEN_DOWN = false; +} + +Turtle.penDown = function(){ + Turtle.PEN_DOWN = true; +} + +Turtle.penColour = function(colour){ + Turtle.PEN_COLOUR = colour; + Turtle.animate("colour"); +} + +//Called to draw +if (document.getElementById('visualization') != null) { + window.addEventListener('load', Turtle.init); +} else { + console.warn('Cannot find visualization element.'); +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 11/Artist_05/public/turtle_config.json b/Cours 1 Code.org/Lecon 11/Artist_05/public/turtle_config.json new file mode 100644 index 0000000..ff661da --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_05/public/turtle_config.json @@ -0,0 +1,14 @@ +{ + "startX":150, + "startY":150, + "startAngle":90, + "strokeWidth":3, + "strokeColour":"#000000", + "colourSpecific":false, + "radius":15, + "animationRate":50, + "width":290, + "height":290, + "imageSolution":true, + "imageName":"solution.png" +} \ No newline at end of file diff --git a/Cours 1/Lecon2/Artist_5/run b/Cours 1 Code.org/Lecon 11/Artist_05/run similarity index 100% rename from Cours 1/Lecon2/Artist_5/run rename to Cours 1 Code.org/Lecon 11/Artist_05/run diff --git a/Cours 1 Code.org/Lecon 11/Artist_05/student/turtle.py b/Cours 1 Code.org/Lecon 11/Artist_05/student/turtle.py new file mode 100644 index 0000000..e5a28fa --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_05/student/turtle.py @@ -0,0 +1,120 @@ +import math +import json +import os +import random + +#Reset the turtle variables to starting position +def resetTurtle(): + Turtle["x"] = start_x + Turtle["y"] = start_y + Turtle["heading"] = start_heading + Turtle["colour"] = start_pen_colour + Turtle["width"] = start_pen_width + Turtle["penDown"] = True + + +def student_code(): +@ @code@@ + +#Code of the solution +def solution(): + for count2 in range(36): + penColour(randomColour()) + for count in range(3): + moveForward(100) + turnRight(120) + turnRight(10) + + +#Generates a random colour +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +# +# Code to "draw" +# + +# Format stored in the dictionary : +# Point x - Point y (as int) : strokewidth strokeColour + +def moveForward(length): + addPoint() + for i in range(length): + Turtle["x"] += int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] -= int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def moveBackward(length): + addPoint() + for i in range(length): + Turtle["x"] -= int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] += int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def addPoint(): + if(sol): # We are computing the solution + if(count_colours): # Colour is important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: # Colour is not important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + else: # We are computing the student output + if(count_colours): + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + +def turnRight(angle): + Turtle["heading"] = (Turtle["heading"] + angle) % 360; + +def turnLeft(angle): + Turtle["heading"] = (Turtle["heading"] - angle) % 360; + if (Turtle["heading"] < 0): + Turtle["heading"] += 360 + +def penWidth(width): + Turtle["width"] = width + +def penUp(): + Turtle["penDown"] = False + +def penDown(): + Turtle["penDown"] = True + +def penColour(colour): + Turtle["colour"] = colour + +# Get the json data +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'turtle_config.json') as f: + data = json.load(f) + +# Dictionaries to compare +sol_output = {} +user_output = {} + +# Variables to start +start_x = data["startX"] +start_y = data["startY"] +start_heading = data["startAngle"] +start_pen_width = data["strokeWidth"] +start_pen_colour = data["strokeColour"] + +count_colours = data["colourSpecific"] + +Turtle = {} # Current turtle state is stocked here +resetTurtle() # Set the variable +sol = True # We will execute the solution first. +solution() # Execute the solution +resetTurtle() # Reset the turtle again +sol = False # We are no longer executing the solution +student_code() # Execute the student code + +if(user_output == sol_output): #If the dicts are the same, success + print("True", end='', flush=True) +else: + + print("Votre solution est incorrecte, essayez encore", end='', flush=True) \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 11/Artist_05/task.yaml b/Cours 1 Code.org/Lecon 11/Artist_05/task.yaml new file mode 100644 index 0000000..28d0d41 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/Artist_05/task.yaml @@ -0,0 +1,140 @@ +accessible: true +author: Celine Deknop +context: Dessine ces 36 triangles avec une boucle imbriquée +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + output: '2' + memory: '100' + time: '30' +name: Exercice 5 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: '45' + files: + - turtle.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + + + 3 + + + + + + + + + + moveForward + + + 100 + + + + + turnRight + + + 120 + + + + + + + + + + + toolbox: |- + + + + + + moveForward + 100 + + + turnRight + 90 + + + turnLeft + 90 + + + + + + + + 0 + + + + + + + + + + + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + visible: true + name: Boucles imbriquées + description: '' + id: '1' + '1': + description: Exercice faisant partie de la leçon 11 + type: 2 + visible: true + name: Lecon 11 + id: '' + '2': + description: Faisant partie du parcours normal + name: Normal + type: 2 + visible: false + id: '' + '3': + name: Facile + description: Faisant partie du parcours facile + type: 2 + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 11/course.yaml b/Cours 1 Code.org/Lecon 11/course.yaml new file mode 100644 index 0000000..5def334 --- /dev/null +++ b/Cours 1 Code.org/Lecon 11/course.yaml @@ -0,0 +1,18 @@ +accessible: true +name: Cours 11 - Drawing +description: '' +admins: +- '' +tutors: [] +groups_student_choice: false +use_classrooms: true +allow_unregister: true +allow_preview: false +registration: true +registration_password: null +registration_ac: null +registration_ac_list: +- '' +is_lti: false +lti_keys: {} +lti_send_back_grade: false diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/blocks.js b/Cours 1 Code.org/Lecon 13/Bee_01/public/blocks.js new file mode 100644 index 0000000..b76cde5 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_01/public/blocks.js @@ -0,0 +1,500 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['custom_if_bee'] = { + /** + * Block for 'if' conditional if there is nectar or honey in a flower. + * @this Blockly.Block + */ + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(210); + this.setTooltip("Execute le code si le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_while_bee'] = { + /** + * Block for while loop if there is nectar or honey + * @this Blockly.Block + */ + init: function() { + this.appendDummyInput() + .appendField("tant que") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(Maze.Blocks.LOOPS_HUE); + this.setTooltip("Execute le code tant que le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'while('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'while '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_bee_if_else'] = { + /** + * Block for 'if/else' conditional if there is nectar or honey in a flower. + * @this Blockly.Block + */ + + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([["à la fleur","isOnFlower"], ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT_IF") + .setCheck(null) + .appendField("faire"); + this.appendStatementInput("STAT_ELSE") + .setCheck(null) + .appendField("sinon"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le premier code si le personnage est sur une fleur, sinon, exécute le secondel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.JavaScript.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.JavaScript.statementToCode(block, 'STAT_ELSE'); + var code = 'if ('+dropdown_type+'()){\n'+statements_stat_if+"}\nelse{"+statements_stat_else+"}\n"; + return code; +}; + +Blockly.Python['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.Python.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.Python.statementToCode(block, 'STAT_ELSE'); + var code = 'if '+dropdown_type+'():\n'+statements_stat_if+"\nelse:"+statements_stat_else; + return code; +}; + +Blockly.Blocks['custom_be_if_on'] = { + /** + * Block for 'if' conditional if the character is on a flower or honey. + * @this Blockly.Block + */ + + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["à la fleur","isOnFlower"], + ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le code si le personnage est sur une fleur ou sur un gâteau de miel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_type+'()){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_type+'():\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting 2 nectars + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/interpreter.js b/Cours 1 Code.org/Lecon 13/Bee_01/public/interpreter.js new file mode 100644 index 0000000..bfc0171 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_01/public/interpreter.js @@ -0,0 +1,90 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(nectarRemaining()); + }; + interpreter.setProperty(scope, 'nectarRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(honeyRemaining()); + }; + interpreter.setProperty(scope, 'honeyRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnFlower()); + }; + interpreter.setProperty(scope, 'isOnFlower', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnHoney()); + }; + interpreter.setProperty(scope, 'isOnHoney', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze.js b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze.js new file mode 100644 index 0000000..826d8ae --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze.js @@ -0,0 +1,1086 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called since no obstacle + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + if(cell.optional){ //If this cell is optional, generate a random number to remove it or not + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, remove it + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + } + } + else if(cell.or){ //If this cell is either this kind or another + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, change the type + cell.remainingValue = cell.value; + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + Maze.mapCells[cell.or].push(cell) + } + } + else + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var min = cell.range[0]; + var max = cell.range[1]; + var val = Math.floor(Math.random() * (max - min + 1)) + min;; //Pick a random number in range + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +//Helper functions +var nectarRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var honeyRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var isOnFlower = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +var isOnHoney = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/avatar.png b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/background.png b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/cloud.png b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/honey.png b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/start.ogg b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/tiles.png b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/tree.png b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/wall.gif b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/wall.gif differ diff --git a/Cours 1/Lecon1/01_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/wall.mp3 old mode 100755 new mode 100644 similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/wall.mp3 rename to Cours 1 Code.org/Lecon 13/Bee_01/public/maze/wall.mp3 diff --git a/Cours 1/Lecon1/01_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/wall.ogg old mode 100755 new mode 100644 similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/wall.ogg rename to Cours 1 Code.org/Lecon 13/Bee_01/public/maze/wall.ogg diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/win.ogg b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/public/maze_config.json b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze_config.json new file mode 100644 index 0000000..2490608 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_01/public/maze_config.json @@ -0,0 +1,67 @@ +{ + "map":{ + "layout": [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 2, 1, 1, 1, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]], + "specialCells":{ + "honey":[], + "redFlower":[ + { + "x":2, + "y":3, + "value":1 + }, + { + "x":3, + "y":3, + "value":1 + }, + { + "x":4, + "y":3, + "value":1 + }, + { + "x":5, + "y":3, + "value":1 + }, + { + "x":6, + "y":3, + "value":1 + } + + ], + "purpleFlower":[], + "cloud":[] + }, + "animationSpeed":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3 + }, + "startDirection":"EAST", + "squareSize":50, + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1/Lecon1/01_maze/run b/Cours 1 Code.org/Lecon 13/Bee_01/run similarity index 100% rename from Cours 1/Lecon1/01_maze/run rename to Cours 1 Code.org/Lecon 13/Bee_01/run diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/student/maze.tpl.py b/Cours 1 Code.org/Lecon 13/Bee_01/student/maze.tpl.py new file mode 100644 index 0000000..7ea8926 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_01/student/maze.tpl.py @@ -0,0 +1,340 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"] + +toRemove = [] +toAdd = [] + +MAP_CELLS = data["map"]["specialCells"] +for kind in MAP_CELLS: # Add the random value to the purple flowers + for item in MAP_CELLS[kind]: + if "optional" in item: # May remove item + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Remove + toRemove.append((item,kind)) + elif "or" in item: # May switch kind + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Switch + toRemove.append((item,kind)) + toAdd.append((item, item["or"])) + if(kind == "purpleFlower"): + min = item["range"][0] + max = item["range"][1] + val = random.randrange(min, max+1) + item["value"] = val + if(kind != "cloud"): + item["remainingValue"] = item["value"] + +#Remove and add items after the loop +for (item,kind) in toRemove: + MAP_CELLS[kind].remove(item) +for (item,kind) in toAdd: + MAP_CELLS[kind].append(item) + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def nectarRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def honeyRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def isOnFlower(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + return True + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + return True + +def isOnHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + return True + + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 13/Bee_01/task.yaml b/Cours 1 Code.org/Lecon 13/Bee_01/task.yaml new file mode 100644 index 0000000..3a6cf77 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_01/task.yaml @@ -0,0 +1,116 @@ +accessible: true +author: Florian Thuin +context: Utilise une boucle pour recueillir tout le nectar. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 1 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + spacing: 20 + snap: true + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + css: true + toolboxPosition: start + trashcan: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + header: | + .. image:: 01_maze/maze/small_static_avatar.png + :height: 40px + + **Aide-moi à récolter tout le nectar de chaque fleur.** +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + name: Boucle "répéter X fois" + id: '1' + description: '' + visible: false + '1': + description: '' + type: 2 + visible: true + name: Lecon 13 + id: '' + '2': + description: '' + name: Facile + type: 2 + visible: false + id: '' + '3': + description: '' + name: Normal + type: 2 + visible: false + id: '' + '4': + type: 2 + name: Challenge + description: '' + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/blocks.js b/Cours 1 Code.org/Lecon 13/Bee_02/public/blocks.js new file mode 100644 index 0000000..b76cde5 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_02/public/blocks.js @@ -0,0 +1,500 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['custom_if_bee'] = { + /** + * Block for 'if' conditional if there is nectar or honey in a flower. + * @this Blockly.Block + */ + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(210); + this.setTooltip("Execute le code si le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_while_bee'] = { + /** + * Block for while loop if there is nectar or honey + * @this Blockly.Block + */ + init: function() { + this.appendDummyInput() + .appendField("tant que") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(Maze.Blocks.LOOPS_HUE); + this.setTooltip("Execute le code tant que le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'while('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'while '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_bee_if_else'] = { + /** + * Block for 'if/else' conditional if there is nectar or honey in a flower. + * @this Blockly.Block + */ + + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([["à la fleur","isOnFlower"], ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT_IF") + .setCheck(null) + .appendField("faire"); + this.appendStatementInput("STAT_ELSE") + .setCheck(null) + .appendField("sinon"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le premier code si le personnage est sur une fleur, sinon, exécute le secondel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.JavaScript.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.JavaScript.statementToCode(block, 'STAT_ELSE'); + var code = 'if ('+dropdown_type+'()){\n'+statements_stat_if+"}\nelse{"+statements_stat_else+"}\n"; + return code; +}; + +Blockly.Python['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.Python.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.Python.statementToCode(block, 'STAT_ELSE'); + var code = 'if '+dropdown_type+'():\n'+statements_stat_if+"\nelse:"+statements_stat_else; + return code; +}; + +Blockly.Blocks['custom_be_if_on'] = { + /** + * Block for 'if' conditional if the character is on a flower or honey. + * @this Blockly.Block + */ + + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["à la fleur","isOnFlower"], + ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le code si le personnage est sur une fleur ou sur un gâteau de miel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_type+'()){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_type+'():\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting 2 nectars + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/interpreter.js b/Cours 1 Code.org/Lecon 13/Bee_02/public/interpreter.js new file mode 100644 index 0000000..bfc0171 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_02/public/interpreter.js @@ -0,0 +1,90 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(nectarRemaining()); + }; + interpreter.setProperty(scope, 'nectarRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(honeyRemaining()); + }; + interpreter.setProperty(scope, 'honeyRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnFlower()); + }; + interpreter.setProperty(scope, 'isOnFlower', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnHoney()); + }; + interpreter.setProperty(scope, 'isOnHoney', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze.js b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze.js new file mode 100644 index 0000000..826d8ae --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze.js @@ -0,0 +1,1086 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called since no obstacle + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + if(cell.optional){ //If this cell is optional, generate a random number to remove it or not + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, remove it + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + } + } + else if(cell.or){ //If this cell is either this kind or another + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, change the type + cell.remainingValue = cell.value; + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + Maze.mapCells[cell.or].push(cell) + } + } + else + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var min = cell.range[0]; + var max = cell.range[1]; + var val = Math.floor(Math.random() * (max - min + 1)) + min;; //Pick a random number in range + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +//Helper functions +var nectarRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var honeyRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var isOnFlower = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +var isOnHoney = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/avatar.png b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/background.png b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/cloud.png b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/honey.png b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/start.ogg b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/tiles.png b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/tree.png b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/wall.gif b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/wall.gif differ diff --git a/Cours 1/Lecon1/02_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/wall.mp3 old mode 100755 new mode 100644 similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/wall.mp3 rename to Cours 1 Code.org/Lecon 13/Bee_02/public/maze/wall.mp3 diff --git a/Cours 1/Lecon1/02_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/wall.ogg old mode 100755 new mode 100644 similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/wall.ogg rename to Cours 1 Code.org/Lecon 13/Bee_02/public/maze/wall.ogg diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/win.ogg b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/public/maze_config.json b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze_config.json new file mode 100644 index 0000000..a556934 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_02/public/maze_config.json @@ -0,0 +1,67 @@ +{ + "map":{ + "layout": [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 2, 1, 1, 1, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]], + "specialCells":{ + "honey":[], + "redFlower":[ + { + "x":2, + "y":3, + "value":3 + }, + { + "x":3, + "y":3, + "value":3 + }, + { + "x":4, + "y":3, + "value":3 + }, + { + "x":5, + "y":3, + "value":3 + }, + { + "x":6, + "y":3, + "value":3 + } + + ], + "purpleFlower":[], + "cloud":[] + }, + "animationSpeed":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3 + }, + "startDirection":"EAST", + "squareSize":50, + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1/Lecon1/02_maze/run b/Cours 1 Code.org/Lecon 13/Bee_02/run similarity index 100% rename from Cours 1/Lecon1/02_maze/run rename to Cours 1 Code.org/Lecon 13/Bee_02/run diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/student/maze.tpl.py b/Cours 1 Code.org/Lecon 13/Bee_02/student/maze.tpl.py new file mode 100644 index 0000000..7ea8926 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_02/student/maze.tpl.py @@ -0,0 +1,340 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"] + +toRemove = [] +toAdd = [] + +MAP_CELLS = data["map"]["specialCells"] +for kind in MAP_CELLS: # Add the random value to the purple flowers + for item in MAP_CELLS[kind]: + if "optional" in item: # May remove item + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Remove + toRemove.append((item,kind)) + elif "or" in item: # May switch kind + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Switch + toRemove.append((item,kind)) + toAdd.append((item, item["or"])) + if(kind == "purpleFlower"): + min = item["range"][0] + max = item["range"][1] + val = random.randrange(min, max+1) + item["value"] = val + if(kind != "cloud"): + item["remainingValue"] = item["value"] + +#Remove and add items after the loop +for (item,kind) in toRemove: + MAP_CELLS[kind].remove(item) +for (item,kind) in toAdd: + MAP_CELLS[kind].append(item) + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def nectarRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def honeyRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def isOnFlower(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + return True + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + return True + +def isOnHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + return True + + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 13/Bee_02/task.yaml b/Cours 1 Code.org/Lecon 13/Bee_02/task.yaml new file mode 100644 index 0000000..2f63de1 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_02/task.yaml @@ -0,0 +1,122 @@ +accessible: true +author: Florian Thuin +context: Recueille tout le nectar de chaque fleur. Utilise une boucle imbriquée. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 2 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + spacing: 20 + snap: true + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + css: true + toolboxPosition: start + trashcan: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + header: | + .. image:: 01_maze/maze/small_static_avatar.png + :height: 40px + + **Aide-moi à récolter tout le nectar de chaque fleur.** +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + name: Boucle répéter X fois + id: '1' + description: '' + type: 0 + visible: false + '1': + id: '2' + description: '' + type: 0 + name: Boucle imbriquée + visible: false + '2': + description: '' + name: Challenge + type: 2 + visible: false + id: '' + '3': + description: '' + type: 2 + name: Facile + visible: false + id: '' + '4': + type: 2 + description: '' + name: Lecon 13 + visible: true + id: '' + '5': + description: '' + name: Normal + type: 2 + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/blocks.js b/Cours 1 Code.org/Lecon 13/Bee_03/public/blocks.js new file mode 100644 index 0000000..b76cde5 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_03/public/blocks.js @@ -0,0 +1,500 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['custom_if_bee'] = { + /** + * Block for 'if' conditional if there is nectar or honey in a flower. + * @this Blockly.Block + */ + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(210); + this.setTooltip("Execute le code si le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_while_bee'] = { + /** + * Block for while loop if there is nectar or honey + * @this Blockly.Block + */ + init: function() { + this.appendDummyInput() + .appendField("tant que") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(Maze.Blocks.LOOPS_HUE); + this.setTooltip("Execute le code tant que le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'while('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'while '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_bee_if_else'] = { + /** + * Block for 'if/else' conditional if there is nectar or honey in a flower. + * @this Blockly.Block + */ + + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([["à la fleur","isOnFlower"], ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT_IF") + .setCheck(null) + .appendField("faire"); + this.appendStatementInput("STAT_ELSE") + .setCheck(null) + .appendField("sinon"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le premier code si le personnage est sur une fleur, sinon, exécute le secondel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.JavaScript.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.JavaScript.statementToCode(block, 'STAT_ELSE'); + var code = 'if ('+dropdown_type+'()){\n'+statements_stat_if+"}\nelse{"+statements_stat_else+"}\n"; + return code; +}; + +Blockly.Python['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.Python.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.Python.statementToCode(block, 'STAT_ELSE'); + var code = 'if '+dropdown_type+'():\n'+statements_stat_if+"\nelse:"+statements_stat_else; + return code; +}; + +Blockly.Blocks['custom_be_if_on'] = { + /** + * Block for 'if' conditional if the character is on a flower or honey. + * @this Blockly.Block + */ + + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["à la fleur","isOnFlower"], + ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le code si le personnage est sur une fleur ou sur un gâteau de miel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_type+'()){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_type+'():\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting 2 nectars + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/interpreter.js b/Cours 1 Code.org/Lecon 13/Bee_03/public/interpreter.js new file mode 100644 index 0000000..bfc0171 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_03/public/interpreter.js @@ -0,0 +1,90 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(nectarRemaining()); + }; + interpreter.setProperty(scope, 'nectarRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(honeyRemaining()); + }; + interpreter.setProperty(scope, 'honeyRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnFlower()); + }; + interpreter.setProperty(scope, 'isOnFlower', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnHoney()); + }; + interpreter.setProperty(scope, 'isOnHoney', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze.js b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze.js new file mode 100644 index 0000000..826d8ae --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze.js @@ -0,0 +1,1086 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called since no obstacle + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + if(cell.optional){ //If this cell is optional, generate a random number to remove it or not + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, remove it + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + } + } + else if(cell.or){ //If this cell is either this kind or another + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, change the type + cell.remainingValue = cell.value; + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + Maze.mapCells[cell.or].push(cell) + } + } + else + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var min = cell.range[0]; + var max = cell.range[1]; + var val = Math.floor(Math.random() * (max - min + 1)) + min;; //Pick a random number in range + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +//Helper functions +var nectarRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var honeyRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var isOnFlower = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +var isOnHoney = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/avatar.png b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/background.png b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/cloud.png b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/honey.png b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/start.ogg b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/tiles.png b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/tree.png b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/wall.gif b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/wall.gif differ diff --git a/Cours 1/Lecon1/03_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/wall.mp3 old mode 100755 new mode 100644 similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/wall.mp3 rename to Cours 1 Code.org/Lecon 13/Bee_03/public/maze/wall.mp3 diff --git a/Cours 1/Lecon1/03_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/wall.ogg old mode 100755 new mode 100644 similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/wall.ogg rename to Cours 1 Code.org/Lecon 13/Bee_03/public/maze/wall.ogg diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/win.ogg b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/public/maze_config.json b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze_config.json new file mode 100644 index 0000000..8805117 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_03/public/maze_config.json @@ -0,0 +1,57 @@ +{ + "map":{ + "layout": [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 2, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]], + "specialCells":{ + "honey":[], + "redFlower":[ + { + "x":5, + "y":5, + "value":5 + }, + { + "x":5, + "y":2, + "value":5 + }, + { + "x":2, + "y":2, + "value":5 + } + + ], + "purpleFlower":[], + "cloud":[] + }, + "animationSpeed":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3 + }, + "startDirection":"EAST", + "squareSize":50, + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1/Lecon1/03_maze/run b/Cours 1 Code.org/Lecon 13/Bee_03/run similarity index 100% rename from Cours 1/Lecon1/03_maze/run rename to Cours 1 Code.org/Lecon 13/Bee_03/run diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/student/maze.tpl.py b/Cours 1 Code.org/Lecon 13/Bee_03/student/maze.tpl.py new file mode 100644 index 0000000..7ea8926 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_03/student/maze.tpl.py @@ -0,0 +1,340 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"] + +toRemove = [] +toAdd = [] + +MAP_CELLS = data["map"]["specialCells"] +for kind in MAP_CELLS: # Add the random value to the purple flowers + for item in MAP_CELLS[kind]: + if "optional" in item: # May remove item + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Remove + toRemove.append((item,kind)) + elif "or" in item: # May switch kind + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Switch + toRemove.append((item,kind)) + toAdd.append((item, item["or"])) + if(kind == "purpleFlower"): + min = item["range"][0] + max = item["range"][1] + val = random.randrange(min, max+1) + item["value"] = val + if(kind != "cloud"): + item["remainingValue"] = item["value"] + +#Remove and add items after the loop +for (item,kind) in toRemove: + MAP_CELLS[kind].remove(item) +for (item,kind) in toAdd: + MAP_CELLS[kind].append(item) + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def nectarRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def honeyRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def isOnFlower(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + return True + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + return True + +def isOnHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + return True + + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 13/Bee_03/task.yaml b/Cours 1 Code.org/Lecon 13/Bee_03/task.yaml new file mode 100644 index 0000000..e9cb9f9 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_03/task.yaml @@ -0,0 +1,116 @@ +accessible: true +author: Florian Thuin +context: Recueille tout le nectar à l’aide d’une boucle imbriquée. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 3 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + spacing: 20 + snap: true + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + css: true + toolboxPosition: start + trashcan: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + blocks_files: + - blocks.js + workspace: |- + + name: '' + header: | + .. image:: 01_maze/maze/small_static_avatar.png + :height: 40px + + **Aide-moi à récolter tout le nectar de chaque fleur.** +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + name: 'Boucle ' + id: '1' + description: '' + visible: false + '1': + id: '2' + description: '' + type: 0 + name: Boucle imbriquée + visible: false + '2': + description: '' + name: Facile + type: 2 + visible: false + id: '' + '3': + description: '' + visible: true + name: Lecon 13 + type: 2 + id: '' + '4': + type: 2 + name: Normal + description: '' + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/blocks.js b/Cours 1 Code.org/Lecon 13/Bee_04/public/blocks.js new file mode 100644 index 0000000..b76cde5 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_04/public/blocks.js @@ -0,0 +1,500 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['custom_if_bee'] = { + /** + * Block for 'if' conditional if there is nectar or honey in a flower. + * @this Blockly.Block + */ + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(210); + this.setTooltip("Execute le code si le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_while_bee'] = { + /** + * Block for while loop if there is nectar or honey + * @this Blockly.Block + */ + init: function() { + this.appendDummyInput() + .appendField("tant que") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(Maze.Blocks.LOOPS_HUE); + this.setTooltip("Execute le code tant que le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'while('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'while '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_bee_if_else'] = { + /** + * Block for 'if/else' conditional if there is nectar or honey in a flower. + * @this Blockly.Block + */ + + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([["à la fleur","isOnFlower"], ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT_IF") + .setCheck(null) + .appendField("faire"); + this.appendStatementInput("STAT_ELSE") + .setCheck(null) + .appendField("sinon"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le premier code si le personnage est sur une fleur, sinon, exécute le secondel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.JavaScript.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.JavaScript.statementToCode(block, 'STAT_ELSE'); + var code = 'if ('+dropdown_type+'()){\n'+statements_stat_if+"}\nelse{"+statements_stat_else+"}\n"; + return code; +}; + +Blockly.Python['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.Python.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.Python.statementToCode(block, 'STAT_ELSE'); + var code = 'if '+dropdown_type+'():\n'+statements_stat_if+"\nelse:"+statements_stat_else; + return code; +}; + +Blockly.Blocks['custom_be_if_on'] = { + /** + * Block for 'if' conditional if the character is on a flower or honey. + * @this Blockly.Block + */ + + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["à la fleur","isOnFlower"], + ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le code si le personnage est sur une fleur ou sur un gâteau de miel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_type+'()){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_type+'():\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting 2 nectars + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/interpreter.js b/Cours 1 Code.org/Lecon 13/Bee_04/public/interpreter.js new file mode 100644 index 0000000..bfc0171 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_04/public/interpreter.js @@ -0,0 +1,90 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(nectarRemaining()); + }; + interpreter.setProperty(scope, 'nectarRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(honeyRemaining()); + }; + interpreter.setProperty(scope, 'honeyRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnFlower()); + }; + interpreter.setProperty(scope, 'isOnFlower', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnHoney()); + }; + interpreter.setProperty(scope, 'isOnHoney', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze.js b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze.js new file mode 100644 index 0000000..826d8ae --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze.js @@ -0,0 +1,1086 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called since no obstacle + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + if(cell.optional){ //If this cell is optional, generate a random number to remove it or not + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, remove it + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + } + } + else if(cell.or){ //If this cell is either this kind or another + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, change the type + cell.remainingValue = cell.value; + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + Maze.mapCells[cell.or].push(cell) + } + } + else + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var min = cell.range[0]; + var max = cell.range[1]; + var val = Math.floor(Math.random() * (max - min + 1)) + min;; //Pick a random number in range + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +//Helper functions +var nectarRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var honeyRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var isOnFlower = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +var isOnHoney = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/avatar.png b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/background.png b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/cloud.png b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/honey.png b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/start.ogg b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/tiles.png b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/tree.png b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/wall.gif b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/wall.gif differ diff --git a/Cours 1/Lecon1/04_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/wall.mp3 old mode 100755 new mode 100644 similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/wall.mp3 rename to Cours 1 Code.org/Lecon 13/Bee_04/public/maze/wall.mp3 diff --git a/Cours 1/Lecon1/04_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/wall.ogg old mode 100755 new mode 100644 similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/wall.ogg rename to Cours 1 Code.org/Lecon 13/Bee_04/public/maze/wall.ogg diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/win.ogg b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/public/maze_config.json b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze_config.json new file mode 100644 index 0000000..16b75be --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_04/public/maze_config.json @@ -0,0 +1,46 @@ +{ + "map":{ + "layout": [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0], + [0, 0, 2, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]], + "specialCells":{ + "honey":[], + "redFlower":[], + "purpleFlower":[ + { + "x":5, + "y":4, + "range":[0,3] + } + ], + "cloud":[] + }, + "animationSpeed":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3 + }, + "startDirection":"EAST", + "squareSize":50, + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1/Lecon1/04_maze/run b/Cours 1 Code.org/Lecon 13/Bee_04/run similarity index 100% rename from Cours 1/Lecon1/04_maze/run rename to Cours 1 Code.org/Lecon 13/Bee_04/run diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/student/maze.tpl.py b/Cours 1 Code.org/Lecon 13/Bee_04/student/maze.tpl.py new file mode 100644 index 0000000..7ea8926 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_04/student/maze.tpl.py @@ -0,0 +1,340 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"] + +toRemove = [] +toAdd = [] + +MAP_CELLS = data["map"]["specialCells"] +for kind in MAP_CELLS: # Add the random value to the purple flowers + for item in MAP_CELLS[kind]: + if "optional" in item: # May remove item + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Remove + toRemove.append((item,kind)) + elif "or" in item: # May switch kind + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Switch + toRemove.append((item,kind)) + toAdd.append((item, item["or"])) + if(kind == "purpleFlower"): + min = item["range"][0] + max = item["range"][1] + val = random.randrange(min, max+1) + item["value"] = val + if(kind != "cloud"): + item["remainingValue"] = item["value"] + +#Remove and add items after the loop +for (item,kind) in toRemove: + MAP_CELLS[kind].remove(item) +for (item,kind) in toAdd: + MAP_CELLS[kind].append(item) + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def nectarRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def honeyRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def isOnFlower(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + return True + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + return True + +def isOnHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + return True + + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 13/Bee_04/task.yaml b/Cours 1 Code.org/Lecon 13/Bee_04/task.yaml new file mode 100644 index 0000000..1320802 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_04/task.yaml @@ -0,0 +1,131 @@ +accessible: true +author: Florian Thuin +context: Ces fleurs pourpres ont une quantité inconnue de nectar. Utilise une boucle + «Tant que nectar» pour recueillir tout le nectar. Cette boucle s'exécutera tant + que la condition est vraie, dans ce cas «Nectar > 0». +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 4 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + nectarRemaining + > + 0 + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + spacing: 20 + snap: true + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + trashcan: true + css: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + description: '' + type: 0 + name: Boucle imbriquée + id: '2' + visible: false + '1': + id: '3' + description: '' + type: 0 + name: Boucle tant qye + visible: false + '2': + type: 0 + description: '' + name: Boucle répéter X fois + id: '1' + visible: false + '3': + description: '' + type: 2 + name: Challenge + visible: false + id: '' + '4': + type: 2 + name: Facile + description: '' + visible: false + id: '' + '5': + visible: true + description: '' + name: Lecon 13 + type: 2 + id: '' + '6': + description: '' + type: 2 + name: Normal + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/blocks.js b/Cours 1 Code.org/Lecon 13/Bee_05/public/blocks.js new file mode 100644 index 0000000..b76cde5 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_05/public/blocks.js @@ -0,0 +1,500 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['custom_if_bee'] = { + /** + * Block for 'if' conditional if there is nectar or honey in a flower. + * @this Blockly.Block + */ + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(210); + this.setTooltip("Execute le code si le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_while_bee'] = { + /** + * Block for while loop if there is nectar or honey + * @this Blockly.Block + */ + init: function() { + this.appendDummyInput() + .appendField("tant que") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(Maze.Blocks.LOOPS_HUE); + this.setTooltip("Execute le code tant que le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'while('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'while '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_bee_if_else'] = { + /** + * Block for 'if/else' conditional if there is nectar or honey in a flower. + * @this Blockly.Block + */ + + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([["à la fleur","isOnFlower"], ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT_IF") + .setCheck(null) + .appendField("faire"); + this.appendStatementInput("STAT_ELSE") + .setCheck(null) + .appendField("sinon"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le premier code si le personnage est sur une fleur, sinon, exécute le secondel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.JavaScript.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.JavaScript.statementToCode(block, 'STAT_ELSE'); + var code = 'if ('+dropdown_type+'()){\n'+statements_stat_if+"}\nelse{"+statements_stat_else+"}\n"; + return code; +}; + +Blockly.Python['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.Python.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.Python.statementToCode(block, 'STAT_ELSE'); + var code = 'if '+dropdown_type+'():\n'+statements_stat_if+"\nelse:"+statements_stat_else; + return code; +}; + +Blockly.Blocks['custom_be_if_on'] = { + /** + * Block for 'if' conditional if the character is on a flower or honey. + * @this Blockly.Block + */ + + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["à la fleur","isOnFlower"], + ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le code si le personnage est sur une fleur ou sur un gâteau de miel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_type+'()){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_type+'():\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting 2 nectars + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/interpreter.js b/Cours 1 Code.org/Lecon 13/Bee_05/public/interpreter.js new file mode 100644 index 0000000..bfc0171 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_05/public/interpreter.js @@ -0,0 +1,90 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(nectarRemaining()); + }; + interpreter.setProperty(scope, 'nectarRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(honeyRemaining()); + }; + interpreter.setProperty(scope, 'honeyRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnFlower()); + }; + interpreter.setProperty(scope, 'isOnFlower', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnHoney()); + }; + interpreter.setProperty(scope, 'isOnHoney', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze.js b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze.js new file mode 100644 index 0000000..826d8ae --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze.js @@ -0,0 +1,1086 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called since no obstacle + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + if(cell.optional){ //If this cell is optional, generate a random number to remove it or not + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, remove it + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + } + } + else if(cell.or){ //If this cell is either this kind or another + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, change the type + cell.remainingValue = cell.value; + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + Maze.mapCells[cell.or].push(cell) + } + } + else + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var min = cell.range[0]; + var max = cell.range[1]; + var val = Math.floor(Math.random() * (max - min + 1)) + min;; //Pick a random number in range + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +//Helper functions +var nectarRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var honeyRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var isOnFlower = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +var isOnHoney = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/avatar.png b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/background.png b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/cloud.png b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/honey.png b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/start.ogg b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/tiles.png b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/tree.png b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/wall.gif b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/wall.gif differ diff --git a/Cours 1/Lecon1/05_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/wall.mp3 old mode 100755 new mode 100644 similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/wall.mp3 rename to Cours 1 Code.org/Lecon 13/Bee_05/public/maze/wall.mp3 diff --git a/Cours 1/Lecon1/05_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/wall.ogg old mode 100755 new mode 100644 similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/wall.ogg rename to Cours 1 Code.org/Lecon 13/Bee_05/public/maze/wall.ogg diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/win.ogg b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/public/maze_config.json b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze_config.json new file mode 100644 index 0000000..72924f0 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_05/public/maze_config.json @@ -0,0 +1,56 @@ +{ + "map":{ + "layout": [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 2, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]], + "specialCells":{ + "honey":[], + "redFlower":[], + "purpleFlower":[ + { + "x":3, + "y":4, + "range":[0,3] + }, + { + "x":5, + "y":4, + "range":[0,3] + }, + { + "x":7, + "y":4, + "range":[0,3] + } + ], + "cloud":[] + }, + "animationSpeed":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3 + }, + "startDirection":"EAST", + "squareSize":50, + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1/Lecon1/05_maze/run b/Cours 1 Code.org/Lecon 13/Bee_05/run similarity index 100% rename from Cours 1/Lecon1/05_maze/run rename to Cours 1 Code.org/Lecon 13/Bee_05/run diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/student/maze.tpl.py b/Cours 1 Code.org/Lecon 13/Bee_05/student/maze.tpl.py new file mode 100644 index 0000000..7ea8926 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_05/student/maze.tpl.py @@ -0,0 +1,340 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"] + +toRemove = [] +toAdd = [] + +MAP_CELLS = data["map"]["specialCells"] +for kind in MAP_CELLS: # Add the random value to the purple flowers + for item in MAP_CELLS[kind]: + if "optional" in item: # May remove item + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Remove + toRemove.append((item,kind)) + elif "or" in item: # May switch kind + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Switch + toRemove.append((item,kind)) + toAdd.append((item, item["or"])) + if(kind == "purpleFlower"): + min = item["range"][0] + max = item["range"][1] + val = random.randrange(min, max+1) + item["value"] = val + if(kind != "cloud"): + item["remainingValue"] = item["value"] + +#Remove and add items after the loop +for (item,kind) in toRemove: + MAP_CELLS[kind].remove(item) +for (item,kind) in toAdd: + MAP_CELLS[kind].append(item) + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def nectarRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def honeyRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def isOnFlower(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + return True + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + return True + +def isOnHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + return True + + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 13/Bee_05/task.yaml b/Cours 1 Code.org/Lecon 13/Bee_05/task.yaml new file mode 100644 index 0000000..afaa307 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_05/task.yaml @@ -0,0 +1,143 @@ +accessible: true +author: Florian Thuin +context: Mets cette boucle dans une autre boucle pour créer une boucle imbriquée et + recueillir tout le nectar des fleurs. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 5 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + nectarRemaining + > + 0 + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + spacing: 20 + snap: true + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + css: true + toolboxPosition: start + trashcan: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + moveForward + + + moveForward + + + nectarRemaining + > + 0 + + + + + + + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + name: Boucle répéter X fois + id: '1' + description: '' + type: 0 + visible: false + '1': + id: '2' + description: '' + type: 0 + name: Boucle imbriquée + visible: false + '2': + description: '' + name: Boucle tant qye + id: '3' + type: 0 + visible: false + '3': + description: '' + type: 2 + name: Facile + visible: false + id: '' + '4': + type: 2 + description: '' + name: Lecon 13 + visible: true + id: '' + '5': + description: '' + name: Normal + type: 2 + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/blocks.js b/Cours 1 Code.org/Lecon 13/Bee_06/public/blocks.js new file mode 100644 index 0000000..b76cde5 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_06/public/blocks.js @@ -0,0 +1,500 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['custom_if_bee'] = { + /** + * Block for 'if' conditional if there is nectar or honey in a flower. + * @this Blockly.Block + */ + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(210); + this.setTooltip("Execute le code si le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_while_bee'] = { + /** + * Block for while loop if there is nectar or honey + * @this Blockly.Block + */ + init: function() { + this.appendDummyInput() + .appendField("tant que") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(Maze.Blocks.LOOPS_HUE); + this.setTooltip("Execute le code tant que le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'while('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'while '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_bee_if_else'] = { + /** + * Block for 'if/else' conditional if there is nectar or honey in a flower. + * @this Blockly.Block + */ + + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([["à la fleur","isOnFlower"], ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT_IF") + .setCheck(null) + .appendField("faire"); + this.appendStatementInput("STAT_ELSE") + .setCheck(null) + .appendField("sinon"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le premier code si le personnage est sur une fleur, sinon, exécute le secondel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.JavaScript.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.JavaScript.statementToCode(block, 'STAT_ELSE'); + var code = 'if ('+dropdown_type+'()){\n'+statements_stat_if+"}\nelse{"+statements_stat_else+"}\n"; + return code; +}; + +Blockly.Python['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.Python.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.Python.statementToCode(block, 'STAT_ELSE'); + var code = 'if '+dropdown_type+'():\n'+statements_stat_if+"\nelse:"+statements_stat_else; + return code; +}; + +Blockly.Blocks['custom_be_if_on'] = { + /** + * Block for 'if' conditional if the character is on a flower or honey. + * @this Blockly.Block + */ + + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["à la fleur","isOnFlower"], + ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le code si le personnage est sur une fleur ou sur un gâteau de miel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_type+'()){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_type+'():\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting 2 nectars + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/interpreter.js b/Cours 1 Code.org/Lecon 13/Bee_06/public/interpreter.js new file mode 100644 index 0000000..bfc0171 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_06/public/interpreter.js @@ -0,0 +1,90 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(nectarRemaining()); + }; + interpreter.setProperty(scope, 'nectarRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(honeyRemaining()); + }; + interpreter.setProperty(scope, 'honeyRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnFlower()); + }; + interpreter.setProperty(scope, 'isOnFlower', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnHoney()); + }; + interpreter.setProperty(scope, 'isOnHoney', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze.js b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze.js new file mode 100644 index 0000000..826d8ae --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze.js @@ -0,0 +1,1086 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called since no obstacle + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + if(cell.optional){ //If this cell is optional, generate a random number to remove it or not + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, remove it + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + } + } + else if(cell.or){ //If this cell is either this kind or another + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, change the type + cell.remainingValue = cell.value; + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + Maze.mapCells[cell.or].push(cell) + } + } + else + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var min = cell.range[0]; + var max = cell.range[1]; + var val = Math.floor(Math.random() * (max - min + 1)) + min;; //Pick a random number in range + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +//Helper functions +var nectarRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var honeyRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var isOnFlower = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +var isOnHoney = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/avatar.png b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/background.png b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/cloud.png b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/honey.png b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/start.ogg b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/tiles.png b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/tree.png b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/wall.gif b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/wall.gif differ diff --git a/Cours 1/Lecon1/06_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/wall.mp3 old mode 100755 new mode 100644 similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/wall.mp3 rename to Cours 1 Code.org/Lecon 13/Bee_06/public/maze/wall.mp3 diff --git a/Cours 1/Lecon1/06_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/wall.ogg old mode 100755 new mode 100644 similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/wall.ogg rename to Cours 1 Code.org/Lecon 13/Bee_06/public/maze/wall.ogg diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/win.ogg b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/public/maze_config.json b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze_config.json new file mode 100644 index 0000000..7bc1966 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_06/public/maze_config.json @@ -0,0 +1,56 @@ +{ + "map":{ + "layout": [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 1, 1, 0, 0], + [0, 0, 0, 1, 1, 0, 0, 0], + [0, 0, 2, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]], + "specialCells":{ + "honey":[], + "redFlower":[], + "purpleFlower":[ + { + "x":3, + "y":4, + "range":[0,3] + }, + { + "x":4, + "y":3, + "range":[0,3] + }, + { + "x":5, + "y":2, + "range":[0,3] + } + ], + "cloud":[] + }, + "animationSpeed":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3 + }, + "startDirection":"EAST", + "squareSize":50, + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1/Lecon1/06_maze/run b/Cours 1 Code.org/Lecon 13/Bee_06/run similarity index 100% rename from Cours 1/Lecon1/06_maze/run rename to Cours 1 Code.org/Lecon 13/Bee_06/run diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/student/maze.tpl.py b/Cours 1 Code.org/Lecon 13/Bee_06/student/maze.tpl.py new file mode 100644 index 0000000..7ea8926 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_06/student/maze.tpl.py @@ -0,0 +1,340 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"] + +toRemove = [] +toAdd = [] + +MAP_CELLS = data["map"]["specialCells"] +for kind in MAP_CELLS: # Add the random value to the purple flowers + for item in MAP_CELLS[kind]: + if "optional" in item: # May remove item + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Remove + toRemove.append((item,kind)) + elif "or" in item: # May switch kind + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Switch + toRemove.append((item,kind)) + toAdd.append((item, item["or"])) + if(kind == "purpleFlower"): + min = item["range"][0] + max = item["range"][1] + val = random.randrange(min, max+1) + item["value"] = val + if(kind != "cloud"): + item["remainingValue"] = item["value"] + +#Remove and add items after the loop +for (item,kind) in toRemove: + MAP_CELLS[kind].remove(item) +for (item,kind) in toAdd: + MAP_CELLS[kind].append(item) + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def nectarRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def honeyRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def isOnFlower(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + return True + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + return True + +def isOnHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + return True + + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 13/Bee_06/task.yaml b/Cours 1 Code.org/Lecon 13/Bee_06/task.yaml new file mode 100644 index 0000000..4b26c79 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_06/task.yaml @@ -0,0 +1,118 @@ +accessible: true +author: Florian Thuin +context: Recueille tout le nectar de ces fleurs en imbriquant une boucle « Tant que + » à l'intérieur d’un bloc « Répéter ». +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 6 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + nectarRemaining + > + 0 + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + spacing: 20 + snap: true + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + css: true + toolboxPosition: start + trashcan: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + name: Boucle imbriquée + id: '2' + description: '' + visible: false + '1': + id: '1' + description: '' + type: 0 + name: Boucle répéter X fois + visible: false + '2': + description: '' + name: Boucle tant qye + id: '3' + type: 0 + visible: false + '3': + description: '' + name: Facile + type: 2 + visible: false + id: '' + '4': + type: 2 + name: Lecon 13 + visible: true + description: '' + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/blocks.js b/Cours 1 Code.org/Lecon 13/Bee_07/public/blocks.js new file mode 100644 index 0000000..b76cde5 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_07/public/blocks.js @@ -0,0 +1,500 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['custom_if_bee'] = { + /** + * Block for 'if' conditional if there is nectar or honey in a flower. + * @this Blockly.Block + */ + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(210); + this.setTooltip("Execute le code si le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_while_bee'] = { + /** + * Block for while loop if there is nectar or honey + * @this Blockly.Block + */ + init: function() { + this.appendDummyInput() + .appendField("tant que") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(Maze.Blocks.LOOPS_HUE); + this.setTooltip("Execute le code tant que le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'while('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'while '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_bee_if_else'] = { + /** + * Block for 'if/else' conditional if there is nectar or honey in a flower. + * @this Blockly.Block + */ + + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([["à la fleur","isOnFlower"], ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT_IF") + .setCheck(null) + .appendField("faire"); + this.appendStatementInput("STAT_ELSE") + .setCheck(null) + .appendField("sinon"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le premier code si le personnage est sur une fleur, sinon, exécute le secondel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.JavaScript.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.JavaScript.statementToCode(block, 'STAT_ELSE'); + var code = 'if ('+dropdown_type+'()){\n'+statements_stat_if+"}\nelse{"+statements_stat_else+"}\n"; + return code; +}; + +Blockly.Python['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.Python.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.Python.statementToCode(block, 'STAT_ELSE'); + var code = 'if '+dropdown_type+'():\n'+statements_stat_if+"\nelse:"+statements_stat_else; + return code; +}; + +Blockly.Blocks['custom_be_if_on'] = { + /** + * Block for 'if' conditional if the character is on a flower or honey. + * @this Blockly.Block + */ + + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["à la fleur","isOnFlower"], + ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le code si le personnage est sur une fleur ou sur un gâteau de miel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_type+'()){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_type+'():\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting 2 nectars + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/interpreter.js b/Cours 1 Code.org/Lecon 13/Bee_07/public/interpreter.js new file mode 100644 index 0000000..bfc0171 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_07/public/interpreter.js @@ -0,0 +1,90 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(nectarRemaining()); + }; + interpreter.setProperty(scope, 'nectarRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(honeyRemaining()); + }; + interpreter.setProperty(scope, 'honeyRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnFlower()); + }; + interpreter.setProperty(scope, 'isOnFlower', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnHoney()); + }; + interpreter.setProperty(scope, 'isOnHoney', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze.js b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze.js new file mode 100644 index 0000000..826d8ae --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze.js @@ -0,0 +1,1086 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called since no obstacle + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + if(cell.optional){ //If this cell is optional, generate a random number to remove it or not + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, remove it + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + } + } + else if(cell.or){ //If this cell is either this kind or another + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, change the type + cell.remainingValue = cell.value; + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + Maze.mapCells[cell.or].push(cell) + } + } + else + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var min = cell.range[0]; + var max = cell.range[1]; + var val = Math.floor(Math.random() * (max - min + 1)) + min;; //Pick a random number in range + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +//Helper functions +var nectarRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var honeyRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var isOnFlower = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +var isOnHoney = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/avatar.png b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/background.png b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/cloud.png b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/honey.png b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/start.ogg b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/tiles.png b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/tree.png b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/wall.gif b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/wall.gif differ diff --git a/Cours 1/Lecon1/07_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/wall.mp3 old mode 100755 new mode 100644 similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/wall.mp3 rename to Cours 1 Code.org/Lecon 13/Bee_07/public/maze/wall.mp3 diff --git a/Cours 1/Lecon1/07_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/wall.ogg old mode 100755 new mode 100644 similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/wall.ogg rename to Cours 1 Code.org/Lecon 13/Bee_07/public/maze/wall.ogg diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/win.ogg b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/public/maze_config.json b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze_config.json new file mode 100644 index 0000000..a4496c0 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_07/public/maze_config.json @@ -0,0 +1,72 @@ +{ + "map":{ + "layout": [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 2], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]], + "specialCells":{ + "honey":[ + { + "x":5, + "y":3, + "value":3 + }, + { + "x":3, + "y":3, + "value":3 + }, + { + "x":1, + "y":3, + "value":3 + } + ], + "redFlower":[ + { + "x":6, + "y":3, + "value":3 + }, + { + "x":4, + "y":3, + "value":3 + }, + { + "x":2, + "y":3, + "value":3 + } + ], + "purpleFlower":[], + "cloud":[] + }, + "animationSpeed":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3 + }, + "startDirection":"WEST", + "squareSize":50, + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} \ No newline at end of file diff --git a/Cours 1/Lecon1/07_maze/run b/Cours 1 Code.org/Lecon 13/Bee_07/run similarity index 100% rename from Cours 1/Lecon1/07_maze/run rename to Cours 1 Code.org/Lecon 13/Bee_07/run diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/student/maze.tpl.py b/Cours 1 Code.org/Lecon 13/Bee_07/student/maze.tpl.py new file mode 100644 index 0000000..7ea8926 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_07/student/maze.tpl.py @@ -0,0 +1,340 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"] + +toRemove = [] +toAdd = [] + +MAP_CELLS = data["map"]["specialCells"] +for kind in MAP_CELLS: # Add the random value to the purple flowers + for item in MAP_CELLS[kind]: + if "optional" in item: # May remove item + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Remove + toRemove.append((item,kind)) + elif "or" in item: # May switch kind + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Switch + toRemove.append((item,kind)) + toAdd.append((item, item["or"])) + if(kind == "purpleFlower"): + min = item["range"][0] + max = item["range"][1] + val = random.randrange(min, max+1) + item["value"] = val + if(kind != "cloud"): + item["remainingValue"] = item["value"] + +#Remove and add items after the loop +for (item,kind) in toRemove: + MAP_CELLS[kind].remove(item) +for (item,kind) in toAdd: + MAP_CELLS[kind].append(item) + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def nectarRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def honeyRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def isOnFlower(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + return True + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + return True + +def isOnHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + return True + + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 13/Bee_07/task.yaml b/Cours 1 Code.org/Lecon 13/Bee_07/task.yaml new file mode 100644 index 0000000..5d5d170 --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/Bee_07/task.yaml @@ -0,0 +1,129 @@ +accessible: true +author: Florian Thuin +context: Recueille tout le nectar et fabrique tout le miel en utilisant des boucles. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 7 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + nectarRemaining + > + 0 + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + spacing: 20 + snap: true + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + trashcan: true + css: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + description: '' + type: 0 + name: Boucle imbriquée + id: '2' + visible: false + '1': + id: '1' + description: '' + type: 0 + name: Boucle répéter X fois + visible: false + '2': + type: 0 + description: '' + name: Boucle tant qye + id: '3' + visible: false + '3': + description: '' + type: 2 + name: Challenge + visible: false + id: '' + '4': + type: 2 + name: Facile + description: '' + visible: false + id: '' + '5': + visible: true + description: '' + name: Lecon 13 + type: 2 + id: '' + '6': + description: '' + type: 2 + name: Normal + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 13/course.yaml b/Cours 1 Code.org/Lecon 13/course.yaml new file mode 100644 index 0000000..354d5df --- /dev/null +++ b/Cours 1 Code.org/Lecon 13/course.yaml @@ -0,0 +1,18 @@ +accessible: true +name: Cours 13 - Collect and construct +description: '' +admins: +- '' +tutors: [] +groups_student_choice: false +use_classrooms: true +allow_unregister: true +allow_preview: false +registration: true +registration_password: null +registration_ac: null +registration_ac_list: +- '' +is_lti: false +lti_keys: {} +lti_send_back_grade: false diff --git a/Cours 1/Lecon1/01_maze/public/bloc_avancer.png b/Cours 1 Code.org/Lecon 2/01_maze/public/bloc_avancer.png similarity index 100% rename from Cours 1/Lecon1/01_maze/public/bloc_avancer.png rename to Cours 1 Code.org/Lecon 2/01_maze/public/bloc_avancer.png diff --git a/Cours 1/Lecon1/01_maze/public/blocks.js b/Cours 1 Code.org/Lecon 2/01_maze/public/blocks.js similarity index 100% rename from Cours 1/Lecon1/01_maze/public/blocks.js rename to Cours 1 Code.org/Lecon 2/01_maze/public/blocks.js diff --git a/Cours 1/Lecon1/01_maze/public/interpreter.js b/Cours 1 Code.org/Lecon 2/01_maze/public/interpreter.js similarity index 100% rename from Cours 1/Lecon1/01_maze/public/interpreter.js rename to Cours 1 Code.org/Lecon 2/01_maze/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 2/01_maze/public/maze.js b/Cours 1 Code.org/Lecon 2/01_maze/public/maze.js new file mode 100644 index 0000000..b3500e6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/01_maze/public/maze.js @@ -0,0 +1,912 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +var visuals_directory_path = task_directory_path+"maze/" + +Maze.SKIN = { + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: json.visuals.obstacleSound, + winSound: json.visuals.winSound, + crashSound: json.visuals.crashSound, + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + if (Maze.SKIN.graph) { + // Draw the grid lines. + // The grid lines are offset so that the lines pass through the centre of + // each square. A half-pixel offset is also added to as standard SVG + // practice to avoid blurriness. + var offset = Maze.SQUARE_SIZE / 2 + 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Draw the tiles making up the maze map. + + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - + finishIcon.getAttribute('width') / 2); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - + finishIcon.getAttribute('height')); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1/Lecon1/01_maze/public/maze/avatar.png b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/avatar.png similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/avatar.png rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/avatar.png diff --git a/Cours 1/Lecon1/01_maze/public/maze/background.png b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/background.png similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/background.png rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/background.png diff --git a/Cours 1/Lecon1/01_maze/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/failure.mp3 similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/failure.mp3 rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/failure.mp3 diff --git a/Cours 1/Lecon1/01_maze/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/failure.ogg similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/failure.ogg rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/failure.ogg diff --git a/Cours 1/Lecon1/01_maze/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/failure_avatar.png similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/failure_avatar.png rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/failure_avatar.png diff --git a/Cours 1/Lecon1/01_maze/public/maze/goal.gif b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/goal.gif similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/goal.gif rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/goal.gif diff --git a/Cours 1/Lecon1/01_maze/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/goalIdle.gif similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/goalIdle.gif rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/goalIdle.gif diff --git a/Cours 1/Lecon1/01_maze/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/maze_forever.gif similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/maze_forever.gif rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/maze_forever.gif diff --git a/Cours 1/Lecon1/01_maze/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/obstacle.gif similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/obstacle.gif rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/obstacle.gif diff --git a/Cours 1/Lecon1/01_maze/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/obstacle.mp3 similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/obstacle.mp3 rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/obstacle.mp3 diff --git a/Cours 1/Lecon1/01_maze/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/obstacle.ogg similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/obstacle.ogg rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/obstacle.ogg diff --git a/Cours 1/Lecon1/01_maze/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/obstacleIdle.gif similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/obstacleIdle.gif rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/obstacleIdle.gif diff --git a/Cours 1/Lecon1/01_maze/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/small_static_avatar.png similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/small_static_avatar.png rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/small_static_avatar.png diff --git a/Cours 1/Lecon1/01_maze/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/start.mp3 similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/start.mp3 rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/start.mp3 diff --git a/Cours 1/Lecon1/01_maze/public/maze/start.ogg b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/start.ogg similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/start.ogg rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/start.ogg diff --git a/Cours 1/Lecon1/01_maze/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/static_avatar.png similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/static_avatar.png rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/static_avatar.png diff --git a/Cours 1/Lecon1/01_maze/public/maze/tiles.png b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/tiles.png similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/tiles.png rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/tiles.png diff --git a/Cours 1/Lecon1/08_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/wall.mp3 similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/wall.mp3 rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/wall.mp3 diff --git a/Cours 1/Lecon1/08_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/wall.ogg similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/wall.ogg rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/wall.ogg diff --git a/Cours 1/Lecon1/01_maze/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/win.mp3 similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/win.mp3 rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/win.mp3 diff --git a/Cours 1/Lecon1/01_maze/public/maze/win.ogg b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/win.ogg similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/win.ogg rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/win.ogg diff --git a/Cours 1/Lecon1/01_maze/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 2/01_maze/public/maze/win_avatar.png similarity index 100% rename from Cours 1/Lecon1/01_maze/public/maze/win_avatar.png rename to Cours 1 Code.org/Lecon 2/01_maze/public/maze/win_avatar.png diff --git a/Cours 1 Code.org/Lecon 2/01_maze/public/maze_config.json b/Cours 1 Code.org/Lecon 2/01_maze/public/maze_config.json new file mode 100644 index 0000000..2a9b066 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/01_maze/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 2, 1, 1, 3, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1/Lecon1/08_maze/run b/Cours 1 Code.org/Lecon 2/01_maze/run similarity index 100% rename from Cours 1/Lecon1/08_maze/run rename to Cours 1 Code.org/Lecon 2/01_maze/run diff --git a/Cours 1/Lecon1/05_maze/student/maze.tpl.py b/Cours 1 Code.org/Lecon 2/01_maze/student/maze.tpl.py similarity index 89% rename from Cours 1/Lecon1/05_maze/student/maze.tpl.py rename to Cours 1 Code.org/Lecon 2/01_maze/student/maze.tpl.py index 5af2c91..de0722d 100644 --- a/Cours 1/Lecon1/05_maze/student/maze.tpl.py +++ b/Cours 1 Code.org/Lecon 2/01_maze/student/maze.tpl.py @@ -2,19 +2,19 @@ This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. Try to forget the basic Python syntax for a while. ''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) class BadPathException(Exception): pass -MAP = [[0, 0, 0, 0, 0, 0, 0, 0], - [0, 2, 1, 1, 1, 1, 0, 0], - [0, 0, 0, 0, 0, 1, 0, 0], - [0, 0, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 4, 0, 1, 0, 0], - [0, 0, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 0, 0, 3, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0]] +MAP = data["map"]["layout"][0] ROWS = len(MAP) COLS = len(MAP[0]) @@ -41,13 +41,7 @@ class BadPathException(Exception): FINISH = "FINISH" OBSTACLE = "OBSTACLE" -SQUARE_TYPE = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4 -} +SQUARE_TYPE = data["map"]["squareType"] PLAYER_POSITION = { 'x': None, @@ -68,10 +62,10 @@ class BadPathException(Exception): FINISH_POSITION['x'] = x FINISH_POSITION['y'] = y -EAST = "east" -SOUTH = "south" -WEST = "west" -NORTH = "north" +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" DIRECTION_TYPE = { NORTH: 0, @@ -99,7 +93,7 @@ class BadPathException(Exception): } } -PLAYER_ORIENTATION = DIRECTION_TYPE[EAST] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] def student_code(): diff --git a/Cours 1/Lecon1/01_maze/task.yaml b/Cours 1 Code.org/Lecon 2/01_maze/task.yaml similarity index 84% rename from Cours 1/Lecon1/01_maze/task.yaml rename to Cours 1 Code.org/Lecon 2/01_maze/task.yaml index 22589ed..09c1c35 100644 --- a/Cours 1/Lecon1/01_maze/task.yaml +++ b/Cours 1 Code.org/Lecon 2/01_maze/task.yaml @@ -17,31 +17,16 @@ groups: false input_random: '0' limits: memory: '100' - time: '30' output: '2' + time: '30' name: Exercice 1 network_grading: false order: 0 problems: code: - options: - maxBlocks: '4' - visual: - position: left - scrollbars: true - sounds: true - media: /static/common/js/blockly/media/ - toolboxPosition: start - trashcan: true - css: true - oneBasedIndex: true - header: |- - .. image:: 01_maze/maze/small_static_avatar.png - :height: 40px - - **Chère personne. Moi zombie. Devoir... atteindre... tournesol.** toolbox: |- + turnLeft @@ -50,44 +35,72 @@ problems: turnRight - workspace: |- - - - + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + css: true + trashcan: true + toolboxPosition: start + sounds: true + maxBlocks: '4' files: - maze.js - interpreter.js type: blockly + name: '' blocks_files: - blocks.js - name: Exécution séquentielle + workspace: |- + + + + header: |- + .. image:: 01_maze/maze/small_static_avatar.png + :height: 40px + + **Chère personne. Moi zombie. Devoir... atteindre... tournesol.** stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: '0': - type: 2 - name: Cours1 - description: Exercice faisant partie du cours 1 + type: 0 + name: Base + id: '1' + description: Introduit un concept de base visible: false - id: '' '1': + id: '2' + description: Demande de créer une séquence d'instruction + type: 0 + name: Séquence + visible: false + '3': name: Normal visible: true description: Fait partie du parcours normal type: 2 id: '' - '2': - name: Challenge + '4': + type: 2 description: Fait partie du parcours challenge + name: Challenge visible: true + id: '' + '5': + visible: true + description: Fait partie du parcours pour élèves en difficulté + name: Facile type: 2 id: '' - '3': + '6': + description: '' + name: Lecon 2 type: 2 - name: Facile visible: true - description: Fait partie du parcours pour élèves en difficulté id: '' weight: 1.0 diff --git a/Cours 1/Lecon1/02_maze/public/blocks.js b/Cours 1 Code.org/Lecon 2/02_maze/public/blocks.js similarity index 100% rename from Cours 1/Lecon1/02_maze/public/blocks.js rename to Cours 1 Code.org/Lecon 2/02_maze/public/blocks.js diff --git a/Cours 1/Lecon1/02_maze/public/interpreter.js b/Cours 1 Code.org/Lecon 2/02_maze/public/interpreter.js similarity index 100% rename from Cours 1/Lecon1/02_maze/public/interpreter.js rename to Cours 1 Code.org/Lecon 2/02_maze/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 2/02_maze/public/maze.js b/Cours 1 Code.org/Lecon 2/02_maze/public/maze.js new file mode 100644 index 0000000..b3500e6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/02_maze/public/maze.js @@ -0,0 +1,912 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +var visuals_directory_path = task_directory_path+"maze/" + +Maze.SKIN = { + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: json.visuals.obstacleSound, + winSound: json.visuals.winSound, + crashSound: json.visuals.crashSound, + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + if (Maze.SKIN.graph) { + // Draw the grid lines. + // The grid lines are offset so that the lines pass through the centre of + // each square. A half-pixel offset is also added to as standard SVG + // practice to avoid blurriness. + var offset = Maze.SQUARE_SIZE / 2 + 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Draw the tiles making up the maze map. + + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - + finishIcon.getAttribute('width') / 2); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - + finishIcon.getAttribute('height')); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1/Lecon1/02_maze/public/maze/avatar.png b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/avatar.png similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/avatar.png rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/avatar.png diff --git a/Cours 1/Lecon1/02_maze/public/maze/background.png b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/background.png similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/background.png rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/background.png diff --git a/Cours 1/Lecon1/02_maze/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/failure.mp3 similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/failure.mp3 rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/failure.mp3 diff --git a/Cours 1/Lecon1/02_maze/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/failure.ogg similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/failure.ogg rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/failure.ogg diff --git a/Cours 1/Lecon1/02_maze/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/failure_avatar.png similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/failure_avatar.png rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/failure_avatar.png diff --git a/Cours 1/Lecon1/02_maze/public/maze/goal.gif b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/goal.gif similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/goal.gif rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/goal.gif diff --git a/Cours 1/Lecon1/02_maze/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/goalIdle.gif similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/goalIdle.gif rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/goalIdle.gif diff --git a/Cours 1/Lecon1/02_maze/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/maze_forever.gif similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/maze_forever.gif rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/maze_forever.gif diff --git a/Cours 1/Lecon1/02_maze/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/obstacle.gif similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/obstacle.gif rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/obstacle.gif diff --git a/Cours 1/Lecon1/02_maze/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/obstacle.mp3 similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/obstacle.mp3 rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/obstacle.mp3 diff --git a/Cours 1/Lecon1/02_maze/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/obstacle.ogg similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/obstacle.ogg rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/obstacle.ogg diff --git a/Cours 1/Lecon1/02_maze/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/obstacleIdle.gif similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/obstacleIdle.gif rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/obstacleIdle.gif diff --git a/Cours 1/Lecon1/02_maze/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/small_static_avatar.png similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/small_static_avatar.png rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/small_static_avatar.png diff --git a/Cours 1/Lecon1/02_maze/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/start.mp3 similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/start.mp3 rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/start.mp3 diff --git a/Cours 1/Lecon1/02_maze/public/maze/start.ogg b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/start.ogg similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/start.ogg rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/start.ogg diff --git a/Cours 1/Lecon1/02_maze/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/static_avatar.png similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/static_avatar.png rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/static_avatar.png diff --git a/Cours 1/Lecon1/02_maze/public/maze/tiles.png b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/tiles.png similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/tiles.png rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/tiles.png diff --git a/Cours 1/Lecon1/09_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/wall.mp3 similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/wall.mp3 rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/wall.mp3 diff --git a/Cours 1/Lecon1/09_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/wall.ogg similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/wall.ogg rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/wall.ogg diff --git a/Cours 1/Lecon1/02_maze/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/win.mp3 similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/win.mp3 rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/win.mp3 diff --git a/Cours 1/Lecon1/02_maze/public/maze/win.ogg b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/win.ogg similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/win.ogg rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/win.ogg diff --git a/Cours 1/Lecon1/02_maze/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 2/02_maze/public/maze/win_avatar.png similarity index 100% rename from Cours 1/Lecon1/02_maze/public/maze/win_avatar.png rename to Cours 1 Code.org/Lecon 2/02_maze/public/maze/win_avatar.png diff --git a/Cours 1 Code.org/Lecon 2/02_maze/public/maze_config.json b/Cours 1 Code.org/Lecon 2/02_maze/public/maze_config.json new file mode 100644 index 0000000..161a679 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/02_maze/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 3, 0, 0], + [0, 0, 0, 2, 1, 4, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1/Lecon1/09_maze/run b/Cours 1 Code.org/Lecon 2/02_maze/run similarity index 100% rename from Cours 1/Lecon1/09_maze/run rename to Cours 1 Code.org/Lecon 2/02_maze/run diff --git a/Cours 1/Lecon1/01_maze/student/maze.tpl.py b/Cours 1 Code.org/Lecon 2/02_maze/student/maze.tpl.py similarity index 89% rename from Cours 1/Lecon1/01_maze/student/maze.tpl.py rename to Cours 1 Code.org/Lecon 2/02_maze/student/maze.tpl.py index a277684..de0722d 100644 --- a/Cours 1/Lecon1/01_maze/student/maze.tpl.py +++ b/Cours 1 Code.org/Lecon 2/02_maze/student/maze.tpl.py @@ -2,19 +2,19 @@ This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. Try to forget the basic Python syntax for a while. ''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) class BadPathException(Exception): pass -MAP = [[0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 2, 1, 1, 3, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0]] +MAP = data["map"]["layout"][0] ROWS = len(MAP) COLS = len(MAP[0]) @@ -41,13 +41,7 @@ class BadPathException(Exception): FINISH = "FINISH" OBSTACLE = "OBSTACLE" -SQUARE_TYPE = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4 -} +SQUARE_TYPE = data["map"]["squareType"] PLAYER_POSITION = { 'x': None, @@ -68,10 +62,10 @@ class BadPathException(Exception): FINISH_POSITION['x'] = x FINISH_POSITION['y'] = y -EAST = "east" -SOUTH = "south" -WEST = "west" -NORTH = "north" +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" DIRECTION_TYPE = { NORTH: 0, @@ -99,7 +93,7 @@ class BadPathException(Exception): } } -PLAYER_ORIENTATION = DIRECTION_TYPE[EAST] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] def student_code(): diff --git a/Cours 1/Lecon1/02_maze/task.yaml b/Cours 1 Code.org/Lecon 2/02_maze/task.yaml similarity index 92% rename from Cours 1/Lecon1/02_maze/task.yaml rename to Cours 1 Code.org/Lecon 2/02_maze/task.yaml index 715d94a..44dde8e 100644 --- a/Cours 1/Lecon1/02_maze/task.yaml +++ b/Cours 1 Code.org/Lecon 2/02_maze/task.yaml @@ -18,84 +18,84 @@ network_grading: false order: 1 problems: code: - blocks_files: - - blocks.js + toolbox: |- + options: - trashcan: true - scrollbars: true - oneBasedIndex: true - maxBlocks: '5' - grid: - spacing: 20 - length: 3 - snap: true - colour: '#ccc' zoom: + scaleSpeed: 1.2 + controls: true maxScale: 3.0 minScale: 0.3 startScale: 1.0 - controls: true - scaleSpeed: 1.2 wheel: false - toolboxPosition: start + grid: + length: 3 + snap: true + spacing: 20 + colour: '#ccc' + scrollbars: true visual: position: left - css: true + oneBasedIndex: true media: /static/common/js/blockly/media/ + toolboxPosition: start + trashcan: true + css: true sounds: true + maxBlocks: '5' + type: blockly files: - maze.js - interpreter.js - name: Mouvements - toolbox: |- - - type: blockly - header: '' + name: '' + blocks_files: + - blocks.js workspace: + header: '' stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: '0': + description: Introduit un concept de base + type: 0 + name: Base + id: '1' + visible: false + '1': + id: '2' + description: Demande de créer une séquence d'instruction + type: 0 + name: Séquence + visible: false + '2': description: Fait partie du parcours challenge - visible: true name: Challenge + visible: true type: 2 id: '' - '1': + '4': type: 2 - name: Cours1 - description: Exercice faisant partie du cours 1 - visible: false - id: '' - '2': description: Fait partie du parcours pour élèves en difficulté - visible: true name: Facile - type: 2 + visible: true id: '' - '3': + '5': + visible: true description: Fait partie du parcours normal name: Normal - visible: true type: 2 id: '' - '4': - description: Introduit un concept de base + '6': + description: '' + name: Lecon 2 type: 2 - name: Base - visible: false - id: '' - '5': - name: Séquence - description: Demande de créer une séquence d'instruction - type: 2 - visible: false + visible: true id: '' weight: 1.0 diff --git a/Cours 1/Lecon1/03_maze/public/blocks.js b/Cours 1 Code.org/Lecon 2/03_maze/public/blocks.js similarity index 100% rename from Cours 1/Lecon1/03_maze/public/blocks.js rename to Cours 1 Code.org/Lecon 2/03_maze/public/blocks.js diff --git a/Cours 1/Lecon1/03_maze/public/interpreter.js b/Cours 1 Code.org/Lecon 2/03_maze/public/interpreter.js similarity index 100% rename from Cours 1/Lecon1/03_maze/public/interpreter.js rename to Cours 1 Code.org/Lecon 2/03_maze/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 2/03_maze/public/maze.js b/Cours 1 Code.org/Lecon 2/03_maze/public/maze.js new file mode 100644 index 0000000..b3500e6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/03_maze/public/maze.js @@ -0,0 +1,912 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +var visuals_directory_path = task_directory_path+"maze/" + +Maze.SKIN = { + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: json.visuals.obstacleSound, + winSound: json.visuals.winSound, + crashSound: json.visuals.crashSound, + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + if (Maze.SKIN.graph) { + // Draw the grid lines. + // The grid lines are offset so that the lines pass through the centre of + // each square. A half-pixel offset is also added to as standard SVG + // practice to avoid blurriness. + var offset = Maze.SQUARE_SIZE / 2 + 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Draw the tiles making up the maze map. + + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - + finishIcon.getAttribute('width') / 2); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - + finishIcon.getAttribute('height')); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1/Lecon1/03_maze/public/maze/avatar.png b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/avatar.png similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/avatar.png rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/avatar.png diff --git a/Cours 1/Lecon1/03_maze/public/maze/background.png b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/background.png similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/background.png rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/background.png diff --git a/Cours 1/Lecon1/03_maze/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/failure.mp3 similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/failure.mp3 rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/failure.mp3 diff --git a/Cours 1/Lecon1/03_maze/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/failure.ogg similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/failure.ogg rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/failure.ogg diff --git a/Cours 1/Lecon1/03_maze/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/failure_avatar.png similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/failure_avatar.png rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/failure_avatar.png diff --git a/Cours 1/Lecon1/03_maze/public/maze/goal.gif b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/goal.gif similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/goal.gif rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/goal.gif diff --git a/Cours 1/Lecon1/03_maze/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/goalIdle.gif similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/goalIdle.gif rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/goalIdle.gif diff --git a/Cours 1/Lecon1/03_maze/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/maze_forever.gif similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/maze_forever.gif rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/maze_forever.gif diff --git a/Cours 1/Lecon1/03_maze/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/obstacle.gif similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/obstacle.gif rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/obstacle.gif diff --git a/Cours 1/Lecon1/03_maze/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/obstacle.mp3 similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/obstacle.mp3 rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/obstacle.mp3 diff --git a/Cours 1/Lecon1/03_maze/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/obstacle.ogg similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/obstacle.ogg rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/obstacle.ogg diff --git a/Cours 1/Lecon1/03_maze/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/obstacleIdle.gif similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/obstacleIdle.gif rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/obstacleIdle.gif diff --git a/Cours 1/Lecon1/03_maze/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/small_static_avatar.png similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/small_static_avatar.png rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/small_static_avatar.png diff --git a/Cours 1/Lecon1/03_maze/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/start.mp3 similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/start.mp3 rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/start.mp3 diff --git a/Cours 1/Lecon1/03_maze/public/maze/start.ogg b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/start.ogg similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/start.ogg rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/start.ogg diff --git a/Cours 1/Lecon1/03_maze/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/static_avatar.png similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/static_avatar.png rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/static_avatar.png diff --git a/Cours 1/Lecon1/03_maze/public/maze/tiles.png b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/tiles.png similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/tiles.png rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/tiles.png diff --git a/Cours 1/Lecon1/10_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/wall.mp3 similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/wall.mp3 rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/wall.mp3 diff --git a/Cours 1/Lecon1/10_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/wall.ogg similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/wall.ogg rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/wall.ogg diff --git a/Cours 1/Lecon1/03_maze/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/win.mp3 similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/win.mp3 rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/win.mp3 diff --git a/Cours 1/Lecon1/03_maze/public/maze/win.ogg b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/win.ogg similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/win.ogg rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/win.ogg diff --git a/Cours 1/Lecon1/03_maze/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 2/03_maze/public/maze/win_avatar.png similarity index 100% rename from Cours 1/Lecon1/03_maze/public/maze/win_avatar.png rename to Cours 1 Code.org/Lecon 2/03_maze/public/maze/win_avatar.png diff --git a/Cours 1 Code.org/Lecon 2/03_maze/public/maze_config.json b/Cours 1 Code.org/Lecon 2/03_maze/public/maze_config.json new file mode 100644 index 0000000..f3c84b0 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/03_maze/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 2, 0, 0], + [0, 4, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 3, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"SOUTH", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1/Lecon1/10_maze/run b/Cours 1 Code.org/Lecon 2/03_maze/run similarity index 100% rename from Cours 1/Lecon1/10_maze/run rename to Cours 1 Code.org/Lecon 2/03_maze/run diff --git a/Cours 1/Lecon1/04_maze/student/maze.tpl.py b/Cours 1 Code.org/Lecon 2/03_maze/student/maze.tpl.py similarity index 89% rename from Cours 1/Lecon1/04_maze/student/maze.tpl.py rename to Cours 1 Code.org/Lecon 2/03_maze/student/maze.tpl.py index 7043541..de0722d 100644 --- a/Cours 1/Lecon1/04_maze/student/maze.tpl.py +++ b/Cours 1 Code.org/Lecon 2/03_maze/student/maze.tpl.py @@ -2,19 +2,19 @@ This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. Try to forget the basic Python syntax for a while. ''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) class BadPathException(Exception): pass -MAP = [[0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 1, 4, 0, 0, 0], - [0, 0, 0, 1, 0, 0, 0, 0], - [0, 2, 1, 1, 1, 1, 3, 0], - [0, 0, 0, 4, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0]] +MAP = data["map"]["layout"][0] ROWS = len(MAP) COLS = len(MAP[0]) @@ -41,13 +41,7 @@ class BadPathException(Exception): FINISH = "FINISH" OBSTACLE = "OBSTACLE" -SQUARE_TYPE = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4 -} +SQUARE_TYPE = data["map"]["squareType"] PLAYER_POSITION = { 'x': None, @@ -68,10 +62,10 @@ class BadPathException(Exception): FINISH_POSITION['x'] = x FINISH_POSITION['y'] = y -EAST = "east" -SOUTH = "south" -WEST = "west" -NORTH = "north" +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" DIRECTION_TYPE = { NORTH: 0, @@ -99,7 +93,7 @@ class BadPathException(Exception): } } -PLAYER_ORIENTATION = DIRECTION_TYPE[EAST] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] def student_code(): diff --git a/Cours 1/Lecon1/03_maze/task.yaml b/Cours 1 Code.org/Lecon 2/03_maze/task.yaml similarity index 83% rename from Cours 1/Lecon1/03_maze/task.yaml rename to Cours 1 Code.org/Lecon 2/03_maze/task.yaml index 8539a85..b269bd9 100644 --- a/Cours 1/Lecon1/03_maze/task.yaml +++ b/Cours 1 Code.org/Lecon 2/03_maze/task.yaml @@ -1,83 +1,77 @@ accessible: true author: Florian Thuin -context: '' +context: |- + .. image:: 01_maze/maze/small_static_avatar.png + :height: 40px + + **Devoir... atteindre... tournesol.** environment: default evaluate: best groups: false input_random: '0' limits: - output: '2' memory: '100' + output: '2' time: '30' name: Exercice 3 network_grading: false order: 2 problems: code: + toolbox: |- + options: zoom: - maxScale: 3.0 - startScale: 1.0 - controls: true scaleSpeed: 1.2 + controls: true + maxScale: 3.0 minScale: 0.3 + startScale: 1.0 wheel: false - trashcan: true - scrollbars: true - visual: - position: left - oneBasedIndex: true - maxBlocks: '9' grid: + length: 3 spacing: 20 snap: true - length: 3 colour: '#ccc' - toolboxPosition: start + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ css: true + toolboxPosition: start + trashcan: true sounds: true - media: /static/common/js/blockly/media/ - blocks_files: - - blocks.js + maxBlocks: '9' files: - maze.js - interpreter.js - name: Mouvements - toolbox: |- - type: blockly - header: |- - .. image:: 01_maze/maze/small_static_avatar.png - :height: 40px - - **Devoir... atteindre... tournesol.** + name: '' + blocks_files: + - blocks.js workspace: + header: '' stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: '0': - name: Cours1 - description: Exercice faisant partie du cours 1 - type: 2 + type: 0 + name: Séquence + id: '2' + description: Demande de créer une séquence d'instruction visible: false - id: '' - '1': + '2': type: 2 - name: Facile description: Fait partie du parcours pour élèves en difficulté + name: Facile visible: true id: '' - '2': - name: Séquence - description: Demande de créer une séquence d'instruction - type: 2 - visible: false - id: '' weight: 1.0 diff --git a/Cours 1/Lecon1/04_maze/public/blocks.js b/Cours 1 Code.org/Lecon 2/04_maze/public/blocks.js similarity index 100% rename from Cours 1/Lecon1/04_maze/public/blocks.js rename to Cours 1 Code.org/Lecon 2/04_maze/public/blocks.js diff --git a/Cours 1/Lecon1/04_maze/public/interpreter.js b/Cours 1 Code.org/Lecon 2/04_maze/public/interpreter.js similarity index 100% rename from Cours 1/Lecon1/04_maze/public/interpreter.js rename to Cours 1 Code.org/Lecon 2/04_maze/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 2/04_maze/public/maze.js b/Cours 1 Code.org/Lecon 2/04_maze/public/maze.js new file mode 100644 index 0000000..b3500e6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/04_maze/public/maze.js @@ -0,0 +1,912 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +var visuals_directory_path = task_directory_path+"maze/" + +Maze.SKIN = { + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: json.visuals.obstacleSound, + winSound: json.visuals.winSound, + crashSound: json.visuals.crashSound, + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + if (Maze.SKIN.graph) { + // Draw the grid lines. + // The grid lines are offset so that the lines pass through the centre of + // each square. A half-pixel offset is also added to as standard SVG + // practice to avoid blurriness. + var offset = Maze.SQUARE_SIZE / 2 + 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Draw the tiles making up the maze map. + + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - + finishIcon.getAttribute('width') / 2); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - + finishIcon.getAttribute('height')); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1/Lecon1/04_maze/public/maze/avatar.png b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/avatar.png similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/avatar.png rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/avatar.png diff --git a/Cours 1/Lecon1/04_maze/public/maze/background.png b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/background.png similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/background.png rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/background.png diff --git a/Cours 1/Lecon1/04_maze/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/failure.mp3 similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/failure.mp3 rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/failure.mp3 diff --git a/Cours 1/Lecon1/04_maze/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/failure.ogg similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/failure.ogg rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/failure.ogg diff --git a/Cours 1/Lecon1/04_maze/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/failure_avatar.png similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/failure_avatar.png rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/failure_avatar.png diff --git a/Cours 1/Lecon1/04_maze/public/maze/goal.gif b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/goal.gif similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/goal.gif rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/goal.gif diff --git a/Cours 1/Lecon1/04_maze/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/goalIdle.gif similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/goalIdle.gif rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/goalIdle.gif diff --git a/Cours 1/Lecon1/04_maze/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/maze_forever.gif similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/maze_forever.gif rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/maze_forever.gif diff --git a/Cours 1/Lecon1/04_maze/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/obstacle.gif similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/obstacle.gif rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/obstacle.gif diff --git a/Cours 1/Lecon1/04_maze/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/obstacle.mp3 similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/obstacle.mp3 rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/obstacle.mp3 diff --git a/Cours 1/Lecon1/04_maze/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/obstacle.ogg similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/obstacle.ogg rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/obstacle.ogg diff --git a/Cours 1/Lecon1/04_maze/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/obstacleIdle.gif similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/obstacleIdle.gif rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/obstacleIdle.gif diff --git a/Cours 1/Lecon1/04_maze/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/small_static_avatar.png similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/small_static_avatar.png rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/small_static_avatar.png diff --git a/Cours 1/Lecon1/04_maze/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/start.mp3 similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/start.mp3 rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/start.mp3 diff --git a/Cours 1/Lecon1/04_maze/public/maze/start.ogg b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/start.ogg similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/start.ogg rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/start.ogg diff --git a/Cours 1/Lecon1/04_maze/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/static_avatar.png similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/static_avatar.png rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/static_avatar.png diff --git a/Cours 1/Lecon1/04_maze/public/maze/tiles.png b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/tiles.png similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/tiles.png rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/tiles.png diff --git a/Cours 1/Lecon1/11_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/wall.mp3 similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/wall.mp3 rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/wall.mp3 diff --git a/Cours 1/Lecon1/11_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/wall.ogg similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/wall.ogg rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/wall.ogg diff --git a/Cours 1/Lecon1/04_maze/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/win.mp3 similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/win.mp3 rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/win.mp3 diff --git a/Cours 1/Lecon1/04_maze/public/maze/win.ogg b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/win.ogg similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/win.ogg rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/win.ogg diff --git a/Cours 1/Lecon1/04_maze/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 2/04_maze/public/maze/win_avatar.png similarity index 100% rename from Cours 1/Lecon1/04_maze/public/maze/win_avatar.png rename to Cours 1 Code.org/Lecon 2/04_maze/public/maze/win_avatar.png diff --git a/Cours 1 Code.org/Lecon 2/04_maze/public/maze_config.json b/Cours 1 Code.org/Lecon 2/04_maze/public/maze_config.json new file mode 100644 index 0000000..f1ffba2 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/04_maze/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 4, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0], + [0, 2, 1, 1, 1, 1, 3, 0], + [0, 0, 0, 4, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1/Lecon1/11_maze/run b/Cours 1 Code.org/Lecon 2/04_maze/run similarity index 100% rename from Cours 1/Lecon1/11_maze/run rename to Cours 1 Code.org/Lecon 2/04_maze/run diff --git a/Cours 1/Lecon1/07_maze/student/maze.tpl.py b/Cours 1 Code.org/Lecon 2/04_maze/student/maze.tpl.py similarity index 89% rename from Cours 1/Lecon1/07_maze/student/maze.tpl.py rename to Cours 1 Code.org/Lecon 2/04_maze/student/maze.tpl.py index 878580f..de0722d 100644 --- a/Cours 1/Lecon1/07_maze/student/maze.tpl.py +++ b/Cours 1 Code.org/Lecon 2/04_maze/student/maze.tpl.py @@ -2,19 +2,19 @@ This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. Try to forget the basic Python syntax for a while. ''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) class BadPathException(Exception): pass -MAP = [[0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 1, 1, 0, 0], - [0, 1, 0, 4, 0, 1, 0, 0], - [0, 1, 0, 1, 1, 1, 0, 0], - [0, 3, 0, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 0, 1, 2, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0]] +MAP = data["map"]["layout"][0] ROWS = len(MAP) COLS = len(MAP[0]) @@ -41,13 +41,7 @@ class BadPathException(Exception): FINISH = "FINISH" OBSTACLE = "OBSTACLE" -SQUARE_TYPE = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4 -} +SQUARE_TYPE = data["map"]["squareType"] PLAYER_POSITION = { 'x': None, @@ -68,10 +62,10 @@ class BadPathException(Exception): FINISH_POSITION['x'] = x FINISH_POSITION['y'] = y -EAST = "east" -SOUTH = "south" -WEST = "west" -NORTH = "north" +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" DIRECTION_TYPE = { NORTH: 0, @@ -99,7 +93,7 @@ class BadPathException(Exception): } } -PLAYER_ORIENTATION = DIRECTION_TYPE[WEST] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] def student_code(): diff --git a/Cours 1/Lecon1/04_maze/task.yaml b/Cours 1 Code.org/Lecon 2/04_maze/task.yaml similarity index 91% rename from Cours 1/Lecon1/04_maze/task.yaml rename to Cours 1 Code.org/Lecon 2/04_maze/task.yaml index 01e4496..be1e025 100644 --- a/Cours 1/Lecon1/04_maze/task.yaml +++ b/Cours 1 Code.org/Lecon 2/04_maze/task.yaml @@ -1,103 +1,103 @@ accessible: true author: Florian Thuin -context: '' +context: Essaie le bloc « Répéter » pour utiliser moins de blocs... environment: default evaluate: best groups: false input_random: '0' limits: - output: '2' memory: '100' + output: '2' time: '30' name: Exercice 4 network_grading: false order: 3 problems: code: - blocks_files: - - blocks.js + toolbox: |- + options: - trashcan: true - scrollbars: true - oneBasedIndex: true - maxBlocks: '2' - grid: - spacing: 20 - length: 3 - snap: true - colour: '#ccc' zoom: + scaleSpeed: 1.2 + controls: true maxScale: 3.0 minScale: 0.3 startScale: 1.0 - controls: true - scaleSpeed: 1.2 wheel: false - toolboxPosition: start - css: true + grid: + length: 3 + spacing: 20 + snap: true + colour: '#ccc' + scrollbars: true visual: position: left + oneBasedIndex: true media: /static/common/js/blockly/media/ + trashcan: true + toolboxPosition: start + css: true sounds: true + maxBlocks: '2' files: - maze.js - interpreter.js - name: Boucles et mouvements - toolbox: |- - type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + header: |- .. image:: 01_maze/maze/small_static_avatar.png :height: 40px **Essaie le bloc « Répéter » pour utiliser moins de blocs...** - workspace: |- - stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: '0': - description: Exercice faisant partie du cours 1 - name: Cours1 - type: 2 - visible: false - id: '' + description: utilise une boucle "répéter X fois" + type: 0 + visible: true + name: Boucle X fois + id: '3' '1': + id: '1' + description: Introduit un concept de base + type: 0 + name: Base + visible: false + '3': + description: Fait partie du parcours challenge type: 2 - visible: true name: Challenge - description: Fait partie du parcours challenge + visible: false id: '' - '2': - description: Fait partie du parcours pour élèves en difficulté - visible: true - name: Facile + '4': type: 2 + name: Facile + description: Fait partie du parcours pour élèves en difficulté + visible: false id: '' - '3': + '5': description: Fait partie du parcours normal name: Normal - visible: true type: 2 - id: '' - '4': - description: Introduit un concept de base - type: 2 - name: Base visible: false id: '' - '5': - name: Boucle X fois - description: utilise une boucle "répéter X fois" + '6': + description: '' type: 2 - visible: false + name: Lecon 2 + visible: true id: '' weight: 1.0 diff --git a/Cours 1/Lecon1/05_maze/public/blocks.js b/Cours 1 Code.org/Lecon 2/05_maze/public/blocks.js similarity index 100% rename from Cours 1/Lecon1/05_maze/public/blocks.js rename to Cours 1 Code.org/Lecon 2/05_maze/public/blocks.js diff --git a/Cours 1/Lecon1/05_maze/public/interpreter.js b/Cours 1 Code.org/Lecon 2/05_maze/public/interpreter.js similarity index 100% rename from Cours 1/Lecon1/05_maze/public/interpreter.js rename to Cours 1 Code.org/Lecon 2/05_maze/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 2/05_maze/public/maze.js b/Cours 1 Code.org/Lecon 2/05_maze/public/maze.js new file mode 100644 index 0000000..b3500e6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/05_maze/public/maze.js @@ -0,0 +1,912 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +var visuals_directory_path = task_directory_path+"maze/" + +Maze.SKIN = { + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: json.visuals.obstacleSound, + winSound: json.visuals.winSound, + crashSound: json.visuals.crashSound, + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + if (Maze.SKIN.graph) { + // Draw the grid lines. + // The grid lines are offset so that the lines pass through the centre of + // each square. A half-pixel offset is also added to as standard SVG + // practice to avoid blurriness. + var offset = Maze.SQUARE_SIZE / 2 + 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Draw the tiles making up the maze map. + + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - + finishIcon.getAttribute('width') / 2); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - + finishIcon.getAttribute('height')); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1/Lecon1/05_maze/public/maze/avatar.png b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/avatar.png similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/avatar.png rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/avatar.png diff --git a/Cours 1/Lecon1/05_maze/public/maze/background.png b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/background.png similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/background.png rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/background.png diff --git a/Cours 1/Lecon1/05_maze/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/failure.mp3 similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/failure.mp3 rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/failure.mp3 diff --git a/Cours 1/Lecon1/05_maze/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/failure.ogg similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/failure.ogg rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/failure.ogg diff --git a/Cours 1/Lecon1/05_maze/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/failure_avatar.png similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/failure_avatar.png rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/failure_avatar.png diff --git a/Cours 1/Lecon1/05_maze/public/maze/goal.gif b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/goal.gif similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/goal.gif rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/goal.gif diff --git a/Cours 1/Lecon1/05_maze/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/goalIdle.gif similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/goalIdle.gif rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/goalIdle.gif diff --git a/Cours 1/Lecon1/05_maze/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/maze_forever.gif similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/maze_forever.gif rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/maze_forever.gif diff --git a/Cours 1/Lecon1/05_maze/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/obstacle.gif similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/obstacle.gif rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/obstacle.gif diff --git a/Cours 1/Lecon1/05_maze/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/obstacle.mp3 similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/obstacle.mp3 rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/obstacle.mp3 diff --git a/Cours 1/Lecon1/05_maze/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/obstacle.ogg similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/obstacle.ogg rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/obstacle.ogg diff --git a/Cours 1/Lecon1/05_maze/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/obstacleIdle.gif similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/obstacleIdle.gif rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/obstacleIdle.gif diff --git a/Cours 1/Lecon1/05_maze/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/small_static_avatar.png similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/small_static_avatar.png rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/small_static_avatar.png diff --git a/Cours 1/Lecon1/05_maze/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/start.mp3 similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/start.mp3 rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/start.mp3 diff --git a/Cours 1/Lecon1/05_maze/public/maze/start.ogg b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/start.ogg similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/start.ogg rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/start.ogg diff --git a/Cours 1/Lecon1/05_maze/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/static_avatar.png similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/static_avatar.png rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/static_avatar.png diff --git a/Cours 1/Lecon1/05_maze/public/maze/tiles.png b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/tiles.png similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/tiles.png rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/tiles.png diff --git a/Cours 1/Lecon1/12_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/wall.mp3 similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/wall.mp3 rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/wall.mp3 diff --git a/Cours 1/Lecon1/12_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/wall.ogg similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/wall.ogg rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/wall.ogg diff --git a/Cours 1/Lecon1/05_maze/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/win.mp3 similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/win.mp3 rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/win.mp3 diff --git a/Cours 1/Lecon1/05_maze/public/maze/win.ogg b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/win.ogg similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/win.ogg rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/win.ogg diff --git a/Cours 1/Lecon1/05_maze/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 2/05_maze/public/maze/win_avatar.png similarity index 100% rename from Cours 1/Lecon1/05_maze/public/maze/win_avatar.png rename to Cours 1 Code.org/Lecon 2/05_maze/public/maze/win_avatar.png diff --git a/Cours 1 Code.org/Lecon 2/05_maze/public/maze_config.json b/Cours 1 Code.org/Lecon 2/05_maze/public/maze_config.json new file mode 100644 index 0000000..267d275 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/05_maze/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 2, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 4, 0, 1, 0, 0], + [0, 0, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 3, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1/Lecon1/12_maze/run b/Cours 1 Code.org/Lecon 2/05_maze/run similarity index 100% rename from Cours 1/Lecon1/12_maze/run rename to Cours 1 Code.org/Lecon 2/05_maze/run diff --git a/Cours 1 Code.org/Lecon 2/05_maze/student/maze.tpl.py b/Cours 1 Code.org/Lecon 2/05_maze/student/maze.tpl.py new file mode 100644 index 0000000..de0722d --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/05_maze/student/maze.tpl.py @@ -0,0 +1,185 @@ +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + + +class BadPathException(Exception): + pass + +MAP = data["map"]["layout"][0] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + global PLAYER_POSITION, FINISH_POSITION + if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: + return True + else: + return False + + +def notDone(): + return not isDone() + + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Il y a une erreur dans votre code.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1/Lecon1/05_maze/task.yaml b/Cours 1 Code.org/Lecon 2/05_maze/task.yaml similarity index 83% rename from Cours 1/Lecon1/05_maze/task.yaml rename to Cours 1 Code.org/Lecon 2/05_maze/task.yaml index 81ddf4b..fba20dd 100644 --- a/Cours 1/Lecon1/05_maze/task.yaml +++ b/Cours 1 Code.org/Lecon 2/05_maze/task.yaml @@ -1,97 +1,91 @@ accessible: true author: Florian Thuin -context: '' +context: |- + .. image:: 01_maze/maze/small_static_avatar.png + :height: 40px + + **Peux-tu résoudre cette énigme en utilisant le moins de blocs possible ?** environment: default evaluate: best groups: false input_random: '0' limits: - output: '2' memory: '100' + output: '2' time: '30' name: Exercice 5 network_grading: false order: 4 problems: code: - blocks_files: - - blocks.js + toolbox: |- + options: - trashcan: true - scrollbars: true - oneBasedIndex: true - maxBlocks: '6' - grid: - spacing: 20 - length: 3 - snap: true - colour: '#ccc' zoom: - maxScale: 3.0 + scaleSpeed: 1.2 + controls: true minScale: 0.3 + maxScale: 3.0 startScale: 1.0 - controls: true - scaleSpeed: 1.2 wheel: false - toolboxPosition: start + grid: + length: 3 + spacing: 20 + snap: true + colour: '#ccc' + scrollbars: true visual: position: left - css: true + oneBasedIndex: true media: /static/common/js/blockly/media/ + css: true + toolboxPosition: start + trashcan: true sounds: true + maxBlocks: '6' files: - maze.js - interpreter.js - name: Boucles - toolbox: |- - type: blockly - header: |- - .. image:: 01_maze/maze/small_static_avatar.png - :height: 40px - - **Peux-tu résoudre cette énigme en utilisant le moins de blocs possible ?** + name: '' + blocks_files: + - blocks.js workspace: |- + header: '' stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: '0': - description: Exercice faisant partie du cours 1 - name: Cours1 - type: 2 - visible: false - id: '' - '1': - type: 2 + type: 0 visible: true - name: Facile - description: Fait partie du parcours pour élèves en difficulté - id: '' - '2': - description: Fait partie du parcours normal - visible: true - name: Normal - type: 2 - id: '' - '3': + name: Boucle X fois + id: '3' + description: utilise une boucle "répéter X fois" + '1': + id: '2' description: Demande de créer une séquence d'instruction + type: 0 name: Séquence + visible: false + '3': + description: Fait partie du parcours pour élèves en difficulté type: 2 + name: Facile visible: false id: '' '4': - description: utilise une boucle "répéter X fois" type: 2 - name: Boucle X fois + name: Normal + description: Fait partie du parcours normal visible: false id: '' weight: 1.0 diff --git a/Cours 1/Lecon1/06_maze/public/blocks.js b/Cours 1 Code.org/Lecon 2/06_maze/public/blocks.js similarity index 100% rename from Cours 1/Lecon1/06_maze/public/blocks.js rename to Cours 1 Code.org/Lecon 2/06_maze/public/blocks.js diff --git a/Cours 1/Lecon1/06_maze/public/interpreter.js b/Cours 1 Code.org/Lecon 2/06_maze/public/interpreter.js similarity index 100% rename from Cours 1/Lecon1/06_maze/public/interpreter.js rename to Cours 1 Code.org/Lecon 2/06_maze/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 2/06_maze/public/maze.js b/Cours 1 Code.org/Lecon 2/06_maze/public/maze.js new file mode 100644 index 0000000..b3500e6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/06_maze/public/maze.js @@ -0,0 +1,912 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +var visuals_directory_path = task_directory_path+"maze/" + +Maze.SKIN = { + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: json.visuals.obstacleSound, + winSound: json.visuals.winSound, + crashSound: json.visuals.crashSound, + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + if (Maze.SKIN.graph) { + // Draw the grid lines. + // The grid lines are offset so that the lines pass through the centre of + // each square. A half-pixel offset is also added to as standard SVG + // practice to avoid blurriness. + var offset = Maze.SQUARE_SIZE / 2 + 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Draw the tiles making up the maze map. + + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - + finishIcon.getAttribute('width') / 2); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - + finishIcon.getAttribute('height')); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1/Lecon1/06_maze/public/maze/avatar.png b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/avatar.png similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/avatar.png rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/avatar.png diff --git a/Cours 1/Lecon1/06_maze/public/maze/background.png b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/background.png similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/background.png rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/background.png diff --git a/Cours 1/Lecon1/06_maze/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/failure.mp3 similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/failure.mp3 rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/failure.mp3 diff --git a/Cours 1/Lecon1/06_maze/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/failure.ogg similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/failure.ogg rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/failure.ogg diff --git a/Cours 1/Lecon1/06_maze/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/failure_avatar.png similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/failure_avatar.png rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/failure_avatar.png diff --git a/Cours 1/Lecon1/06_maze/public/maze/goal.gif b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/goal.gif similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/goal.gif rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/goal.gif diff --git a/Cours 1/Lecon1/06_maze/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/goalIdle.gif similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/goalIdle.gif rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/goalIdle.gif diff --git a/Cours 1/Lecon1/06_maze/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/maze_forever.gif similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/maze_forever.gif rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/maze_forever.gif diff --git a/Cours 1/Lecon1/06_maze/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/obstacle.gif similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/obstacle.gif rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/obstacle.gif diff --git a/Cours 1/Lecon1/06_maze/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/obstacle.mp3 similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/obstacle.mp3 rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/obstacle.mp3 diff --git a/Cours 1/Lecon1/06_maze/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/obstacle.ogg similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/obstacle.ogg rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/obstacle.ogg diff --git a/Cours 1/Lecon1/06_maze/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/obstacleIdle.gif similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/obstacleIdle.gif rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/obstacleIdle.gif diff --git a/Cours 1/Lecon1/06_maze/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/small_static_avatar.png similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/small_static_avatar.png rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/small_static_avatar.png diff --git a/Cours 1/Lecon1/06_maze/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/start.mp3 similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/start.mp3 rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/start.mp3 diff --git a/Cours 1/Lecon1/06_maze/public/maze/start.ogg b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/start.ogg similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/start.ogg rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/start.ogg diff --git a/Cours 1/Lecon1/06_maze/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/static_avatar.png similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/static_avatar.png rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/static_avatar.png diff --git a/Cours 1/Lecon1/06_maze/public/maze/tiles.png b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/tiles.png similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/tiles.png rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/tiles.png diff --git a/Cours 1 Code.org/Lecon 2/06_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/wall.mp3 new file mode 100755 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 2/06_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/wall.ogg new file mode 100755 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/wall.ogg differ diff --git a/Cours 1/Lecon1/06_maze/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/win.mp3 similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/win.mp3 rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/win.mp3 diff --git a/Cours 1/Lecon1/06_maze/public/maze/win.ogg b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/win.ogg similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/win.ogg rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/win.ogg diff --git a/Cours 1/Lecon1/06_maze/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 2/06_maze/public/maze/win_avatar.png similarity index 100% rename from Cours 1/Lecon1/06_maze/public/maze/win_avatar.png rename to Cours 1 Code.org/Lecon 2/06_maze/public/maze/win_avatar.png diff --git a/Cours 1 Code.org/Lecon 2/06_maze/public/maze_config.json b/Cours 1 Code.org/Lecon 2/06_maze/public/maze_config.json new file mode 100644 index 0000000..41d4a29 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/06_maze/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 0, 0], + [0, 1, 0, 4, 0, 1, 0, 0], + [0, 1, 0, 1, 1, 1, 0, 0], + [0, 3, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 2, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"WEST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 2/06_maze/run b/Cours 1 Code.org/Lecon 2/06_maze/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/06_maze/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 2/06_maze/student/maze.tpl.py b/Cours 1 Code.org/Lecon 2/06_maze/student/maze.tpl.py new file mode 100644 index 0000000..de0722d --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/06_maze/student/maze.tpl.py @@ -0,0 +1,185 @@ +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + + +class BadPathException(Exception): + pass + +MAP = data["map"]["layout"][0] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + global PLAYER_POSITION, FINISH_POSITION + if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: + return True + else: + return False + + +def notDone(): + return not isDone() + + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Il y a une erreur dans votre code.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1/Lecon1/07_maze/task.yaml b/Cours 1 Code.org/Lecon 2/06_maze/task.yaml similarity index 85% rename from Cours 1/Lecon1/07_maze/task.yaml rename to Cours 1 Code.org/Lecon 2/06_maze/task.yaml index cf9840d..4e8392e 100644 --- a/Cours 1/Lecon1/07_maze/task.yaml +++ b/Cours 1 Code.org/Lecon 2/06_maze/task.yaml @@ -1,102 +1,102 @@ accessible: true author: Florian Thuin -context: '' +context: |- + .. image:: 01_maze/maze/small_static_avatar.png + :height: 40px + + **Chère personne. Moi zombie. Devoir... atteindre... tournesol.** environment: default evaluate: best groups: false input_random: '0' limits: - output: '2' memory: '100' + output: '2' time: '30' -name: Exercice 7 +name: Exercice 6 network_grading: false order: 6 problems: code: - blocks_files: - - blocks.js + toolbox: |- + options: - trashcan: true - scrollbars: true - oneBasedIndex: true - maxBlocks: '10' - grid: - spacing: 20 - length: 3 - snap: true - colour: '#ccc' zoom: + scaleSpeed: 1.2 + controls: true maxScale: 3.0 minScale: 0.3 startScale: 1.0 - controls: true - scaleSpeed: 1.2 wheel: false - toolboxPosition: start + grid: + length: 3 + spacing: 20 + snap: true + colour: '#ccc' + scrollbars: true visual: position: left - css: true + oneBasedIndex: true media: /static/common/js/blockly/media/ + toolboxPosition: start + trashcan: true + css: true sounds: true + maxBlocks: '10' files: - maze.js - interpreter.js - name: Plusieurs boucles - toolbox: |- - type: blockly - header: |- - .. image:: 01_maze/maze/small_static_avatar.png - :height: 40px - - **Chère personne. Moi zombie. Devoir... atteindre... tournesol.** + name: '' + blocks_files: + - blocks.js workspace: + header: '' stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: '0': - description: utilise une boucle "répéter X fois" - name: Boucle X fois - type: 2 - visible: false - id: '' - '1': - type: 2 - name: Boucles imbriquées description: Demande de créer une boucle dans une boucle + type: 0 + name: Boucles imbriquées + id: '4' visible: false - id: '' - '2': - description: Exercice faisant partie du cours 1 - name: Cours1 - type: 2 - visible: false - id: '' + '1': + id: '3' + description: utilise une boucle "répéter X fois" + type: 0 + visible: true + name: Boucle X fois '3': description: Fait partie du parcours pour élèves en difficulté - name: Facile - visible: true type: 2 + name: Facile + visible: false id: '' '4': - description: Fait partie du parcours normal - visible: true type: 2 name: Normal + description: Fait partie du parcours normal + visible: false id: '' '5': - name: Séquence description: Demande de créer une séquence d'instruction + name: Séquence type: 2 visible: false id: '' + '6': + description: '' + type: 2 + name: Lecon 2 + visible: true + id: '' weight: 1.0 diff --git a/Cours 1/Lecon1/07_maze/public/blocks.js b/Cours 1 Code.org/Lecon 2/07_maze/public/blocks.js similarity index 100% rename from Cours 1/Lecon1/07_maze/public/blocks.js rename to Cours 1 Code.org/Lecon 2/07_maze/public/blocks.js diff --git a/Cours 1/Lecon1/07_maze/public/interpreter.js b/Cours 1 Code.org/Lecon 2/07_maze/public/interpreter.js similarity index 100% rename from Cours 1/Lecon1/07_maze/public/interpreter.js rename to Cours 1 Code.org/Lecon 2/07_maze/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 2/07_maze/public/maze.js b/Cours 1 Code.org/Lecon 2/07_maze/public/maze.js new file mode 100644 index 0000000..b3500e6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/07_maze/public/maze.js @@ -0,0 +1,912 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +var visuals_directory_path = task_directory_path+"maze/" + +Maze.SKIN = { + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: json.visuals.obstacleSound, + winSound: json.visuals.winSound, + crashSound: json.visuals.crashSound, + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + if (Maze.SKIN.graph) { + // Draw the grid lines. + // The grid lines are offset so that the lines pass through the centre of + // each square. A half-pixel offset is also added to as standard SVG + // practice to avoid blurriness. + var offset = Maze.SQUARE_SIZE / 2 + 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Draw the tiles making up the maze map. + + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - + finishIcon.getAttribute('width') / 2); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - + finishIcon.getAttribute('height')); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1/Lecon1/07_maze/public/maze/avatar.png b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/avatar.png similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/avatar.png rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/avatar.png diff --git a/Cours 1/Lecon1/07_maze/public/maze/background.png b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/background.png similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/background.png rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/background.png diff --git a/Cours 1/Lecon1/07_maze/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/failure.mp3 similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/failure.mp3 rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/failure.mp3 diff --git a/Cours 1/Lecon1/07_maze/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/failure.ogg similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/failure.ogg rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/failure.ogg diff --git a/Cours 1/Lecon1/07_maze/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/failure_avatar.png similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/failure_avatar.png rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/failure_avatar.png diff --git a/Cours 1/Lecon1/07_maze/public/maze/goal.gif b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/goal.gif similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/goal.gif rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/goal.gif diff --git a/Cours 1/Lecon1/07_maze/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/goalIdle.gif similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/goalIdle.gif rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/goalIdle.gif diff --git a/Cours 1/Lecon1/07_maze/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/maze_forever.gif similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/maze_forever.gif rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/maze_forever.gif diff --git a/Cours 1/Lecon1/07_maze/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/obstacle.gif similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/obstacle.gif rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/obstacle.gif diff --git a/Cours 1/Lecon1/07_maze/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/obstacle.mp3 similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/obstacle.mp3 rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/obstacle.mp3 diff --git a/Cours 1/Lecon1/07_maze/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/obstacle.ogg similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/obstacle.ogg rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/obstacle.ogg diff --git a/Cours 1/Lecon1/07_maze/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/obstacleIdle.gif similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/obstacleIdle.gif rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/obstacleIdle.gif diff --git a/Cours 1/Lecon1/07_maze/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/small_static_avatar.png similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/small_static_avatar.png rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/small_static_avatar.png diff --git a/Cours 1/Lecon1/07_maze/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/start.mp3 similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/start.mp3 rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/start.mp3 diff --git a/Cours 1/Lecon1/07_maze/public/maze/start.ogg b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/start.ogg similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/start.ogg rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/start.ogg diff --git a/Cours 1/Lecon1/07_maze/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/static_avatar.png similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/static_avatar.png rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/static_avatar.png diff --git a/Cours 1/Lecon1/07_maze/public/maze/tiles.png b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/tiles.png similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/tiles.png rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/tiles.png diff --git a/Cours 1 Code.org/Lecon 2/07_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/wall.mp3 new file mode 100755 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 2/07_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/wall.ogg new file mode 100755 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/wall.ogg differ diff --git a/Cours 1/Lecon1/07_maze/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/win.mp3 similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/win.mp3 rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/win.mp3 diff --git a/Cours 1/Lecon1/07_maze/public/maze/win.ogg b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/win.ogg similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/win.ogg rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/win.ogg diff --git a/Cours 1/Lecon1/07_maze/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 2/07_maze/public/maze/win_avatar.png similarity index 100% rename from Cours 1/Lecon1/07_maze/public/maze/win_avatar.png rename to Cours 1 Code.org/Lecon 2/07_maze/public/maze/win_avatar.png diff --git a/Cours 1 Code.org/Lecon 2/07_maze/public/maze_config.json b/Cours 1 Code.org/Lecon 2/07_maze/public/maze_config.json new file mode 100644 index 0000000..ed47860 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/07_maze/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 2, 0, 1, 1, 1, 0], + [0, 0, 1, 0, 1, 0, 1, 0], + [0, 0, 1, 0, 1, 4, 1, 0], + [0, 0, 1, 4, 1, 0, 1, 0], + [0, 0, 1, 0, 1, 0, 1, 0], + [0, 0, 1, 1, 1, 0, 3, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"SOUTH", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 2/07_maze/run b/Cours 1 Code.org/Lecon 2/07_maze/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/07_maze/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 2/07_maze/student/maze.tpl.py b/Cours 1 Code.org/Lecon 2/07_maze/student/maze.tpl.py new file mode 100644 index 0000000..de0722d --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/07_maze/student/maze.tpl.py @@ -0,0 +1,185 @@ +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + + +class BadPathException(Exception): + pass + +MAP = data["map"]["layout"][0] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + global PLAYER_POSITION, FINISH_POSITION + if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: + return True + else: + return False + + +def notDone(): + return not isDone() + + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Il y a une erreur dans votre code.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1/Lecon1/08_maze/task.yaml b/Cours 1 Code.org/Lecon 2/07_maze/task.yaml similarity index 85% rename from Cours 1/Lecon1/08_maze/task.yaml rename to Cours 1 Code.org/Lecon 2/07_maze/task.yaml index ae5a47f..ab5cfeb 100644 --- a/Cours 1/Lecon1/08_maze/task.yaml +++ b/Cours 1 Code.org/Lecon 2/07_maze/task.yaml @@ -1,90 +1,94 @@ accessible: true author: Florian Thuin -context: '' +context: |- + .. image:: 01_maze/maze/small_static_avatar.png + :height: 40px + + **Utilise le bloc « Répéter » pour amener le zombie au tournesol. (Évite les plantes carnivores!)** environment: default evaluate: best groups: false input_random: '0' limits: - output: '2' memory: '100' + output: '2' time: '30' -name: Exercice 8 +name: Exercice 7 network_grading: false order: 7 problems: code: + toolbox: |- + options: zoom: - maxScale: 3.0 + scaleSpeed: 1.2 + controls: true minScale: 0.3 + maxScale: 3.0 startScale: 1.0 - controls: true - scaleSpeed: 1.2 wheel: false - trashcan: true - scrollbars: true - oneBasedIndex: true - maxBlocks: '14' grid: - spacing: 20 length: 3 + spacing: 20 snap: true colour: '#ccc' - toolboxPosition: start + scrollbars: true visual: position: left - css: true + oneBasedIndex: true media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true sounds: true - blocks_files: - - blocks.js + maxBlocks: '14' files: - maze.js - interpreter.js - name: Plein de boucles ! - toolbox: |- - type: blockly + blocks_files: + - blocks.js + workspace: + name: '' header: |- .. image:: 01_maze/maze/small_static_avatar.png :height: 40px **Utilise le bloc « Répéter » pour amener le zombie au tournesol. (Évite les plantes carnivores!)** - workspace: stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: '0': + visible: true + type: 0 name: Boucle X fois + id: '3' description: utilise une boucle "répéter X fois" - type: 2 - visible: false - id: '' '1': - type: 2 - name: Cours1 - description: Exercice faisant partie du cours 1 + id: '2' + description: Demande de créer une séquence d'instruction + type: 0 + name: Séquence visible: false - id: '' - '2': + '3': description: Fait partie du parcours pour élèves en difficulté visible: true name: Facile type: 2 id: '' - '3': - description: Demande de créer une séquence d'instruction - name: Séquence + '4': type: 2 - visible: false + description: '' + name: Lecon 2 + visible: true id: '' weight: 1.0 diff --git a/Cours 1/Lecon1/08_maze/public/blocks.js b/Cours 1 Code.org/Lecon 2/08_maze/public/blocks.js similarity index 100% rename from Cours 1/Lecon1/08_maze/public/blocks.js rename to Cours 1 Code.org/Lecon 2/08_maze/public/blocks.js diff --git a/Cours 1/Lecon1/08_maze/public/interpreter.js b/Cours 1 Code.org/Lecon 2/08_maze/public/interpreter.js similarity index 100% rename from Cours 1/Lecon1/08_maze/public/interpreter.js rename to Cours 1 Code.org/Lecon 2/08_maze/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 2/08_maze/public/maze.js b/Cours 1 Code.org/Lecon 2/08_maze/public/maze.js new file mode 100644 index 0000000..b3500e6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/08_maze/public/maze.js @@ -0,0 +1,912 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +var visuals_directory_path = task_directory_path+"maze/" + +Maze.SKIN = { + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: json.visuals.obstacleSound, + winSound: json.visuals.winSound, + crashSound: json.visuals.crashSound, + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + if (Maze.SKIN.graph) { + // Draw the grid lines. + // The grid lines are offset so that the lines pass through the centre of + // each square. A half-pixel offset is also added to as standard SVG + // practice to avoid blurriness. + var offset = Maze.SQUARE_SIZE / 2 + 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Draw the tiles making up the maze map. + + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - + finishIcon.getAttribute('width') / 2); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - + finishIcon.getAttribute('height')); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1/Lecon1/08_maze/public/maze/avatar.png b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/avatar.png similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/avatar.png rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/avatar.png diff --git a/Cours 1/Lecon1/08_maze/public/maze/background.png b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/background.png similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/background.png rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/background.png diff --git a/Cours 1/Lecon1/08_maze/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/failure.mp3 similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/failure.mp3 rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/failure.mp3 diff --git a/Cours 1/Lecon1/08_maze/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/failure.ogg similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/failure.ogg rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/failure.ogg diff --git a/Cours 1/Lecon1/08_maze/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/failure_avatar.png similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/failure_avatar.png rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/failure_avatar.png diff --git a/Cours 1/Lecon1/08_maze/public/maze/goal.gif b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/goal.gif similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/goal.gif rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/goal.gif diff --git a/Cours 1/Lecon1/08_maze/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/goalIdle.gif similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/goalIdle.gif rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/goalIdle.gif diff --git a/Cours 1/Lecon1/08_maze/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/maze_forever.gif similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/maze_forever.gif rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/maze_forever.gif diff --git a/Cours 1/Lecon1/08_maze/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/obstacle.gif similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/obstacle.gif rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/obstacle.gif diff --git a/Cours 1/Lecon1/08_maze/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/obstacle.mp3 similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/obstacle.mp3 rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/obstacle.mp3 diff --git a/Cours 1/Lecon1/08_maze/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/obstacle.ogg similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/obstacle.ogg rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/obstacle.ogg diff --git a/Cours 1/Lecon1/08_maze/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/obstacleIdle.gif similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/obstacleIdle.gif rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/obstacleIdle.gif diff --git a/Cours 1/Lecon1/08_maze/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/small_static_avatar.png similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/small_static_avatar.png rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/small_static_avatar.png diff --git a/Cours 1/Lecon1/08_maze/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/start.mp3 similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/start.mp3 rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/start.mp3 diff --git a/Cours 1/Lecon1/08_maze/public/maze/start.ogg b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/start.ogg similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/start.ogg rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/start.ogg diff --git a/Cours 1/Lecon1/08_maze/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/static_avatar.png similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/static_avatar.png rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/static_avatar.png diff --git a/Cours 1/Lecon1/08_maze/public/maze/tiles.png b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/tiles.png similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/tiles.png rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/tiles.png diff --git a/Cours 1 Code.org/Lecon 2/08_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/wall.mp3 new file mode 100755 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 2/08_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/wall.ogg new file mode 100755 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/wall.ogg differ diff --git a/Cours 1/Lecon1/08_maze/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/win.mp3 similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/win.mp3 rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/win.mp3 diff --git a/Cours 1/Lecon1/08_maze/public/maze/win.ogg b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/win.ogg similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/win.ogg rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/win.ogg diff --git a/Cours 1/Lecon1/08_maze/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 2/08_maze/public/maze/win_avatar.png similarity index 100% rename from Cours 1/Lecon1/08_maze/public/maze/win_avatar.png rename to Cours 1 Code.org/Lecon 2/08_maze/public/maze/win_avatar.png diff --git a/Cours 1 Code.org/Lecon 2/08_maze/public/maze_config.json b/Cours 1 Code.org/Lecon 2/08_maze/public/maze_config.json new file mode 100644 index 0000000..97142d0 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/08_maze/public/maze_config.json @@ -0,0 +1,51 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 2, 1, 3, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]], + + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 2, 1, 1, 1, 1, 3, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 2/08_maze/run b/Cours 1 Code.org/Lecon 2/08_maze/run new file mode 100644 index 0000000..8836dfa --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/08_maze/run @@ -0,0 +1,34 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + #feedback.set_global_feedback("Vous avez résolu l'exercice. Vous avez fait"+make_output.replace("True","")+" pas.") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance") + #feedback.set_state(make_output) + feedback.set_problem_feedback(make_output,"code") diff --git a/Cours 1/Lecon1/03_maze/student/maze.tpl.py b/Cours 1 Code.org/Lecon 2/08_maze/student/maze.tpl.py similarity index 73% rename from Cours 1/Lecon1/03_maze/student/maze.tpl.py rename to Cours 1 Code.org/Lecon 2/08_maze/student/maze.tpl.py index 3d9aa1f..1ea9b01 100644 --- a/Cours 1/Lecon1/03_maze/student/maze.tpl.py +++ b/Cours 1 Code.org/Lecon 2/08_maze/student/maze.tpl.py @@ -2,22 +2,38 @@ This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. Try to forget the basic Python syntax for a while. ''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) class BadPathException(Exception): pass -MAP = [[0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 2, 0, 0], - [0, 4, 1, 1, 1, 1, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0], - [0, 0, 3, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0]] - -ROWS = len(MAP) -COLS = len(MAP[0]) +MAP = None + +ROWS = 0 +COLS = 0 + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y UNSET = "UNSET" SUCCESS = "SUCCESS" @@ -41,13 +57,7 @@ class BadPathException(Exception): FINISH = "FINISH" OBSTACLE = "OBSTACLE" -SQUARE_TYPE = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4 -} +SQUARE_TYPE = data["map"]["squareType"] PLAYER_POSITION = { 'x': None, @@ -59,19 +69,10 @@ class BadPathException(Exception): 'y': None } -for y in range(ROWS): - for x in range(COLS): - if MAP[y][x] == SQUARE_TYPE[START]: - PLAYER_POSITION['x'] = x - PLAYER_POSITION['y'] = y - if MAP[y][x] == SQUARE_TYPE[FINISH]: - FINISH_POSITION['x'] = x - FINISH_POSITION['y'] = y - -EAST = "east" -SOUTH = "south" -WEST = "west" -NORTH = "north" +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" DIRECTION_TYPE = { NORTH: 0, @@ -99,7 +100,7 @@ class BadPathException(Exception): } } -PLAYER_ORIENTATION = DIRECTION_TYPE[SOUTH] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] def student_code(): @@ -182,10 +183,13 @@ def notDone(): try: - student_code() - if isDone(): - print("True", end='', flush=True) - else: - print("Il y a une erreur dans votre code.", end='', flush=True) + for i in range(len(data["map"]["layout"])): + init(data["map"]["layout"][i]) + student_code() + if notDone(): + print(str(data["map"]["layout"][i]), end='', flush=True) + quit() + print("True", end='', flush=True) + except BadPathException: print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1/Lecon1/09_maze/task.yaml b/Cours 1 Code.org/Lecon 2/08_maze/task.yaml similarity index 87% rename from Cours 1/Lecon1/09_maze/task.yaml rename to Cours 1 Code.org/Lecon 2/08_maze/task.yaml index 88cace7..d376238 100644 --- a/Cours 1/Lecon1/09_maze/task.yaml +++ b/Cours 1 Code.org/Lecon 2/08_maze/task.yaml @@ -1,108 +1,111 @@ accessible: true author: Florian Thuin -context: '' +context: |- + Cette fois, utilise la boucle "répéter jusqu'à". + + Attention, ton code doit être capable de traverser autant de cases que nécéssaire jusqu'à la plante ! environment: default evaluate: best groups: false input_random: '0' limits: memory: '100' - output: '2' time: '30' -name: Exercice 9 + output: '2' +name: Exercice 8 network_grading: false order: 8 problems: code: - blocks_files: - - blocks.js + toolbox: |- + options: - trashcan: true scrollbars: true - oneBasedIndex: true - maxBlocks: '2' grid: - spacing: 20 length: 3 snap: true + spacing: 20 colour: '#ccc' + css: true + toolboxPosition: start zoom: + scaleSpeed: 1.2 + controls: true maxScale: 3.0 minScale: 0.3 startScale: 1.0 - controls: true - scaleSpeed: 1.2 wheel: false - toolboxPosition: start - css: true visual: position: left + oneBasedIndex: true media: /static/common/js/blockly/media/ + maxBlocks: '2' + trashcan: true sounds: true files: - maze.js - interpreter.js - name: 'Boucle : jusqu''à' type: blockly - toolbox: |- - + name: '' + blocks_files: + - blocks.js + workspace: header: |- .. image:: 01_maze/maze/small_static_avatar.png :height: 40px **Crée une boucle avec le bloc « Répéter jusqu’à ».** - workspace: stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: '0': - description: Introduit un concept de base - name: Base - type: 2 - visible: false - id: '' + type: 0 + visible: true + id: '5' + name: Boucle jusqu'à + description: Demande d'utiliser des boucles "jusqu'à" '1': - type: 2 + id: '1' visible: true - name: Challenge - description: Fait partie du parcours challenge - id: '' + name: Base + description: Introduit un concept de base + type: 0 '2': - description: Exercice faisant partie du cours 1 - name: Cours1 + visible: true + id: '2' + type: 0 + description: Demande de créer une séquence d'instruction + name: Séquence + '3': + description: Fait partie du parcours challenge + name: Challenge type: 2 visible: false id: '' - '3': + '5': description: Fait partie du parcours pour élèves en difficulté name: Facile - visible: true type: 2 + visible: false id: '' - '4': + '6': description: Fait partie du parcours normal - visible: true - type: 2 name: Normal - id: '' - '5': - name: Séquence - description: Demande de créer une séquence d'instruction type: 2 visible: false id: '' - '6': - name: Boucle jusqu'à + '7': + description: '' + visible: true + name: Lecon 2 type: 2 - description: Demande d'utiliser des boucles "jusqu'à" - visible: false id: '' weight: 1.0 diff --git a/Cours 1/Lecon1/10_maze/public/blocks.js b/Cours 1 Code.org/Lecon 2/09_maze/public/blocks.js similarity index 100% rename from Cours 1/Lecon1/10_maze/public/blocks.js rename to Cours 1 Code.org/Lecon 2/09_maze/public/blocks.js diff --git a/Cours 1/Lecon1/09_maze/public/interpreter.js b/Cours 1 Code.org/Lecon 2/09_maze/public/interpreter.js similarity index 100% rename from Cours 1/Lecon1/09_maze/public/interpreter.js rename to Cours 1 Code.org/Lecon 2/09_maze/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 2/09_maze/public/maze.js b/Cours 1 Code.org/Lecon 2/09_maze/public/maze.js new file mode 100644 index 0000000..b3500e6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/09_maze/public/maze.js @@ -0,0 +1,912 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +var visuals_directory_path = task_directory_path+"maze/" + +Maze.SKIN = { + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: json.visuals.obstacleSound, + winSound: json.visuals.winSound, + crashSound: json.visuals.crashSound, + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + if (Maze.SKIN.graph) { + // Draw the grid lines. + // The grid lines are offset so that the lines pass through the centre of + // each square. A half-pixel offset is also added to as standard SVG + // practice to avoid blurriness. + var offset = Maze.SQUARE_SIZE / 2 + 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Draw the tiles making up the maze map. + + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - + finishIcon.getAttribute('width') / 2); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - + finishIcon.getAttribute('height')); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1/Lecon1/09_maze/public/maze/avatar.png b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/avatar.png similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/avatar.png rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/avatar.png diff --git a/Cours 1/Lecon1/09_maze/public/maze/background.png b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/background.png similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/background.png rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/background.png diff --git a/Cours 1/Lecon1/09_maze/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/failure.mp3 similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/failure.mp3 rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/failure.mp3 diff --git a/Cours 1/Lecon1/09_maze/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/failure.ogg similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/failure.ogg rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/failure.ogg diff --git a/Cours 1/Lecon1/09_maze/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/failure_avatar.png similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/failure_avatar.png rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/failure_avatar.png diff --git a/Cours 1/Lecon1/09_maze/public/maze/goal.gif b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/goal.gif similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/goal.gif rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/goal.gif diff --git a/Cours 1/Lecon1/09_maze/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/goalIdle.gif similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/goalIdle.gif rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/goalIdle.gif diff --git a/Cours 1/Lecon1/09_maze/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/maze_forever.gif similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/maze_forever.gif rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/maze_forever.gif diff --git a/Cours 1/Lecon1/09_maze/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/obstacle.gif similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/obstacle.gif rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/obstacle.gif diff --git a/Cours 1/Lecon1/09_maze/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/obstacle.mp3 similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/obstacle.mp3 rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/obstacle.mp3 diff --git a/Cours 1/Lecon1/09_maze/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/obstacle.ogg similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/obstacle.ogg rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/obstacle.ogg diff --git a/Cours 1/Lecon1/09_maze/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/obstacleIdle.gif similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/obstacleIdle.gif rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/obstacleIdle.gif diff --git a/Cours 1/Lecon1/09_maze/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/small_static_avatar.png similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/small_static_avatar.png rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/small_static_avatar.png diff --git a/Cours 1/Lecon1/09_maze/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/start.mp3 similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/start.mp3 rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/start.mp3 diff --git a/Cours 1/Lecon1/09_maze/public/maze/start.ogg b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/start.ogg similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/start.ogg rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/start.ogg diff --git a/Cours 1/Lecon1/09_maze/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/static_avatar.png similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/static_avatar.png rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/static_avatar.png diff --git a/Cours 1/Lecon1/09_maze/public/maze/tiles.png b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/tiles.png similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/tiles.png rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/tiles.png diff --git a/Cours 1 Code.org/Lecon 2/09_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/wall.mp3 new file mode 100755 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 2/09_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/wall.ogg new file mode 100755 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/wall.ogg differ diff --git a/Cours 1/Lecon1/09_maze/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/win.mp3 similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/win.mp3 rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/win.mp3 diff --git a/Cours 1/Lecon1/09_maze/public/maze/win.ogg b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/win.ogg similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/win.ogg rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/win.ogg diff --git a/Cours 1/Lecon1/09_maze/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 2/09_maze/public/maze/win_avatar.png similarity index 100% rename from Cours 1/Lecon1/09_maze/public/maze/win_avatar.png rename to Cours 1 Code.org/Lecon 2/09_maze/public/maze/win_avatar.png diff --git a/Cours 1 Code.org/Lecon 2/09_maze/public/maze_config.json b/Cours 1 Code.org/Lecon 2/09_maze/public/maze_config.json new file mode 100644 index 0000000..5fe02c3 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/09_maze/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 2, 0, 3, 0, 0], + [0, 0, 0, 1, 0, 1, 0, 0], + [0, 0, 0, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"SOUTH", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 2/09_maze/run b/Cours 1 Code.org/Lecon 2/09_maze/run new file mode 100644 index 0000000..8836dfa --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/09_maze/run @@ -0,0 +1,34 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + #feedback.set_global_feedback("Vous avez résolu l'exercice. Vous avez fait"+make_output.replace("True","")+" pas.") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance") + #feedback.set_state(make_output) + feedback.set_problem_feedback(make_output,"code") diff --git a/Cours 1 Code.org/Lecon 2/09_maze/student/maze.tpl.py b/Cours 1 Code.org/Lecon 2/09_maze/student/maze.tpl.py new file mode 100644 index 0000000..1ea9b01 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/09_maze/student/maze.tpl.py @@ -0,0 +1,195 @@ +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + + +class BadPathException(Exception): + pass + +MAP = None + +ROWS = 0 +COLS = 0 + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + global PLAYER_POSITION, FINISH_POSITION + if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: + return True + else: + return False + + +def notDone(): + return not isDone() + + +try: + for i in range(len(data["map"]["layout"])): + init(data["map"]["layout"][i]) + student_code() + if notDone(): + print(str(data["map"]["layout"][i]), end='', flush=True) + quit() + print("True", end='', flush=True) + +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1/Lecon1/10_maze/task.yaml b/Cours 1 Code.org/Lecon 2/09_maze/task.yaml similarity index 85% rename from Cours 1/Lecon1/10_maze/task.yaml rename to Cours 1 Code.org/Lecon 2/09_maze/task.yaml index 7736fb9..15b1b7e 100644 --- a/Cours 1/Lecon1/10_maze/task.yaml +++ b/Cours 1 Code.org/Lecon 2/09_maze/task.yaml @@ -1,90 +1,94 @@ accessible: true author: Florian Thuin -context: '' +context: |- + .. image:: 01_maze/maze/small_static_avatar.png + :height: 40px + + **Peux-tu rejoindre le tournesol en utilisant uniquement quatre blocs ?** environment: default evaluate: best groups: false input_random: '0' limits: - output: '2' memory: '100' + output: '2' time: '30' -name: Exercice 10 +name: Exercice 9 network_grading: false order: 9 problems: code: + toolbox: |- + options: zoom: - maxScale: 3.0 + scaleSpeed: 1.2 + controls: true minScale: 0.3 + maxScale: 3.0 startScale: 1.0 - controls: true - scaleSpeed: 1.2 wheel: false - trashcan: true - scrollbars: true - oneBasedIndex: true - maxBlocks: '4' grid: - spacing: 20 length: 3 + spacing: 20 snap: true colour: '#ccc' - toolboxPosition: start + scrollbars: true visual: position: left - css: true + oneBasedIndex: true media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true sounds: true - blocks_files: - - blocks.js + maxBlocks: '4' files: - maze.js - interpreter.js - name: Boucles - toolbox: |- - type: blockly + name: '' + blocks_files: + - blocks.js + workspace: header: |- .. image:: 01_maze/maze/small_static_avatar.png :height: 40px **Peux-tu rejoindre le tournesol en utilisant uniquement quatre blocs ?** - workspace: stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: '0': + visible: true + type: 0 name: Boucle jusqu'à + id: '5' description: Demande d'utiliser des boucles "jusqu'à" - type: 2 - visible: false - id: '' '1': - type: 2 + id: '2' + description: Demande de créer une séquence d'instruction + type: 0 visible: true - name: Challenge - description: Fait partie du parcours challenge - id: '' + name: Séquence '2': - description: Exercice faisant partie du cours 1 - name: Cours1 type: 2 + description: Fait partie du parcours challenge + name: Challenge visible: false id: '' - '3': - description: Demande de créer une séquence d'instruction - name: Séquence + '4': type: 2 - visible: false + description: '' + name: Lecon 2 + visible: true id: '' weight: 1.0 diff --git a/Cours 1/Lecon1/11_maze/public/blocks.js b/Cours 1 Code.org/Lecon 2/10_maze/public/blocks.js similarity index 100% rename from Cours 1/Lecon1/11_maze/public/blocks.js rename to Cours 1 Code.org/Lecon 2/10_maze/public/blocks.js diff --git a/Cours 1/Lecon1/10_maze/public/interpreter.js b/Cours 1 Code.org/Lecon 2/10_maze/public/interpreter.js similarity index 100% rename from Cours 1/Lecon1/10_maze/public/interpreter.js rename to Cours 1 Code.org/Lecon 2/10_maze/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 2/10_maze/public/maze.js b/Cours 1 Code.org/Lecon 2/10_maze/public/maze.js new file mode 100644 index 0000000..b3500e6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/10_maze/public/maze.js @@ -0,0 +1,912 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +var visuals_directory_path = task_directory_path+"maze/" + +Maze.SKIN = { + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: json.visuals.obstacleSound, + winSound: json.visuals.winSound, + crashSound: json.visuals.crashSound, + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + if (Maze.SKIN.graph) { + // Draw the grid lines. + // The grid lines are offset so that the lines pass through the centre of + // each square. A half-pixel offset is also added to as standard SVG + // practice to avoid blurriness. + var offset = Maze.SQUARE_SIZE / 2 + 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Draw the tiles making up the maze map. + + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - + finishIcon.getAttribute('width') / 2); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - + finishIcon.getAttribute('height')); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1/Lecon1/10_maze/public/maze/avatar.png b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/avatar.png similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/avatar.png rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/avatar.png diff --git a/Cours 1/Lecon1/10_maze/public/maze/background.png b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/background.png similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/background.png rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/background.png diff --git a/Cours 1/Lecon1/10_maze/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/failure.mp3 similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/failure.mp3 rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/failure.mp3 diff --git a/Cours 1/Lecon1/10_maze/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/failure.ogg similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/failure.ogg rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/failure.ogg diff --git a/Cours 1/Lecon1/10_maze/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/failure_avatar.png similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/failure_avatar.png rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/failure_avatar.png diff --git a/Cours 1/Lecon1/10_maze/public/maze/goal.gif b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/goal.gif similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/goal.gif rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/goal.gif diff --git a/Cours 1/Lecon1/10_maze/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/goalIdle.gif similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/goalIdle.gif rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/goalIdle.gif diff --git a/Cours 1/Lecon1/10_maze/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/maze_forever.gif similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/maze_forever.gif rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/maze_forever.gif diff --git a/Cours 1/Lecon1/10_maze/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/obstacle.gif similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/obstacle.gif rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/obstacle.gif diff --git a/Cours 1/Lecon1/10_maze/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/obstacle.mp3 similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/obstacle.mp3 rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/obstacle.mp3 diff --git a/Cours 1/Lecon1/10_maze/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/obstacle.ogg similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/obstacle.ogg rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/obstacle.ogg diff --git a/Cours 1/Lecon1/10_maze/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/obstacleIdle.gif similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/obstacleIdle.gif rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/obstacleIdle.gif diff --git a/Cours 1/Lecon1/10_maze/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/small_static_avatar.png similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/small_static_avatar.png rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/small_static_avatar.png diff --git a/Cours 1/Lecon1/10_maze/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/start.mp3 similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/start.mp3 rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/start.mp3 diff --git a/Cours 1/Lecon1/10_maze/public/maze/start.ogg b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/start.ogg similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/start.ogg rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/start.ogg diff --git a/Cours 1/Lecon1/10_maze/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/static_avatar.png similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/static_avatar.png rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/static_avatar.png diff --git a/Cours 1/Lecon1/10_maze/public/maze/tiles.png b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/tiles.png similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/tiles.png rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/tiles.png diff --git a/Cours 1 Code.org/Lecon 2/10_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/wall.mp3 new file mode 100755 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 2/10_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/wall.ogg new file mode 100755 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/wall.ogg differ diff --git a/Cours 1/Lecon1/10_maze/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/win.mp3 similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/win.mp3 rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/win.mp3 diff --git a/Cours 1/Lecon1/10_maze/public/maze/win.ogg b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/win.ogg similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/win.ogg rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/win.ogg diff --git a/Cours 1/Lecon1/10_maze/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 2/10_maze/public/maze/win_avatar.png similarity index 100% rename from Cours 1/Lecon1/10_maze/public/maze/win_avatar.png rename to Cours 1 Code.org/Lecon 2/10_maze/public/maze/win_avatar.png diff --git a/Cours 1 Code.org/Lecon 2/10_maze/public/maze_config.json b/Cours 1 Code.org/Lecon 2/10_maze/public/maze_config.json new file mode 100644 index 0000000..00c53e2 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/10_maze/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 3, 0], + [0, 0, 0, 0, 0, 1, 1, 0], + [0, 0, 0, 0, 1, 1, 0, 0], + [0, 0, 0, 1, 1, 0, 0, 0], + [0, 0, 1, 1, 0, 0, 0, 0], + [0, 2, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"SOUTH", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 2/10_maze/run b/Cours 1 Code.org/Lecon 2/10_maze/run new file mode 100644 index 0000000..8836dfa --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/10_maze/run @@ -0,0 +1,34 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + #feedback.set_global_feedback("Vous avez résolu l'exercice. Vous avez fait"+make_output.replace("True","")+" pas.") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance") + #feedback.set_state(make_output) + feedback.set_problem_feedback(make_output,"code") diff --git a/Cours 1 Code.org/Lecon 2/10_maze/student/maze.tpl.py b/Cours 1 Code.org/Lecon 2/10_maze/student/maze.tpl.py new file mode 100644 index 0000000..1ea9b01 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/10_maze/student/maze.tpl.py @@ -0,0 +1,195 @@ +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + + +class BadPathException(Exception): + pass + +MAP = None + +ROWS = 0 +COLS = 0 + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + global PLAYER_POSITION, FINISH_POSITION + if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: + return True + else: + return False + + +def notDone(): + return not isDone() + + +try: + for i in range(len(data["map"]["layout"])): + init(data["map"]["layout"][i]) + student_code() + if notDone(): + print(str(data["map"]["layout"][i]), end='', flush=True) + quit() + print("True", end='', flush=True) + +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1/Lecon1/11_maze/task.yaml b/Cours 1 Code.org/Lecon 2/10_maze/task.yaml similarity index 89% rename from Cours 1/Lecon1/11_maze/task.yaml rename to Cours 1 Code.org/Lecon 2/10_maze/task.yaml index f754ece..fcf227a 100644 --- a/Cours 1/Lecon1/11_maze/task.yaml +++ b/Cours 1 Code.org/Lecon 2/10_maze/task.yaml @@ -1,102 +1,106 @@ accessible: true author: Florian Thuin -context: '' +context: |- + .. image:: 01_maze/maze/small_static_avatar.png + :height: 40px + + **Peux-tu aller au tournesol en utilisant uniquement six blocs?** environment: default evaluate: best groups: false input_random: '0' limits: - output: '2' memory: '100' + output: '2' time: '30' -name: Exercice 11 +name: Exercice 10 network_grading: false order: 10 problems: code: - blocks_files: - - blocks.js + toolbox: |- + options: - trashcan: true - scrollbars: true - oneBasedIndex: true - maxBlocks: '6' - grid: - spacing: 20 - length: 3 - snap: true - colour: '#ccc' zoom: - maxScale: 3.0 + scaleSpeed: 1.2 + controls: true minScale: 0.3 + maxScale: 3.0 startScale: 1.0 - controls: true - scaleSpeed: 1.2 wheel: false - toolboxPosition: start - css: true + grid: + length: 3 + spacing: 20 + snap: true + colour: '#ccc' + scrollbars: true visual: position: left + oneBasedIndex: true media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true sounds: true + maxBlocks: '6' + type: blockly files: - maze.js - interpreter.js - name: Boucles - toolbox: |- - - type: blockly + name: '' + blocks_files: + - blocks.js + workspace: header: |- .. image:: 01_maze/maze/small_static_avatar.png :height: 40px **Peux-tu aller au tournesol en utilisant uniquement six blocs?** - workspace: stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: '0': - description: Demande d'utiliser des boucles "jusqu'à" + type: 0 + visible: true name: Boucle jusqu'à - type: 2 - visible: false - id: '' + id: '5' + description: Demande d'utiliser des boucles "jusqu'à" '1': - type: 2 - name: Challenge - description: Fait partie du parcours challenge + id: '2' + description: Demande de créer une séquence d'instruction + type: 0 visible: true - id: '' + name: Séquence '2': - description: Exercice faisant partie du cours 1 - name: Cours1 type: 2 + description: Fait partie du parcours challenge + name: Challenge visible: false id: '' - '3': - description: Fait partie du parcours pour élèves en difficulté - name: Facile - visible: true - type: 2 - id: '' '4': - description: Fait partie du parcours normal - visible: true type: 2 - name: Normal + name: Facile + description: Fait partie du parcours pour élèves en difficulté + visible: false id: '' '5': - name: Séquence - description: Demande de créer une séquence d'instruction + description: Fait partie du parcours normal + name: Normal type: 2 visible: false id: '' + '6': + description: '' + type: 2 + name: Lecon 2 + visible: true + id: '' weight: 1.0 diff --git a/Cours 1/Lecon1/12_maze/public/blocks.js b/Cours 1 Code.org/Lecon 2/11_maze/public/blocks.js similarity index 100% rename from Cours 1/Lecon1/12_maze/public/blocks.js rename to Cours 1 Code.org/Lecon 2/11_maze/public/blocks.js diff --git a/Cours 1/Lecon1/11_maze/public/interpreter.js b/Cours 1 Code.org/Lecon 2/11_maze/public/interpreter.js similarity index 100% rename from Cours 1/Lecon1/11_maze/public/interpreter.js rename to Cours 1 Code.org/Lecon 2/11_maze/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 2/11_maze/public/maze.js b/Cours 1 Code.org/Lecon 2/11_maze/public/maze.js new file mode 100644 index 0000000..b3500e6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/11_maze/public/maze.js @@ -0,0 +1,912 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +var visuals_directory_path = task_directory_path+"maze/" + +Maze.SKIN = { + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: json.visuals.obstacleSound, + winSound: json.visuals.winSound, + crashSound: json.visuals.crashSound, + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + if (Maze.SKIN.graph) { + // Draw the grid lines. + // The grid lines are offset so that the lines pass through the centre of + // each square. A half-pixel offset is also added to as standard SVG + // practice to avoid blurriness. + var offset = Maze.SQUARE_SIZE / 2 + 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Draw the tiles making up the maze map. + + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - + finishIcon.getAttribute('width') / 2); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - + finishIcon.getAttribute('height')); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1/Lecon1/11_maze/public/maze/avatar.png b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/avatar.png similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/avatar.png rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/avatar.png diff --git a/Cours 1/Lecon1/11_maze/public/maze/background.png b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/background.png similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/background.png rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/background.png diff --git a/Cours 1/Lecon1/11_maze/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/failure.mp3 similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/failure.mp3 rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/failure.mp3 diff --git a/Cours 1/Lecon1/11_maze/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/failure.ogg similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/failure.ogg rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/failure.ogg diff --git a/Cours 1/Lecon1/11_maze/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/failure_avatar.png similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/failure_avatar.png rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/failure_avatar.png diff --git a/Cours 1/Lecon1/11_maze/public/maze/goal.gif b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/goal.gif similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/goal.gif rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/goal.gif diff --git a/Cours 1/Lecon1/11_maze/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/goalIdle.gif similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/goalIdle.gif rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/goalIdle.gif diff --git a/Cours 1/Lecon1/11_maze/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/maze_forever.gif similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/maze_forever.gif rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/maze_forever.gif diff --git a/Cours 1/Lecon1/11_maze/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/obstacle.gif similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/obstacle.gif rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/obstacle.gif diff --git a/Cours 1/Lecon1/11_maze/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/obstacle.mp3 similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/obstacle.mp3 rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/obstacle.mp3 diff --git a/Cours 1/Lecon1/11_maze/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/obstacle.ogg similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/obstacle.ogg rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/obstacle.ogg diff --git a/Cours 1/Lecon1/11_maze/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/obstacleIdle.gif similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/obstacleIdle.gif rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/obstacleIdle.gif diff --git a/Cours 1/Lecon1/11_maze/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/small_static_avatar.png similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/small_static_avatar.png rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/small_static_avatar.png diff --git a/Cours 1/Lecon1/11_maze/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/start.mp3 similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/start.mp3 rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/start.mp3 diff --git a/Cours 1/Lecon1/11_maze/public/maze/start.ogg b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/start.ogg similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/start.ogg rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/start.ogg diff --git a/Cours 1/Lecon1/11_maze/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/static_avatar.png similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/static_avatar.png rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/static_avatar.png diff --git a/Cours 1/Lecon1/11_maze/public/maze/tiles.png b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/tiles.png similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/tiles.png rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/tiles.png diff --git a/Cours 1 Code.org/Lecon 2/11_maze/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/wall.mp3 new file mode 100755 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 2/11_maze/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/wall.ogg new file mode 100755 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/wall.ogg differ diff --git a/Cours 1/Lecon1/11_maze/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/win.mp3 similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/win.mp3 rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/win.mp3 diff --git a/Cours 1/Lecon1/11_maze/public/maze/win.ogg b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/win.ogg similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/win.ogg rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/win.ogg diff --git a/Cours 1/Lecon1/11_maze/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 2/11_maze/public/maze/win_avatar.png similarity index 100% rename from Cours 1/Lecon1/11_maze/public/maze/win_avatar.png rename to Cours 1 Code.org/Lecon 2/11_maze/public/maze/win_avatar.png diff --git a/Cours 1 Code.org/Lecon 2/11_maze/public/maze_config.json b/Cours 1 Code.org/Lecon 2/11_maze/public/maze_config.json new file mode 100644 index 0000000..83ca014 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/11_maze/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 2, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 0, 0, 0, 0], + [0, 0, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 3, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"SOUTH", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 2/11_maze/run b/Cours 1 Code.org/Lecon 2/11_maze/run new file mode 100644 index 0000000..8836dfa --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/11_maze/run @@ -0,0 +1,34 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + #feedback.set_global_feedback("Vous avez résolu l'exercice. Vous avez fait"+make_output.replace("True","")+" pas.") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance") + #feedback.set_state(make_output) + feedback.set_problem_feedback(make_output,"code") diff --git a/Cours 1 Code.org/Lecon 2/11_maze/student/maze.tpl.py b/Cours 1 Code.org/Lecon 2/11_maze/student/maze.tpl.py new file mode 100644 index 0000000..1ea9b01 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/11_maze/student/maze.tpl.py @@ -0,0 +1,195 @@ +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + + +class BadPathException(Exception): + pass + +MAP = None + +ROWS = 0 +COLS = 0 + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + global PLAYER_POSITION, FINISH_POSITION + if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: + return True + else: + return False + + +def notDone(): + return not isDone() + + +try: + for i in range(len(data["map"]["layout"])): + init(data["map"]["layout"][i]) + student_code() + if notDone(): + print(str(data["map"]["layout"][i]), end='', flush=True) + quit() + print("True", end='', flush=True) + +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1/Lecon1/12_maze/task.yaml b/Cours 1 Code.org/Lecon 2/11_maze/task.yaml similarity index 85% rename from Cours 1/Lecon1/12_maze/task.yaml rename to Cours 1 Code.org/Lecon 2/11_maze/task.yaml index b267960..77312e9 100644 --- a/Cours 1/Lecon1/12_maze/task.yaml +++ b/Cours 1 Code.org/Lecon 2/11_maze/task.yaml @@ -1,90 +1,94 @@ accessible: true author: Florian Thuin -context: '' +context: |- + .. image:: 01_maze/maze/small_static_avatar.png + :height: 40px + + **OK, c'est légèrement différent. Peux-tu le faire en seulement six blocs ?** environment: default evaluate: best groups: false input_random: '0' limits: - output: '2' memory: '100' + output: '2' time: '30' -name: Exercice 12 +name: Exercice 11 network_grading: false order: 11 problems: code: + toolbox: |- + options: zoom: + scaleSpeed: 1.2 + controls: true maxScale: 3.0 minScale: 0.3 startScale: 1.0 - controls: true - scaleSpeed: 1.2 wheel: false - trashcan: true - scrollbars: true - oneBasedIndex: true - maxBlocks: '6' grid: - spacing: 20 length: 3 + spacing: 20 snap: true colour: '#ccc' - toolboxPosition: start + scrollbars: true visual: position: left - css: true + oneBasedIndex: true media: /static/common/js/blockly/media/ + css: true + toolboxPosition: start + trashcan: true sounds: true - blocks_files: - - blocks.js + maxBlocks: '6' files: - maze.js - interpreter.js - name: Boucle - toolbox: |- - type: blockly + name: '' + blocks_files: + - blocks.js + workspace: header: |- .. image:: 01_maze/maze/small_static_avatar.png :height: 40px **OK, c'est légèrement différent. Peux-tu le faire en seulement six blocs ?** - workspace: stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: '0': + visible: true name: Boucle jusqu'à + id: '5' + type: 0 description: Demande d'utiliser des boucles "jusqu'à" - type: 2 - visible: false - id: '' '1': - type: 2 - name: Cours1 - description: Exercice faisant partie du cours 1 - visible: false - id: '' - '2': - description: Fait partie du parcours pour élèves en difficulté + id: '2' + description: Demande de créer une séquence d'instruction + type: 0 visible: true + name: Séquence + '3': + description: Fait partie du parcours pour élèves en difficulté name: Facile type: 2 + visible: false id: '' - '3': - description: Demande de créer une séquence d'instruction - name: Séquence + '4': type: 2 - visible: false + description: '' + name: Lecon 2 + visible: true id: '' weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 2/course.yaml b/Cours 1 Code.org/Lecon 2/course.yaml new file mode 100644 index 0000000..f6c1089 --- /dev/null +++ b/Cours 1 Code.org/Lecon 2/course.yaml @@ -0,0 +1,18 @@ +accessible: true +name: Cours 2 - Maze +admins: +- '' +tutors: [] +groups_student_choice: false +use_classrooms: true +allow_unregister: true +allow_preview: false +registration: true +registration_password: null +registration_ac: null +registration_ac_list: +- '' +is_lti: false +lti_keys: {} +lti_send_back_grade: false +description: '' diff --git a/Cours 1/Lecon2/Artist_3/public/blocks.js b/Cours 1 Code.org/Lecon 3/Artist_1/public/blocks.js similarity index 100% rename from Cours 1/Lecon2/Artist_3/public/blocks.js rename to Cours 1 Code.org/Lecon 3/Artist_1/public/blocks.js diff --git a/Cours 1 Code.org/Lecon 3/Artist_1/public/create_img.py b/Cours 1 Code.org/Lecon 3/Artist_1/public/create_img.py new file mode 100644 index 0000000..be8c52a --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_1/public/create_img.py @@ -0,0 +1,99 @@ +import Image, ImageDraw +import math +import json +import os + +def moveForward(length): + global current_x, current_y, current_heading, penColour, current_width + next_x = current_x + int(length * math.sin(2 * math.pi * current_heading / 360)); + next_y = current_y - int(length * math.cos(2 * math.pi * current_heading / 360)); + if(penDown): + draw.line([current_x, current_y, next_x, next_y], penColour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, penColour, current_width + next_x = current_x - int(length * math.sin(2 * math.pi * current_heading / 360)); + next_y = current_y + int(length * math.cos(2 * math.pi * current_heading / 360)); + if(penDown): + draw.line([current_x, current_y, next_x, next_y], penColour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global penDown + penDown = False + +def penDown(): + global penDown + penDown = True + +def penColour(colour): + global penColour + penColour = colour + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +penDown = True +penColour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for i in range(4): + moveForward(250) + turnRight(90) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1/Lecon2/Artist_6/public/interpreter.js b/Cours 1 Code.org/Lecon 3/Artist_1/public/interpreter.js similarity index 100% rename from Cours 1/Lecon2/Artist_6/public/interpreter.js rename to Cours 1 Code.org/Lecon 3/Artist_1/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 3/Artist_1/public/solution.png b/Cours 1 Code.org/Lecon 3/Artist_1/public/solution.png new file mode 100644 index 0000000..997d16d Binary files /dev/null and b/Cours 1 Code.org/Lecon 3/Artist_1/public/solution.png differ diff --git a/Cours 1/Lecon2/Artist_4/public/turtle.js b/Cours 1 Code.org/Lecon 3/Artist_1/public/turtle.js similarity index 93% rename from Cours 1/Lecon2/Artist_4/public/turtle.js rename to Cours 1 Code.org/Lecon 3/Artist_1/public/turtle.js index ebc09ae..d29dc39 100644 --- a/Cours 1/Lecon2/Artist_4/public/turtle.js +++ b/Cours 1 Code.org/Lecon 3/Artist_1/public/turtle.js @@ -4,7 +4,7 @@ var task_directory_path = window.location.pathname + "/"; window.Turtle = {}; window.Maze = {}; -//Get the json file and it's information +//Get the json file and its informations var turtle_file = "" if(task_directory_path.includes("edit")){ //When we are editing the task turtle_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"turtle_config.json" @@ -16,27 +16,19 @@ request.open("GET", turtle_file, false); request.send(null) var json = JSON.parse(request.responseText); +var imagePath = "" +if(json.imageSolution) + imagePath = task_directory_path+json.imageName; + //Code of the solution var solution = function(){ //Here, put the javascript corresponding to the solved exercice - var i; - for(i = 0; i < 360; i++){ - Turtle.penColour(randomColour()); - Turtle.moveForward(100); - Turtle.moveBackwards(100); - Turtle.turnRight(1); - } } //Code of the decor var decoration = function(){ //Here, put the code for any decor, not part of the exercice } -var randomColour = function(){ - var colour = Math.floor(Math.random()*16777215); - return "#"+colour.toString(16).toUpperCase() -} - //Canvas size Turtle.CANVAS_WIDTH = json.width; Turtle.CANVAS_HEIGHT = json.height; @@ -108,8 +100,10 @@ Turtle.drawMap = function() { //Draw the decor Turtle.drawDecor(); - //Draw the solution - Turtle.drawSolution(); + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); //Draw the turtle Turtle.updateImage(); @@ -166,6 +160,21 @@ Turtle.resetTurtle = function(){ } +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + console.log(img) + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } + +} + + Turtle.drawSolution = function(){ var c = document.getElementById("solution-canvas"); var ctx = c.getContext("2d") diff --git a/Cours 1/Lecon2/Artist_1/public/turtle_config.json b/Cours 1 Code.org/Lecon 3/Artist_1/public/turtle_config.json similarity index 72% rename from Cours 1/Lecon2/Artist_1/public/turtle_config.json rename to Cours 1 Code.org/Lecon 3/Artist_1/public/turtle_config.json index 19f5162..9fc3e62 100644 --- a/Cours 1/Lecon2/Artist_1/public/turtle_config.json +++ b/Cours 1 Code.org/Lecon 3/Artist_1/public/turtle_config.json @@ -8,5 +8,7 @@ "radius":15, "animationRate":50, "width":290, - "height":290 + "height":290, + "imageSolution":true, + "imageName":"solution.png" } \ No newline at end of file diff --git a/Cours 1/Lecon2/Artist_6/run b/Cours 1 Code.org/Lecon 3/Artist_1/run similarity index 100% rename from Cours 1/Lecon2/Artist_6/run rename to Cours 1 Code.org/Lecon 3/Artist_1/run diff --git a/Cours 1/Lecon2/Artist_1/student/turtle.py b/Cours 1 Code.org/Lecon 3/Artist_1/student/turtle.py similarity index 100% rename from Cours 1/Lecon2/Artist_1/student/turtle.py rename to Cours 1 Code.org/Lecon 3/Artist_1/student/turtle.py diff --git a/Cours 1/Lecon2/Artist_1/task.yaml b/Cours 1 Code.org/Lecon 3/Artist_1/task.yaml similarity index 85% rename from Cours 1/Lecon2/Artist_1/task.yaml rename to Cours 1 Code.org/Lecon 3/Artist_1/task.yaml index 17ff300..963e233 100644 --- a/Cours 1/Lecon2/Artist_1/task.yaml +++ b/Cours 1 Code.org/Lecon 3/Artist_1/task.yaml @@ -6,38 +6,18 @@ evaluate: best groups: false input_random: '0' limits: - time: '30' memory: '100' output: '2' + time: '30' name: Exercice 1 network_grading: false order: 0 problems: code: - blocks_files: - - blocks.js - options: - maxBlocks: '45' - sounds: true - trashcan: true - visual: - position: left - media: /static/common/js/blockly/media/ - scrollbars: true - oneBasedIndex: true - toolboxPosition: start - css: true - files: - - turtle.js - - interpreter.js - header: 'Termine de dessiner la boîte. (Chaque ligne a une longueur de 250 - pixels) ' - workspace: |- - - - toolbox: |- + + moveForward @@ -55,59 +35,85 @@ problems: turnLeft 90 + + + - - penUp - - - 4 + + + + + ??? + + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: '45' + files: + - turtle.js + - interpreter.js type: blockly name: '' + blocks_files: + - blocks.js + workspace: |- + + + + header: 'Termine de dessiner la boîte. (Chaque ligne a une longueur de 250 + pixels) ' stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: - '4': - description: '' - name: Boucle répéter X fois - id: '1' + '0': type: 0 visible: true - '5': - visible: true - name: Instructions avec paramètres - type: 0 + name: Boucle répéter X fois + id: '1' + description: '' + '1': id: '2' description: '' - '0': + type: 0 visible: true - name: Lecon 2 - type: 2 - description: Exercice faisant partie de la leçon 2 - id: '' - '1': + name: Instructions avec paramètres + '2': description: Faisant partie du parcours normal - type: 2 name: Normal + type: 2 visible: false id: '' - '2': - description: Faisant partie du parcours facile + '3': name: Facile + description: Faisant partie du parcours facile type: 2 visible: false id: '' - '3': + '4': type: 2 description: Faisant partie du parcours challenge name: Challenge visible: false id: '' + '5': + visible: true + description: '' + name: Lecon 5 + type: 2 + id: '' weight: 1.0 diff --git a/Cours 1/Lecon2/Artist_4/public/blocks.js b/Cours 1 Code.org/Lecon 3/Artist_2/public/blocks.js similarity index 100% rename from Cours 1/Lecon2/Artist_4/public/blocks.js rename to Cours 1 Code.org/Lecon 3/Artist_2/public/blocks.js diff --git a/Cours 1 Code.org/Lecon 3/Artist_2/public/create_img.py b/Cours 1 Code.org/Lecon 3/Artist_2/public/create_img.py new file mode 100644 index 0000000..1eb0bd4 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_2/public/create_img.py @@ -0,0 +1,102 @@ +import Image, ImageDraw +import math +import json +import os +import random + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + int(length * math.sin(2 * math.pi * current_heading / 360)); + next_y = current_y - int(length * math.cos(2 * math.pi * current_heading / 360)); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - int(length * math.sin(2 * math.pi * current_heading / 360)); + next_y = current_y + int(length * math.cos(2 * math.pi * current_heading / 360)); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for i in range(2): + moveForward(200) + turnLeft(90) + moveForward(100) + turnLeft(90) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1/Lecon2/Artist_7/public/interpreter.js b/Cours 1 Code.org/Lecon 3/Artist_2/public/interpreter.js similarity index 100% rename from Cours 1/Lecon2/Artist_7/public/interpreter.js rename to Cours 1 Code.org/Lecon 3/Artist_2/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 3/Artist_2/public/solution.png b/Cours 1 Code.org/Lecon 3/Artist_2/public/solution.png new file mode 100644 index 0000000..c1b53a6 Binary files /dev/null and b/Cours 1 Code.org/Lecon 3/Artist_2/public/solution.png differ diff --git a/Cours 1/Lecon2/Artist_2/public/turtle.js b/Cours 1 Code.org/Lecon 3/Artist_2/public/turtle.js similarity index 90% rename from Cours 1/Lecon2/Artist_2/public/turtle.js rename to Cours 1 Code.org/Lecon 3/Artist_2/public/turtle.js index 43e33ba..3bb55b8 100644 --- a/Cours 1/Lecon2/Artist_2/public/turtle.js +++ b/Cours 1 Code.org/Lecon 3/Artist_2/public/turtle.js @@ -16,16 +16,13 @@ request.open("GET", turtle_file, false); request.send(null) var json = JSON.parse(request.responseText); +var imagePath = "" +if(json.imageSolution) //If there is a solution image, use it + imagePath = task_directory_path+json.imageName; + //Code of the solution var solution = function(){ - //Here, put the javascript corresponding to the solved exercice - var i; - for(i = 0; i < 2; i++){ - Turtle.move(200); - Turtle.turnLeft(90); - Turtle.move(100); - Turtle.turnLeft(90); - } + //Here, put the javascript corresponding to the solved exercice (or use the image) } //Code of the decor var decoration = function(){ @@ -51,7 +48,6 @@ var decoration = function(){ Turtle.turnRight(180); Turtle.penDown(); drawFlower(); - } var drawFlower = function(){ @@ -145,8 +141,10 @@ Turtle.drawMap = function() { //Draw the decor Turtle.drawDecor(); - //Draw the solution - Turtle.drawSolution(); + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); //Draw the turtle Turtle.updateImage(); @@ -203,6 +201,19 @@ Turtle.resetTurtle = function(){ } +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } + +} + Turtle.drawSolution = function(){ var c = document.getElementById("solution-canvas"); var ctx = c.getContext("2d") @@ -340,6 +351,21 @@ Turtle.moveBackwards = function(length){ Turtle.move(-length); } +Turtle.circle = function(radius){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + ctx.beginPath(); + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, radius, 0 , 2*Math.PI); + ctx.stroke(); + Turtle.updateImage(); +} + Turtle.turn = function(angle, direction){ switch (direction){ case 0: diff --git a/Cours 1/Lecon2/Artist_2/public/turtle_config.json b/Cours 1 Code.org/Lecon 3/Artist_2/public/turtle_config.json similarity index 71% rename from Cours 1/Lecon2/Artist_2/public/turtle_config.json rename to Cours 1 Code.org/Lecon 3/Artist_2/public/turtle_config.json index 4761980..e12c985 100644 --- a/Cours 1/Lecon2/Artist_2/public/turtle_config.json +++ b/Cours 1 Code.org/Lecon 3/Artist_2/public/turtle_config.json @@ -8,5 +8,7 @@ "radius":15, "animationRate":50, "width":300, - "height":300 + "height":300, + "imageSolution":true, + "imageName":"solution.png" } \ No newline at end of file diff --git a/Cours 1/Lecon2/Artist_7/run b/Cours 1 Code.org/Lecon 3/Artist_2/run similarity index 100% rename from Cours 1/Lecon2/Artist_7/run rename to Cours 1 Code.org/Lecon 3/Artist_2/run diff --git a/Cours 1/Lecon2/Artist_2/student/turtle.py b/Cours 1 Code.org/Lecon 3/Artist_2/student/turtle.py similarity index 100% rename from Cours 1/Lecon2/Artist_2/student/turtle.py rename to Cours 1 Code.org/Lecon 3/Artist_2/student/turtle.py diff --git a/Cours 1 Code.org/Lecon 3/Artist_2/task.yaml b/Cours 1 Code.org/Lecon 3/Artist_2/task.yaml new file mode 100644 index 0000000..f0fd191 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_2/task.yaml @@ -0,0 +1,114 @@ +accessible: true +author: Celine Deknop +context: |- + Ce parterre de fleurs rectangulaire a un périmètre de 600 pixels. Le côté le plus long a une longueur de 200 pixels. Dessine le rectangle. + Remarque : Tu ne dois pas repasser sur les lignes des fleurs, uniquement dessiner le rectangle +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + output: '2' + memory: '100' + time: '30' +name: Exercice 2 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: '45' + files: + - turtle.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + toolbox: |- + + + + + + moveForward + + + 100 + + + + + turnRight + 90 + + + turnLeft + 90 + + + + + + + + + + + + + + ??? + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + visible: true + name: Boucle répéter X fois + description: '' + id: '1' + '1': + id: '2' + description: '' + type: 0 + visible: true + name: Instructions avec paramètres + '2': + description: Faisant partie du parcours normal + name: Normal + type: 2 + visible: false + id: '' + '3': + name: Facile + description: Faisant partie du parcours facile + type: 2 + visible: false + id: '' + '4': + type: 2 + description: '' + name: Lecon5 + visible: true + id: '' +weight: 1.0 diff --git a/Cours 1/Lecon2/Artist_5/public/blocks.js b/Cours 1 Code.org/Lecon 3/Artist_3/public/blocks.js similarity index 100% rename from Cours 1/Lecon2/Artist_5/public/blocks.js rename to Cours 1 Code.org/Lecon 3/Artist_3/public/blocks.js diff --git a/Cours 1 Code.org/Lecon 3/Artist_3/public/create_img.py b/Cours 1 Code.org/Lecon 3/Artist_3/public/create_img.py new file mode 100644 index 0000000..41358a8 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_3/public/create_img.py @@ -0,0 +1,102 @@ +import Image, ImageDraw +import math +import json +import os +import random + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + int(length * math.sin(2 * math.pi * current_heading / 360)); + next_y = current_y - int(length * math.cos(2 * math.pi * current_heading / 360)); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - int(length * math.sin(2 * math.pi * current_heading / 360)); + next_y = current_y + int(length * math.cos(2 * math.pi * current_heading / 360)); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for i in range(8): + penColour(randomColour()) + moveForward(100) + moveBackward(100) + turnRight(45) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1 Code.org/Lecon 3/Artist_3/public/interpreter.js b/Cours 1 Code.org/Lecon 3/Artist_3/public/interpreter.js new file mode 100644 index 0000000..0e8e386 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_3/public/interpreter.js @@ -0,0 +1,53 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(length) { + Turtle.move(length); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.move(-length); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 0); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 1); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(width) { + Turtle.penWidth(width); + }; + interpreter.setProperty(scope, 'penWidth', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penUp(); + }; + interpreter.setProperty(scope, 'penUp', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penDown(); + }; + interpreter.setProperty(scope, 'penDown', + interpreter.createNativeFunction(wrapper)); + wrapper = function(colour) { + Turtle.penColour(colour); + }; + interpreter.setProperty(scope, 'penColour', + interpreter.createNativeFunction(wrapper)); + + + + + Turtle.log = []; + Turtle.reset(false); +}; + +var animate = function() { + Turtle.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 3/Artist_3/public/solution.png b/Cours 1 Code.org/Lecon 3/Artist_3/public/solution.png new file mode 100644 index 0000000..3111990 Binary files /dev/null and b/Cours 1 Code.org/Lecon 3/Artist_3/public/solution.png differ diff --git a/Cours 1 Code.org/Lecon 3/Artist_3/public/turtle.js b/Cours 1 Code.org/Lecon 3/Artist_3/public/turtle.js new file mode 100644 index 0000000..b1c17db --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_3/public/turtle.js @@ -0,0 +1,378 @@ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Turtle = {}; +window.Maze = {}; + +//Get the json file and it's information +var turtle_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + turtle_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"turtle_config.json" +}else { //When displaying the task + turtle_file = task_directory_path + "turtle_config.json"; +} +var request = new XMLHttpRequest(); +request.open("GET", turtle_file, false); +request.send(null) +var json = JSON.parse(request.responseText); + +var imagePath = "" +if(json.imageSolution) //If there is a solution image, use it + imagePath = task_directory_path+json.imageName; + +//Code of the solution +var solution = function(){ + //Here, put the javascript corresponding to the solved exercice (or use the image) +} +//Code of the decor +var decoration = function(){ + //Here, put the code for any decor, not part of the exercice +} + +var randomColour = function(){ + var colour = Math.floor(Math.random()*16777215); + return "#"+colour.toString(16).toUpperCase() +} + +//Canvas size +Turtle.CANVAS_WIDTH = json.width; +Turtle.CANVAS_HEIGHT = json.height; + +//Starting position and radius of turtle +Turtle.START_X = json.startX; +Turtle.START_Y = json.startY; +Turtle.RADIUS = json.radius; + +//Current coordinates of turtle +Turtle.CURRENT_COORD = { + x:Turtle.START_X, + y:Turtle.START_Y +} + +//Current heading of turtle +Turtle.HEADING = json.startAngle; + +//Weater or not the pen is down and it's width +Turtle.PEN_DOWN = true; //At start, the pen is always down +Turtle.PEN_WIDTH = json.strokeWidth; +Turtle.PEN_COLOUR = json.strokeColour; + +//Variables used to draw on the solution canvas or the decor canvas +var sol = false; +var decor = false; + +//milisec between each frame +window.stepSpeed = json.animationRate; + +/** +* +* Animations functions +* +*/ + +Turtle.drawMap = function() { + var div = document.getElementById('visualization'); + + // Add the canvas for the turtle + var canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute("style", "border:1px solid #F1EEE7;") + canvas.setAttribute("id", "turtle-canvas"); + div.appendChild(canvas); + // Add the canvas for the user. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "user-canvas"); + div.appendChild(canvas); + // Add the canvas for the solution. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "solution-canvas"); + div.appendChild(canvas); + //Add the canvas for the decor + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none') + canvas.setAttribute("id", "decor-canvas"); + div.appendChild(canvas) + + //Draw the decor + Turtle.drawDecor(); + + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); + + //Draw the turtle + Turtle.updateImage(); + + +} + +Turtle.drawTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + + //Draw the turtle body + ctx.beginPath(); + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, Turtle.RADIUS, 0, 2 * Math.PI); + ctx.stroke(); + + // Draw the turtle head. + var WIDTH = 0.3; + var HEAD_TIP = 10; + var ARROW_TIP = 4; + var BEND = 6; + var radians = 2 * Math.PI * Turtle.HEADING / 360; + var tipX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + HEAD_TIP) * Math.sin(radians); + var tipY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + HEAD_TIP) * Math.cos(radians); + radians -= WIDTH; + var leftX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var leftY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + radians += WIDTH / 2; + var leftControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var leftControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH; + var rightControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var rightControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH / 2; + var rightX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var rightY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + + ctx.beginPath(); + ctx.moveTo(tipX, tipY); + ctx.lineTo(leftX, leftY); + ctx.bezierCurveTo(leftControlX, leftControlY, + rightControlX, rightControlY, rightX, rightY); + ctx.closePath(); + ctx.fillStyle = Turtle.PEN_COLOUR; + ctx.fill(); +} + +Turtle.resetTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + //Clear any previous turtle + ctx.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + +} + +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } + +} + +Turtle.drawSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + sol = true; + solution(); + sol = false; + Turtle.reset(true); +} + +Turtle.drawDecor = function(){ + var c = document.getElementById("decor-canvas"); + var ctx = c.getContext("2d") + decor = true; + decoration(); + decor = false; + Turtle.reset(true); +} + +Turtle.animate = function(id) { + switch(id){ + case "move" : + Turtle.updateImage(); + break; + case "turn" : + Turtle.updateImage(); + break; + case "colour": + Turtle.updateImage(); + break; + default: + //This should not happen + console.warn("Unknown animation"); + break; + } +} + +Turtle.updateImage = function(){ + Turtle.resetTurtle(); + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d") + var c2 = document.getElementById("user-canvas"); + var c3 = document.getElementById("solution-canvas"); + var c4 = document.getElementById("decor-canvas"); + ctx1.drawImage(c4, 0, 0); //Fuse any decor + ctx1.drawImage(c3, 0, 0); //Fuse solution canvas + ctx1.drawImage(c2, 0, 0); //Fuse user canvas + Turtle.drawTurtle(); //Add the turtle +} + +Turtle.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Turtle.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + } + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,penUp,penDown,penWidth,penColour,'); + + Turtle.drawMap(); +}; + +Turtle.reset = function(bool){ + if(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + } + Turtle.updateImage(); +} + +//Workaround current plugin implementation that uses Maze as a variable +Maze.reset = function(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + if(!bool){ + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d"); + var c2 = document.getElementById("user-canvas"); + var ctx2 = c2.getContext("2d"); + ctx1.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + ctx2.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + } + Turtle.updateImage(); +} + +/** +* +* Blocks functions +* +*/ +Turtle.move = function(length){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + if (Turtle.PEN_DOWN) { + ctx.beginPath(); + ctx.moveTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + } + if(length){ + Turtle.CURRENT_COORD.x += length * Math.sin(2 * Math.PI * Turtle.HEADING / 360); + Turtle.CURRENT_COORD.y -= length * Math.cos(2 * Math.PI * Turtle.HEADING / 360); + } + if(Turtle.PEN_DOWN){ + ctx.lineWidth = Turtle.PEN_WIDTH; + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.lineTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + ctx.stroke(); + } + Turtle.animate("move"); +} + +Turtle.moveForward = function(length){ + Turtle.move(length); +} + + +Turtle.moveBackwards = function(length){ + Turtle.move(-length); +} + +Turtle.circle = function(radius){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + ctx.beginPath(); + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, radius, 0 , 2*Math.PI); + ctx.stroke(); + Turtle.updateImage(); +} + +Turtle.turn = function(angle, direction){ + switch (direction){ + case 0: + Turtle.HEADING = (Turtle.HEADING - angle) % 360; + if (Turtle.HEADING < 0) { + Turtle.HEADING += 360; + } + break; + case 1: + Turtle.HEADING = (Turtle.HEADING + angle) % 360; + break; + } + Turtle.animate("turn"); +} + +Turtle.turnRight = function(angle){ + Turtle.turn(angle, 1); +} + +Turtle.turnLeft = function(angle){ + Turtle.turn(angle, 0); +} + +Turtle.penWidth = function(width){ + Turtle.PEN_WIDTH = width; +} + +Turtle.penUp = function(){ + Turtle.PEN_DOWN = false; +} + +Turtle.penDown = function(){ + Turtle.PEN_DOWN = true; +} + +Turtle.penColour = function(colour){ + Turtle.PEN_COLOUR = colour; + Turtle.animate("colour"); +} + +//Called to draw +if (document.getElementById('visualization') != null) { + window.addEventListener('load', Turtle.init); +} else { + console.warn('Cannot find visualization element.'); +} \ No newline at end of file diff --git a/Cours 1/Lecon2/Artist_9/public/turtle_config.json b/Cours 1 Code.org/Lecon 3/Artist_3/public/turtle_config.json similarity index 72% rename from Cours 1/Lecon2/Artist_9/public/turtle_config.json rename to Cours 1 Code.org/Lecon 3/Artist_3/public/turtle_config.json index 8f9e50a..812b2c5 100644 --- a/Cours 1/Lecon2/Artist_9/public/turtle_config.json +++ b/Cours 1 Code.org/Lecon 3/Artist_3/public/turtle_config.json @@ -8,5 +8,7 @@ "radius":15, "animationRate":50, "width":300, - "height":300 + "height":300, + "imageSolution":true, + "imageName":"solution.png" } \ No newline at end of file diff --git a/Cours 1/Lecon2/Artist_8/run b/Cours 1 Code.org/Lecon 3/Artist_3/run similarity index 100% rename from Cours 1/Lecon2/Artist_8/run rename to Cours 1 Code.org/Lecon 3/Artist_3/run diff --git a/Cours 1/Lecon2/Artist_4/student/turtle.py b/Cours 1 Code.org/Lecon 3/Artist_3/student/turtle.py similarity index 98% rename from Cours 1/Lecon2/Artist_4/student/turtle.py rename to Cours 1 Code.org/Lecon 3/Artist_3/student/turtle.py index f477297..9d1e846 100644 --- a/Cours 1/Lecon2/Artist_4/student/turtle.py +++ b/Cours 1 Code.org/Lecon 3/Artist_3/student/turtle.py @@ -18,11 +18,11 @@ def student_code(): #Code of the solution def solution(): - for i in range(360): + for i in range(8): penColour(randomColour()) moveForward(100) moveBackward(100) - turnRight(1) + turnRight(45) def randomColour(): colour = "%06x" % random.randint(0, 0xFFFFFF) diff --git a/Cours 1/Lecon2/Artist_3/task.yaml b/Cours 1 Code.org/Lecon 3/Artist_3/task.yaml similarity index 81% rename from Cours 1/Lecon2/Artist_3/task.yaml rename to Cours 1 Code.org/Lecon 3/Artist_3/task.yaml index 2d8cbe6..e5140c6 100644 --- a/Cours 1/Lecon2/Artist_3/task.yaml +++ b/Cours 1 Code.org/Lecon 3/Artist_3/task.yaml @@ -8,30 +8,68 @@ groups: false input_random: '0' limits: memory: '100' - time: '30' output: '2' + time: '30' name: Exercice 3 network_grading: false order: 0 problems: code: - blocks_files: - - blocks.js + toolbox: |- + + + + + + moveForward + + + 100 + + + + + turnRight + 90 + + + turnLeft + 90 + + + + + + + + + + + + + + ??? + + + options: - maxBlocks: '45' - sounds: true - trashcan: true + scrollbars: true visual: position: left - media: /static/common/js/blockly/media/ oneBasedIndex: true + media: /static/common/js/blockly/media/ toolboxPosition: start - scrollbars: true css: true + trashcan: true + sounds: true + maxBlocks: '45' files: - turtle.js - interpreter.js type: blockly + name: '' + blocks_files: + - blocks.js workspace: |- @@ -60,71 +98,45 @@ problems: header: '' - toolbox: |- - - - - moveForward - - - 100 - - - - - turnRight - 90 - - - - - - - - penUp - - - - - 0 - - - - - name: '' stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: - '3': + '0': type: 0 - id: '2' visible: true - description: '' name: Instructions avec paramètres - '4': + id: '2' description: '' - name: Boucle "répéter X fois" + '1': id: '1' + description: '' type: 0 visible: true - '0': - visible: true - name: Lecon 2 - type: 2 - description: Exercice faisant partie de la leçon 2 - id: '' - '1': + name: Boucle "répéter X fois" + '2': description: Faisant partie du parcours normal - type: 2 name: Normal + type: 2 visible: false id: '' - '2': - description: Faisant partie du parcours facile + '3': name: Facile + description: Faisant partie du parcours facile + type: 2 + visible: false + id: '' + '4': type: 2 + description: Faisant partie du parcours challenge + name: Challenge visible: false id: '' + '5': + visible: true + description: '' + name: Lecon5 + type: 2 + id: '' weight: 1.0 diff --git a/Cours 1/Lecon2/Artist_6/public/blocks.js b/Cours 1 Code.org/Lecon 3/Artist_4/public/blocks.js similarity index 100% rename from Cours 1/Lecon2/Artist_6/public/blocks.js rename to Cours 1 Code.org/Lecon 3/Artist_4/public/blocks.js diff --git a/Cours 1 Code.org/Lecon 3/Artist_4/public/create_img.py b/Cours 1 Code.org/Lecon 3/Artist_4/public/create_img.py new file mode 100644 index 0000000..f911634 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_4/public/create_img.py @@ -0,0 +1,102 @@ +import Image, ImageDraw +import math +import json +import os +import random + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + int(length * math.sin(2 * math.pi * current_heading / 360)); + next_y = current_y - int(length * math.cos(2 * math.pi * current_heading / 360)); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - int(length * math.sin(2 * math.pi * current_heading / 360)); + next_y = current_y + int(length * math.cos(2 * math.pi * current_heading / 360)); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for i in range(360): + penColour(randomColour()) + moveForward(100) + moveBackward(100) + turnRight(1) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1 Code.org/Lecon 3/Artist_4/public/interpreter.js b/Cours 1 Code.org/Lecon 3/Artist_4/public/interpreter.js new file mode 100644 index 0000000..0e8e386 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_4/public/interpreter.js @@ -0,0 +1,53 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(length) { + Turtle.move(length); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.move(-length); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 0); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 1); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(width) { + Turtle.penWidth(width); + }; + interpreter.setProperty(scope, 'penWidth', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penUp(); + }; + interpreter.setProperty(scope, 'penUp', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penDown(); + }; + interpreter.setProperty(scope, 'penDown', + interpreter.createNativeFunction(wrapper)); + wrapper = function(colour) { + Turtle.penColour(colour); + }; + interpreter.setProperty(scope, 'penColour', + interpreter.createNativeFunction(wrapper)); + + + + + Turtle.log = []; + Turtle.reset(false); +}; + +var animate = function() { + Turtle.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 3/Artist_4/public/solution.png b/Cours 1 Code.org/Lecon 3/Artist_4/public/solution.png new file mode 100644 index 0000000..e7b7c9c Binary files /dev/null and b/Cours 1 Code.org/Lecon 3/Artist_4/public/solution.png differ diff --git a/Cours 1 Code.org/Lecon 3/Artist_4/public/turtle.js b/Cours 1 Code.org/Lecon 3/Artist_4/public/turtle.js new file mode 100644 index 0000000..b1c17db --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_4/public/turtle.js @@ -0,0 +1,378 @@ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Turtle = {}; +window.Maze = {}; + +//Get the json file and it's information +var turtle_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + turtle_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"turtle_config.json" +}else { //When displaying the task + turtle_file = task_directory_path + "turtle_config.json"; +} +var request = new XMLHttpRequest(); +request.open("GET", turtle_file, false); +request.send(null) +var json = JSON.parse(request.responseText); + +var imagePath = "" +if(json.imageSolution) //If there is a solution image, use it + imagePath = task_directory_path+json.imageName; + +//Code of the solution +var solution = function(){ + //Here, put the javascript corresponding to the solved exercice (or use the image) +} +//Code of the decor +var decoration = function(){ + //Here, put the code for any decor, not part of the exercice +} + +var randomColour = function(){ + var colour = Math.floor(Math.random()*16777215); + return "#"+colour.toString(16).toUpperCase() +} + +//Canvas size +Turtle.CANVAS_WIDTH = json.width; +Turtle.CANVAS_HEIGHT = json.height; + +//Starting position and radius of turtle +Turtle.START_X = json.startX; +Turtle.START_Y = json.startY; +Turtle.RADIUS = json.radius; + +//Current coordinates of turtle +Turtle.CURRENT_COORD = { + x:Turtle.START_X, + y:Turtle.START_Y +} + +//Current heading of turtle +Turtle.HEADING = json.startAngle; + +//Weater or not the pen is down and it's width +Turtle.PEN_DOWN = true; //At start, the pen is always down +Turtle.PEN_WIDTH = json.strokeWidth; +Turtle.PEN_COLOUR = json.strokeColour; + +//Variables used to draw on the solution canvas or the decor canvas +var sol = false; +var decor = false; + +//milisec between each frame +window.stepSpeed = json.animationRate; + +/** +* +* Animations functions +* +*/ + +Turtle.drawMap = function() { + var div = document.getElementById('visualization'); + + // Add the canvas for the turtle + var canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute("style", "border:1px solid #F1EEE7;") + canvas.setAttribute("id", "turtle-canvas"); + div.appendChild(canvas); + // Add the canvas for the user. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "user-canvas"); + div.appendChild(canvas); + // Add the canvas for the solution. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "solution-canvas"); + div.appendChild(canvas); + //Add the canvas for the decor + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none') + canvas.setAttribute("id", "decor-canvas"); + div.appendChild(canvas) + + //Draw the decor + Turtle.drawDecor(); + + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); + + //Draw the turtle + Turtle.updateImage(); + + +} + +Turtle.drawTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + + //Draw the turtle body + ctx.beginPath(); + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, Turtle.RADIUS, 0, 2 * Math.PI); + ctx.stroke(); + + // Draw the turtle head. + var WIDTH = 0.3; + var HEAD_TIP = 10; + var ARROW_TIP = 4; + var BEND = 6; + var radians = 2 * Math.PI * Turtle.HEADING / 360; + var tipX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + HEAD_TIP) * Math.sin(radians); + var tipY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + HEAD_TIP) * Math.cos(radians); + radians -= WIDTH; + var leftX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var leftY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + radians += WIDTH / 2; + var leftControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var leftControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH; + var rightControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var rightControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH / 2; + var rightX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var rightY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + + ctx.beginPath(); + ctx.moveTo(tipX, tipY); + ctx.lineTo(leftX, leftY); + ctx.bezierCurveTo(leftControlX, leftControlY, + rightControlX, rightControlY, rightX, rightY); + ctx.closePath(); + ctx.fillStyle = Turtle.PEN_COLOUR; + ctx.fill(); +} + +Turtle.resetTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + //Clear any previous turtle + ctx.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + +} + +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } + +} + +Turtle.drawSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + sol = true; + solution(); + sol = false; + Turtle.reset(true); +} + +Turtle.drawDecor = function(){ + var c = document.getElementById("decor-canvas"); + var ctx = c.getContext("2d") + decor = true; + decoration(); + decor = false; + Turtle.reset(true); +} + +Turtle.animate = function(id) { + switch(id){ + case "move" : + Turtle.updateImage(); + break; + case "turn" : + Turtle.updateImage(); + break; + case "colour": + Turtle.updateImage(); + break; + default: + //This should not happen + console.warn("Unknown animation"); + break; + } +} + +Turtle.updateImage = function(){ + Turtle.resetTurtle(); + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d") + var c2 = document.getElementById("user-canvas"); + var c3 = document.getElementById("solution-canvas"); + var c4 = document.getElementById("decor-canvas"); + ctx1.drawImage(c4, 0, 0); //Fuse any decor + ctx1.drawImage(c3, 0, 0); //Fuse solution canvas + ctx1.drawImage(c2, 0, 0); //Fuse user canvas + Turtle.drawTurtle(); //Add the turtle +} + +Turtle.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Turtle.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + } + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,penUp,penDown,penWidth,penColour,'); + + Turtle.drawMap(); +}; + +Turtle.reset = function(bool){ + if(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + } + Turtle.updateImage(); +} + +//Workaround current plugin implementation that uses Maze as a variable +Maze.reset = function(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + if(!bool){ + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d"); + var c2 = document.getElementById("user-canvas"); + var ctx2 = c2.getContext("2d"); + ctx1.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + ctx2.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + } + Turtle.updateImage(); +} + +/** +* +* Blocks functions +* +*/ +Turtle.move = function(length){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + if (Turtle.PEN_DOWN) { + ctx.beginPath(); + ctx.moveTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + } + if(length){ + Turtle.CURRENT_COORD.x += length * Math.sin(2 * Math.PI * Turtle.HEADING / 360); + Turtle.CURRENT_COORD.y -= length * Math.cos(2 * Math.PI * Turtle.HEADING / 360); + } + if(Turtle.PEN_DOWN){ + ctx.lineWidth = Turtle.PEN_WIDTH; + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.lineTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + ctx.stroke(); + } + Turtle.animate("move"); +} + +Turtle.moveForward = function(length){ + Turtle.move(length); +} + + +Turtle.moveBackwards = function(length){ + Turtle.move(-length); +} + +Turtle.circle = function(radius){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + ctx.beginPath(); + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, radius, 0 , 2*Math.PI); + ctx.stroke(); + Turtle.updateImage(); +} + +Turtle.turn = function(angle, direction){ + switch (direction){ + case 0: + Turtle.HEADING = (Turtle.HEADING - angle) % 360; + if (Turtle.HEADING < 0) { + Turtle.HEADING += 360; + } + break; + case 1: + Turtle.HEADING = (Turtle.HEADING + angle) % 360; + break; + } + Turtle.animate("turn"); +} + +Turtle.turnRight = function(angle){ + Turtle.turn(angle, 1); +} + +Turtle.turnLeft = function(angle){ + Turtle.turn(angle, 0); +} + +Turtle.penWidth = function(width){ + Turtle.PEN_WIDTH = width; +} + +Turtle.penUp = function(){ + Turtle.PEN_DOWN = false; +} + +Turtle.penDown = function(){ + Turtle.PEN_DOWN = true; +} + +Turtle.penColour = function(colour){ + Turtle.PEN_COLOUR = colour; + Turtle.animate("colour"); +} + +//Called to draw +if (document.getElementById('visualization') != null) { + window.addEventListener('load', Turtle.init); +} else { + console.warn('Cannot find visualization element.'); +} \ No newline at end of file diff --git a/Cours 1/Lecon2/Artist_4/public/turtle_config.json b/Cours 1 Code.org/Lecon 3/Artist_4/public/turtle_config.json similarity index 72% rename from Cours 1/Lecon2/Artist_4/public/turtle_config.json rename to Cours 1 Code.org/Lecon 3/Artist_4/public/turtle_config.json index 8f9e50a..812b2c5 100644 --- a/Cours 1/Lecon2/Artist_4/public/turtle_config.json +++ b/Cours 1 Code.org/Lecon 3/Artist_4/public/turtle_config.json @@ -8,5 +8,7 @@ "radius":15, "animationRate":50, "width":300, - "height":300 + "height":300, + "imageSolution":true, + "imageName":"solution.png" } \ No newline at end of file diff --git a/Cours 1/Lecon2/Artist_9/run b/Cours 1 Code.org/Lecon 3/Artist_4/run similarity index 100% rename from Cours 1/Lecon2/Artist_9/run rename to Cours 1 Code.org/Lecon 3/Artist_4/run diff --git a/Cours 1/Lecon2/Artist_3/student/turtle.py b/Cours 1 Code.org/Lecon 3/Artist_4/student/turtle.py similarity index 100% rename from Cours 1/Lecon2/Artist_3/student/turtle.py rename to Cours 1 Code.org/Lecon 3/Artist_4/student/turtle.py diff --git a/Cours 1/Lecon2/Artist_4/task.yaml b/Cours 1 Code.org/Lecon 3/Artist_4/task.yaml similarity index 81% rename from Cours 1/Lecon2/Artist_4/task.yaml rename to Cours 1 Code.org/Lecon 3/Artist_4/task.yaml index 527fe3d..c21f87a 100644 --- a/Cours 1/Lecon2/Artist_4/task.yaml +++ b/Cours 1 Code.org/Lecon 3/Artist_4/task.yaml @@ -7,31 +7,32 @@ evaluate: best groups: false input_random: '0' limits: + output: '2' memory: '100' time: '30' - output: '2' name: Exercice 4 network_grading: false order: 0 problems: code: - blocks_files: - - blocks.js options: - maxBlocks: '45' - sounds: true - trashcan: true + scrollbars: true visual: position: left - media: /static/common/js/blockly/media/ oneBasedIndex: true + media: /static/common/js/blockly/media/ toolboxPosition: start - scrollbars: true css: true + trashcan: true + sounds: true + maxBlocks: '45' files: - turtle.js - interpreter.js type: blockly + name: '' + blocks_files: + - blocks.js workspace: |- @@ -59,9 +60,10 @@ problems: - header: '' toolbox: |- + + moveForward @@ -75,50 +77,60 @@ problems: turnRight 90 + + turnLeft + 90 + + + + - - penUp - - - - - 0 - - + + + + + ??? + - name: '' + header: '' stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: - '3': + '0': type: 0 - id: '1' visible: true - description: '' name: Instructions avec paramètres - '4': description: '' - name: Boucles "répéter X fois" + id: '1' + '1': id: '2' + description: '' type: 0 visible: true - '0': - visible: true - name: Lecon 2 - type: 2 - description: Exercice faisant partie de la leçon 2 - id: '' + name: Boucles "répéter X fois" '2': description: Faisant partie du parcours facile name: Facile type: 2 visible: false id: '' + '3': + name: Normal + description: Faisant partie du parcours normal + type: 2 + visible: false + id: '' + '4': + type: 2 + description: '' + name: Lecon5 + visible: true + id: '' weight: 1.0 diff --git a/Cours 1/Lecon2/Artist_7/public/blocks.js b/Cours 1 Code.org/Lecon 3/Artist_5/public/blocks.js similarity index 100% rename from Cours 1/Lecon2/Artist_7/public/blocks.js rename to Cours 1 Code.org/Lecon 3/Artist_5/public/blocks.js diff --git a/Cours 1 Code.org/Lecon 3/Artist_5/public/create_img.py b/Cours 1 Code.org/Lecon 3/Artist_5/public/create_img.py new file mode 100644 index 0000000..0c71f5d --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_5/public/create_img.py @@ -0,0 +1,102 @@ +import Image, ImageDraw +import math +import json +import os +import random + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + int(length * math.sin(2 * math.pi * current_heading / 360)); + next_y = current_y - int(length * math.cos(2 * math.pi * current_heading / 360)); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - int(length * math.sin(2 * math.pi * current_heading / 360)); + next_y = current_y + int(length * math.cos(2 * math.pi * current_heading / 360)); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for i in range(3): + penColour(randomColour()) + turnLeft(30) + moveForward(100) + moveBackward(100) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1 Code.org/Lecon 3/Artist_5/public/interpreter.js b/Cours 1 Code.org/Lecon 3/Artist_5/public/interpreter.js new file mode 100644 index 0000000..0e8e386 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_5/public/interpreter.js @@ -0,0 +1,53 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(length) { + Turtle.move(length); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.move(-length); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 0); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 1); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(width) { + Turtle.penWidth(width); + }; + interpreter.setProperty(scope, 'penWidth', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penUp(); + }; + interpreter.setProperty(scope, 'penUp', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penDown(); + }; + interpreter.setProperty(scope, 'penDown', + interpreter.createNativeFunction(wrapper)); + wrapper = function(colour) { + Turtle.penColour(colour); + }; + interpreter.setProperty(scope, 'penColour', + interpreter.createNativeFunction(wrapper)); + + + + + Turtle.log = []; + Turtle.reset(false); +}; + +var animate = function() { + Turtle.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 3/Artist_5/public/solution.png b/Cours 1 Code.org/Lecon 3/Artist_5/public/solution.png new file mode 100644 index 0000000..35f093f Binary files /dev/null and b/Cours 1 Code.org/Lecon 3/Artist_5/public/solution.png differ diff --git a/Cours 1/Lecon2/Artist_5/public/turtle.js b/Cours 1 Code.org/Lecon 3/Artist_5/public/turtle.js similarity index 89% rename from Cours 1/Lecon2/Artist_5/public/turtle.js rename to Cours 1 Code.org/Lecon 3/Artist_5/public/turtle.js index 956efd9..370fb4d 100644 --- a/Cours 1/Lecon2/Artist_5/public/turtle.js +++ b/Cours 1 Code.org/Lecon 3/Artist_5/public/turtle.js @@ -16,21 +16,18 @@ request.open("GET", turtle_file, false); request.send(null) var json = JSON.parse(request.responseText); +var imagePath = "" +if(json.imageSolution) //If there is a solution image, use it + imagePath = task_directory_path+json.imageName; + //Code of the solution var solution = function(){ - //Here, put the javascript corresponding to the solved exercice - var i; - for(i = 0; i < 3; i++){ - Turtle.penColour(randomColour()); - Turtle.turnLeft(30); - Turtle.moveForward(100); - Turtle.moveBackwards(100); - } + //Here, put the javascript corresponding to the solved exercice (or use the image) } //Code of the decor var decoration = function(){ //Here, put the code for any decor, not part of the exercice - Turtle.moveBackwards(100); +Turtle.moveBackwards(100); Turtle.moveForward(50); Turtle.turnLeft(90); Turtle.moveForward(50); @@ -41,7 +38,7 @@ var decoration = function(){ Turtle.moveForward(50); Turtle.turnLeft(90); Turtle.penDown(); - Turtle.moveForward(100) + Turtle.moveForward(100); } var randomColour = function(){ @@ -120,8 +117,10 @@ Turtle.drawMap = function() { //Draw the decor Turtle.drawDecor(); - //Draw the solution - Turtle.drawSolution(); + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); //Draw the turtle Turtle.updateImage(); @@ -178,6 +177,19 @@ Turtle.resetTurtle = function(){ } +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } + +} + Turtle.drawSolution = function(){ var c = document.getElementById("solution-canvas"); var ctx = c.getContext("2d") @@ -315,6 +327,21 @@ Turtle.moveBackwards = function(length){ Turtle.move(-length); } +Turtle.circle = function(radius){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + ctx.beginPath(); + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, radius, 0 , 2*Math.PI); + ctx.stroke(); + Turtle.updateImage(); +} + Turtle.turn = function(angle, direction){ switch (direction){ case 0: diff --git a/Cours 1/Lecon2/Artist_5/public/turtle_config.json b/Cours 1 Code.org/Lecon 3/Artist_5/public/turtle_config.json similarity index 72% rename from Cours 1/Lecon2/Artist_5/public/turtle_config.json rename to Cours 1 Code.org/Lecon 3/Artist_5/public/turtle_config.json index 8f9e50a..812b2c5 100644 --- a/Cours 1/Lecon2/Artist_5/public/turtle_config.json +++ b/Cours 1 Code.org/Lecon 3/Artist_5/public/turtle_config.json @@ -8,5 +8,7 @@ "radius":15, "animationRate":50, "width":300, - "height":300 + "height":300, + "imageSolution":true, + "imageName":"solution.png" } \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 3/Artist_5/run b/Cours 1 Code.org/Lecon 3/Artist_5/run new file mode 100644 index 0000000..5d51d53 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_5/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("turtle.py") + + p = subprocess.Popen(shlex.split("python3 turtle.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1/Lecon2/Artist_5/student/turtle.py b/Cours 1 Code.org/Lecon 3/Artist_5/student/turtle.py similarity index 100% rename from Cours 1/Lecon2/Artist_5/student/turtle.py rename to Cours 1 Code.org/Lecon 3/Artist_5/student/turtle.py diff --git a/Cours 1/Lecon2/Artist_5/task.yaml b/Cours 1 Code.org/Lecon 3/Artist_5/task.yaml similarity index 84% rename from Cours 1/Lecon2/Artist_5/task.yaml rename to Cours 1 Code.org/Lecon 3/Artist_5/task.yaml index 28681cd..a004721 100644 --- a/Cours 1/Lecon2/Artist_5/task.yaml +++ b/Cours 1 Code.org/Lecon 3/Artist_5/task.yaml @@ -7,30 +7,68 @@ groups: false input_random: '0' limits: memory: '100' - time: '30' output: '2' + time: '30' name: Exercice 5 network_grading: false order: 0 problems: code: - blocks_files: - - blocks.js + toolbox: |- + + + + + + moveForward + + + 100 + + + + + turnRight + 90 + + + turnLeft + 90 + + + + + + + + + + + + + + ??? + + + options: - maxBlocks: '45' - sounds: true - trashcan: true + scrollbars: true visual: position: left - media: /static/common/js/blockly/media/ - scrollbars: true oneBasedIndex: true + media: /static/common/js/blockly/media/ toolboxPosition: start css: true + trashcan: true + sounds: true + maxBlocks: '45' files: - turtle.js - interpreter.js - header: '' + type: blockly + name: '' + blocks_files: + - blocks.js workspace: |- @@ -71,82 +109,46 @@ problems: - toolbox: |- - - - - moveForward - - - 100 - - - - - turnRight - - - 30 - - - - - - - - - - penUp - - - - - 0 - - - - - type: blockly - name: '' + header: '' stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: '0': + type: 0 visible: true name: Instructions avec paramètres id: '1' - type: 0 description: '' '1': - description: '' id: '2' + description: '' type: 0 - name: Boucles "répéter X fois" visible: true + name: Boucles "répéter X fois" '2': - visible: true - description: Exercice faisant partie de la leçon 2 - name: Lecon 2 + description: Faisant partie du parcours facile + name: Facile type: 2 + visible: false id: '' '3': + name: Challenge + description: Faisant partie du parcours challenge type: 2 - description: Faisant partie du parcours facile - name: Facile visible: false id: '' '4': - description: Faisant partie du parcours challenge - name: Challenge type: 2 + description: Faisant partie du parcours normal + name: Normal visible: false id: '' '5': - name: Normal + visible: true + description: '' + name: Lecon5 type: 2 - description: Faisant partie du parcours normal - visible: false id: '' weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 3/Artist_6/public/blocks.js b/Cours 1 Code.org/Lecon 3/Artist_6/public/blocks.js new file mode 100644 index 0000000..1e9af84 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_6/public/blocks.js @@ -0,0 +1,364 @@ +/** + * Blockly Games: Turtle Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Turtle application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Turtle.Blocks = {}; + +/** + * Common HSV hue for all blocks in this category. + */ +Turtle.Blocks.HUE = 160; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['turtle_move'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + this.appendValueInput("VALUE") + .setCheck('Number') + .appendField(new Blockly.FieldDropdown( + [ + ["avancer de","moveForward"], + ["reculer de","moveBackward"]] + ), "DIR"); + this.appendDummyInput() + .appendField("pixels"); + this.setColour(Turtle.Blocks.HUE); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setInputsInline(true); + this.setTooltip("Bouger"); + } +}; + +Blockly.JavaScript['turtle_move'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_move'] = function(block) { + // Generate Python for moving forward or backwards. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_move_internal'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['avancer de', 'moveForward'], + ['reculer de', 'moveBackward']]; + var VALUES = + [['20 pixels', '20'], + ['50 pixels', '50'], + ['100 pixels', '100'], + ['150 pixels', '150']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Bouger'); + } +}; + +Blockly.JavaScript['turtle_move_internal'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ');\n'; +}; + +Blockly.Python['turtle_move_internal'] = function(block) { + // Generate Python for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite', 'turnRight'], + ['tourner à gauche', 'turnLeft']]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Turtle.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Turtle.Blocks.LEFT_TURN; + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('VALUE') + .setCheck('Number') + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourner"); + } +}; + +Blockly.JavaScript['turtle_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn'] = function(block) { + // Generate Python for turning left or right. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.Python.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_turn_internal'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite de', 'turnRight'], + ['tourner à gauche de', 'turnLeft']]; + var VALUES = + [['1\u00B0', '1'], + ['45\u00B0', '45'], + ['72\u00B0', '72'], + ['90\u00B0', '90'], + ['120\u00B0', '120'], + ['144\u00B0', '144']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Tourner de X degrés'); + } +}; + +Blockly.JavaScript['turtle_turn_internal'] = function(block) { + // Generate JavaScript for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn_internal'] = function(block) { + // Generate Python for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_width'] = { + /** + * Block for setting the width. + * @this Blockly.Block + */ + init: function() { + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('WIDTH') + .setCheck('Number') + .appendField('Définis l\'épaisseur de la ligne'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Définis l\'épaisseur de la ligne'); + } +}; + +Blockly.JavaScript['turtle_width'] = function(block) { + // Generate JavaScript for setting the width. + var width = Blockly.JavaScript.valueToCode(block, 'WIDTH', + Blockly.JavaScript.ORDER_NONE) || '1'; + return 'penWidth(' + width + ');\n'; +}; + +Blockly.Python['turtle_width'] = function(block) { + // Generate Python for setting the width. + var width = Blockly.Python.valueToCode(block, 'WIDTH', + Blockly.Python.ORDER_NONE) || '1'; + return 'penWidth(' + width + ')\n'; +}; + +Blockly.Blocks['turtle_pen'] = { + /** + * Block for pen up/down. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "%1", + "args0": [ + { + "type": "field_dropdown", + "name": "PEN", + "options": [ + ['lever le crayon', "penUp"], + ['poser le crayon', "penDown"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Turtle.Blocks.HUE, + "tooltip": "Lever ou poser le crayon sur la feuille" + }); + } +}; + +Blockly.JavaScript['turtle_pen'] = function(block) { + // Generate JavaScript for pen up/down. + return block.getFieldValue('PEN') + + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_pen'] = function(block) { + // Generate Python for pen up/down. + return block.getFieldValue('PEN') + + '()\n'; +}; + +Blockly.Blocks['turtle_colour'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendValueInput('COLOUR') + .setCheck('Colour') + .appendField('définis la couleur'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Changer la couleur du trait"); + } +}; + +Blockly.JavaScript['turtle_colour'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = Blockly.JavaScript.valueToCode(block, 'COLOUR', + Blockly.JavaScript.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour'] = function(block) { + // Generate Python for setting the colour. + var colour = Blockly.Python.valueToCode(block, 'COLOUR', + Blockly.Python.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ')\n'; +}; + +Blockly.Blocks['turtle_colour_internal'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendDummyInput() + .appendField('définis la couleur') + .appendField(new Blockly.FieldColour('#ff0000'), 'COLOUR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Changer la couleur du trait'); + } +}; + +Blockly.JavaScript['turtle_colour_internal'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour_internal'] = function(block) { + // Generate Python for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\')\n'; +}; + +Blockly.Blocks['turtle_repeat_internal'] = { + /** + * Block for repeat n times (internal number). + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.CONTROLS_REPEAT_TITLE, + "args0": [ + { + "type": "field_dropdown", + "name": "TIMES", + "options": [ + ["3", "3"], + ["4", "4"], + ["5", "5"], + ["360", "360"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Blockly.Blocks.loops.HUE, + "tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP, + "helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL + }); + this.appendStatementInput('DO') + .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO); + } +}; + +Blockly.JavaScript['turtle_repeat_internal'] = +Blockly.JavaScript['controls_repeat']; + +Blockly.Python['turtle_repeat_internal'] = +Blockly.Python['controls_repeat']; \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 3/Artist_6/public/create_img.py b/Cours 1 Code.org/Lecon 3/Artist_6/public/create_img.py new file mode 100644 index 0000000..7716734 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_6/public/create_img.py @@ -0,0 +1,104 @@ +import Image, ImageDraw +import math +import json +import os + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + int(length * math.sin(2 * math.pi * current_heading / 360)); + next_y = current_y - int(length * math.cos(2 * math.pi * current_heading / 360)); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - int(length * math.sin(2 * math.pi * current_heading / 360)); + next_y = current_y + int(length * math.cos(2 * math.pi * current_heading / 360)); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for i in range(2): + moveForward(240) + turnLeft(90) + moveForward(160) + turnLeft(90) +moveForward(120) +turnLeft(90) +moveForward(160) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1 Code.org/Lecon 3/Artist_6/public/interpreter.js b/Cours 1 Code.org/Lecon 3/Artist_6/public/interpreter.js new file mode 100644 index 0000000..0e8e386 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_6/public/interpreter.js @@ -0,0 +1,53 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(length) { + Turtle.move(length); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.move(-length); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 0); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 1); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(width) { + Turtle.penWidth(width); + }; + interpreter.setProperty(scope, 'penWidth', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penUp(); + }; + interpreter.setProperty(scope, 'penUp', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penDown(); + }; + interpreter.setProperty(scope, 'penDown', + interpreter.createNativeFunction(wrapper)); + wrapper = function(colour) { + Turtle.penColour(colour); + }; + interpreter.setProperty(scope, 'penColour', + interpreter.createNativeFunction(wrapper)); + + + + + Turtle.log = []; + Turtle.reset(false); +}; + +var animate = function() { + Turtle.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 3/Artist_6/public/solution.png b/Cours 1 Code.org/Lecon 3/Artist_6/public/solution.png new file mode 100644 index 0000000..6c6cdd4 Binary files /dev/null and b/Cours 1 Code.org/Lecon 3/Artist_6/public/solution.png differ diff --git a/Cours 1/Lecon2/Artist_6/public/turtle.js b/Cours 1 Code.org/Lecon 3/Artist_6/public/turtle.js similarity index 94% rename from Cours 1/Lecon2/Artist_6/public/turtle.js rename to Cours 1 Code.org/Lecon 3/Artist_6/public/turtle.js index 40f62d5..4f89e5b 100644 --- a/Cours 1/Lecon2/Artist_6/public/turtle.js +++ b/Cours 1 Code.org/Lecon 3/Artist_6/public/turtle.js @@ -16,18 +16,13 @@ request.open("GET", turtle_file, false); request.send(null) var json = JSON.parse(request.responseText); +var imagePath = "" +if(json.imageSolution) //If there is a solution image, use it + imagePath = task_directory_path+json.imageName; + //Code of the solution var solution = function(){ - //Here, put the javascript corresponding to the solved exercice - for (var count = 0; count < 2; count++) { - Turtle.moveForward(240); - Turtle.turnLeft(90); - Turtle.moveForward(160); - Turtle.turnLeft(90); - } - Turtle.moveForward(120); - Turtle.turnLeft(90); - Turtle.moveForward(160); + //Here, put the javascript corresponding to the solved exercice (or use the image) } //Code of the decor var decoration = function(){ @@ -135,8 +130,10 @@ Turtle.drawMap = function() { //Draw the decor Turtle.drawDecor(); - //Draw the solution - Turtle.drawSolution(); + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); //Draw the turtle Turtle.updateImage(); @@ -193,6 +190,19 @@ Turtle.resetTurtle = function(){ } +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } + +} + Turtle.drawSolution = function(){ var c = document.getElementById("solution-canvas"); var ctx = c.getContext("2d") diff --git a/Cours 1/Lecon2/Artist_6/public/turtle_config.json b/Cours 1 Code.org/Lecon 3/Artist_6/public/turtle_config.json similarity index 71% rename from Cours 1/Lecon2/Artist_6/public/turtle_config.json rename to Cours 1 Code.org/Lecon 3/Artist_6/public/turtle_config.json index ac82382..8457649 100644 --- a/Cours 1/Lecon2/Artist_6/public/turtle_config.json +++ b/Cours 1 Code.org/Lecon 3/Artist_6/public/turtle_config.json @@ -8,5 +8,7 @@ "radius":15, "animationRate":50, "width":300, - "height":300 + "height":300, + "imageSolution":true, + "imageName":"solution.png" } \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 3/Artist_6/run b/Cours 1 Code.org/Lecon 3/Artist_6/run new file mode 100644 index 0000000..5d51d53 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_6/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("turtle.py") + + p = subprocess.Popen(shlex.split("python3 turtle.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1/Lecon2/Artist_6/student/turtle.py b/Cours 1 Code.org/Lecon 3/Artist_6/student/turtle.py similarity index 100% rename from Cours 1/Lecon2/Artist_6/student/turtle.py rename to Cours 1 Code.org/Lecon 3/Artist_6/student/turtle.py diff --git a/Cours 1/Lecon2/Artist_6/task.yaml b/Cours 1 Code.org/Lecon 3/Artist_6/task.yaml similarity index 80% rename from Cours 1/Lecon2/Artist_6/task.yaml rename to Cours 1 Code.org/Lecon 3/Artist_6/task.yaml index a05b386..9fa3e05 100644 --- a/Cours 1/Lecon2/Artist_6/task.yaml +++ b/Cours 1 Code.org/Lecon 3/Artist_6/task.yaml @@ -8,38 +8,40 @@ evaluate: best groups: false input_random: '0' limits: + output: '2' memory: '100' time: '30' - output: '2' name: Exercice 6 network_grading: false order: 0 problems: code: - blocks_files: - - blocks.js options: - maxBlocks: '45' - sounds: true - trashcan: true + scrollbars: true visual: position: left - media: /static/common/js/blockly/media/ oneBasedIndex: true + media: /static/common/js/blockly/media/ toolboxPosition: start - scrollbars: true css: true + trashcan: true + sounds: true + maxBlocks: '45' files: - turtle.js - interpreter.js type: blockly + name: '' + blocks_files: + - blocks.js workspace: |- - header: '' toolbox: |- + + moveForward @@ -53,56 +55,52 @@ problems: turnRight 90 - - - - - - - penUp + + turnLeft + 90 - - - - 0 - - + + + + + ??? + - name: '' + header: '' stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: '0': + type: 0 visible: true name: Instructions avec paramètres - id: '1' - type: 0 description: '' + id: '1' '1': - description: '' id: '2' + description: '' type: 0 - name: Boucles "répéter X fois" visible: true + name: Boucles "répéter X fois" '2': - visible: true - description: Exercice faisant partie de la leçon 2 - name: Lecon 2 + description: Faisant partie du parcours challenge + name: Challenge type: 2 + visible: false id: '' '3': + name: Normal + description: Faisant partie du parcours normal type: 2 - description: Faisant partie du parcours challenge - name: Challenge visible: false id: '' '4': - description: Faisant partie du parcours normal - name: Normal type: 2 - visible: false + description: '' + name: Lecon5 + visible: true id: '' weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 3/Artist_7/public/blocks.js b/Cours 1 Code.org/Lecon 3/Artist_7/public/blocks.js new file mode 100644 index 0000000..1e9af84 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_7/public/blocks.js @@ -0,0 +1,364 @@ +/** + * Blockly Games: Turtle Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Turtle application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Turtle.Blocks = {}; + +/** + * Common HSV hue for all blocks in this category. + */ +Turtle.Blocks.HUE = 160; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['turtle_move'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + this.appendValueInput("VALUE") + .setCheck('Number') + .appendField(new Blockly.FieldDropdown( + [ + ["avancer de","moveForward"], + ["reculer de","moveBackward"]] + ), "DIR"); + this.appendDummyInput() + .appendField("pixels"); + this.setColour(Turtle.Blocks.HUE); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setInputsInline(true); + this.setTooltip("Bouger"); + } +}; + +Blockly.JavaScript['turtle_move'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_move'] = function(block) { + // Generate Python for moving forward or backwards. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_move_internal'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['avancer de', 'moveForward'], + ['reculer de', 'moveBackward']]; + var VALUES = + [['20 pixels', '20'], + ['50 pixels', '50'], + ['100 pixels', '100'], + ['150 pixels', '150']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Bouger'); + } +}; + +Blockly.JavaScript['turtle_move_internal'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ');\n'; +}; + +Blockly.Python['turtle_move_internal'] = function(block) { + // Generate Python for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite', 'turnRight'], + ['tourner à gauche', 'turnLeft']]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Turtle.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Turtle.Blocks.LEFT_TURN; + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('VALUE') + .setCheck('Number') + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourner"); + } +}; + +Blockly.JavaScript['turtle_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn'] = function(block) { + // Generate Python for turning left or right. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.Python.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_turn_internal'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite de', 'turnRight'], + ['tourner à gauche de', 'turnLeft']]; + var VALUES = + [['1\u00B0', '1'], + ['45\u00B0', '45'], + ['72\u00B0', '72'], + ['90\u00B0', '90'], + ['120\u00B0', '120'], + ['144\u00B0', '144']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Tourner de X degrés'); + } +}; + +Blockly.JavaScript['turtle_turn_internal'] = function(block) { + // Generate JavaScript for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn_internal'] = function(block) { + // Generate Python for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_width'] = { + /** + * Block for setting the width. + * @this Blockly.Block + */ + init: function() { + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('WIDTH') + .setCheck('Number') + .appendField('Définis l\'épaisseur de la ligne'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Définis l\'épaisseur de la ligne'); + } +}; + +Blockly.JavaScript['turtle_width'] = function(block) { + // Generate JavaScript for setting the width. + var width = Blockly.JavaScript.valueToCode(block, 'WIDTH', + Blockly.JavaScript.ORDER_NONE) || '1'; + return 'penWidth(' + width + ');\n'; +}; + +Blockly.Python['turtle_width'] = function(block) { + // Generate Python for setting the width. + var width = Blockly.Python.valueToCode(block, 'WIDTH', + Blockly.Python.ORDER_NONE) || '1'; + return 'penWidth(' + width + ')\n'; +}; + +Blockly.Blocks['turtle_pen'] = { + /** + * Block for pen up/down. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "%1", + "args0": [ + { + "type": "field_dropdown", + "name": "PEN", + "options": [ + ['lever le crayon', "penUp"], + ['poser le crayon', "penDown"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Turtle.Blocks.HUE, + "tooltip": "Lever ou poser le crayon sur la feuille" + }); + } +}; + +Blockly.JavaScript['turtle_pen'] = function(block) { + // Generate JavaScript for pen up/down. + return block.getFieldValue('PEN') + + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_pen'] = function(block) { + // Generate Python for pen up/down. + return block.getFieldValue('PEN') + + '()\n'; +}; + +Blockly.Blocks['turtle_colour'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendValueInput('COLOUR') + .setCheck('Colour') + .appendField('définis la couleur'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Changer la couleur du trait"); + } +}; + +Blockly.JavaScript['turtle_colour'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = Blockly.JavaScript.valueToCode(block, 'COLOUR', + Blockly.JavaScript.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour'] = function(block) { + // Generate Python for setting the colour. + var colour = Blockly.Python.valueToCode(block, 'COLOUR', + Blockly.Python.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ')\n'; +}; + +Blockly.Blocks['turtle_colour_internal'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendDummyInput() + .appendField('définis la couleur') + .appendField(new Blockly.FieldColour('#ff0000'), 'COLOUR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Changer la couleur du trait'); + } +}; + +Blockly.JavaScript['turtle_colour_internal'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour_internal'] = function(block) { + // Generate Python for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\')\n'; +}; + +Blockly.Blocks['turtle_repeat_internal'] = { + /** + * Block for repeat n times (internal number). + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.CONTROLS_REPEAT_TITLE, + "args0": [ + { + "type": "field_dropdown", + "name": "TIMES", + "options": [ + ["3", "3"], + ["4", "4"], + ["5", "5"], + ["360", "360"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Blockly.Blocks.loops.HUE, + "tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP, + "helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL + }); + this.appendStatementInput('DO') + .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO); + } +}; + +Blockly.JavaScript['turtle_repeat_internal'] = +Blockly.JavaScript['controls_repeat']; + +Blockly.Python['turtle_repeat_internal'] = +Blockly.Python['controls_repeat']; \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 3/Artist_7/public/create_img.py b/Cours 1 Code.org/Lecon 3/Artist_7/public/create_img.py new file mode 100644 index 0000000..97888cc --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_7/public/create_img.py @@ -0,0 +1,101 @@ +import Image, ImageDraw +import math +import json +import os + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y - length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y + length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for i in range(3): + for j in range(3): + moveForward(50) + turnRight(120) + moveForward(50) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1 Code.org/Lecon 3/Artist_7/public/interpreter.js b/Cours 1 Code.org/Lecon 3/Artist_7/public/interpreter.js new file mode 100644 index 0000000..0e8e386 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_7/public/interpreter.js @@ -0,0 +1,53 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(length) { + Turtle.move(length); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.move(-length); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 0); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 1); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(width) { + Turtle.penWidth(width); + }; + interpreter.setProperty(scope, 'penWidth', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penUp(); + }; + interpreter.setProperty(scope, 'penUp', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penDown(); + }; + interpreter.setProperty(scope, 'penDown', + interpreter.createNativeFunction(wrapper)); + wrapper = function(colour) { + Turtle.penColour(colour); + }; + interpreter.setProperty(scope, 'penColour', + interpreter.createNativeFunction(wrapper)); + + + + + Turtle.log = []; + Turtle.reset(false); +}; + +var animate = function() { + Turtle.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 3/Artist_7/public/solution.png b/Cours 1 Code.org/Lecon 3/Artist_7/public/solution.png new file mode 100644 index 0000000..3d43c60 Binary files /dev/null and b/Cours 1 Code.org/Lecon 3/Artist_7/public/solution.png differ diff --git a/Cours 1/Lecon2/Artist_7/public/turtle.js b/Cours 1 Code.org/Lecon 3/Artist_7/public/turtle.js similarity index 93% rename from Cours 1/Lecon2/Artist_7/public/turtle.js rename to Cours 1 Code.org/Lecon 3/Artist_7/public/turtle.js index 7fec641..9ce1fd3 100644 --- a/Cours 1/Lecon2/Artist_7/public/turtle.js +++ b/Cours 1 Code.org/Lecon 3/Artist_7/public/turtle.js @@ -16,16 +16,13 @@ request.open("GET", turtle_file, false); request.send(null) var json = JSON.parse(request.responseText); +var imagePath = "" +if(json.imageSolution) //If there is a solution image, use it + imagePath = task_directory_path+json.imageName; + //Code of the solution var solution = function(){ - //Here, put the javascript corresponding to the solved exercice - for (var count2 = 0; count2 < 3; count2++) { - for (var count = 0; count < 3; count++) { - Turtle.moveForward(50); - Turtle.turnRight(120); - } - Turtle.moveForward(50); - } + //Here, put the javascript corresponding to the solved exercice (or use the image) } //Code of the decor var decoration = function(){ @@ -115,8 +112,10 @@ Turtle.drawMap = function() { //Draw the decor Turtle.drawDecor(); - //Draw the solution - Turtle.drawSolution(); + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); //Draw the turtle Turtle.updateImage(); @@ -173,6 +172,19 @@ Turtle.resetTurtle = function(){ } +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } + +} + Turtle.drawSolution = function(){ var c = document.getElementById("solution-canvas"); var ctx = c.getContext("2d") diff --git a/Cours 1/Lecon2/Artist_7/public/turtle_config.json b/Cours 1 Code.org/Lecon 3/Artist_7/public/turtle_config.json similarity index 71% rename from Cours 1/Lecon2/Artist_7/public/turtle_config.json rename to Cours 1 Code.org/Lecon 3/Artist_7/public/turtle_config.json index ccce648..9864b28 100644 --- a/Cours 1/Lecon2/Artist_7/public/turtle_config.json +++ b/Cours 1 Code.org/Lecon 3/Artist_7/public/turtle_config.json @@ -8,5 +8,7 @@ "radius":15, "animationRate":50, "width":300, - "height":300 + "height":300, + "imageSolution":true, + "imageName":"solution.png" } \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 3/Artist_7/run b/Cours 1 Code.org/Lecon 3/Artist_7/run new file mode 100644 index 0000000..5d51d53 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_7/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("turtle.py") + + p = subprocess.Popen(shlex.split("python3 turtle.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1/Lecon2/Artist_7/student/turtle.py b/Cours 1 Code.org/Lecon 3/Artist_7/student/turtle.py similarity index 100% rename from Cours 1/Lecon2/Artist_7/student/turtle.py rename to Cours 1 Code.org/Lecon 3/Artist_7/student/turtle.py diff --git a/Cours 1/Lecon2/Artist_7/task.yaml b/Cours 1 Code.org/Lecon 3/Artist_7/task.yaml similarity index 77% rename from Cours 1/Lecon2/Artist_7/task.yaml rename to Cours 1 Code.org/Lecon 3/Artist_7/task.yaml index 2378884..ae5be7a 100644 --- a/Cours 1/Lecon2/Artist_7/task.yaml +++ b/Cours 1 Code.org/Lecon 3/Artist_7/task.yaml @@ -15,22 +15,61 @@ network_grading: false order: 0 problems: code: - blocks_files: - - blocks.js + toolbox: |- + + + + + + moveForward + + + 100 + + + + + turnRight + 90 + + + turnLeft + 90 + + + + + + + + + + + + + + ??? + + + options: - css: true - sounds: true - toolboxPosition: start + scrollbars: true visual: position: left + oneBasedIndex: true media: /static/common/js/blockly/media/ - maxBlocks: '45' - scrollbars: true + css: true trashcan: true - oneBasedIndex: true + toolboxPosition: start + sounds: true + maxBlocks: '45' files: - turtle.js - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js workspace: |- @@ -55,35 +94,6 @@ problems: header: '' - type: blockly - toolbox: |- - - - - moveForward - 50 - - - turnRight - 90 - - - - - - - - penUp - - - - - 0 - - - - - name: '' stored_submissions: 0 submission_limit: amount: -1 @@ -92,31 +102,37 @@ tags: '0': type: 0 name: Boucles impbriquées - id: '3' description: '' + id: '3' visible: false - '3': + '1': id: '1' - visible: true description: '' - name: Boucle répéter X fois type: 0 - '4': - description: '' - id: '2' visible: true + name: Boucle répéter X fois + '2': + description: '' name: Instructions avec paramètres + id: '2' type: 0 - '1': + visible: true + '3': + name: Normal description: Faisant partie du parcours normal type: 2 - name: Normal visible: false id: '' - '2': + '4': type: 2 - description: Faisant partie du parcours facile name: Facile + description: Faisant partie du parcours facile visible: false id: '' + '5': + visible: true + description: '' + name: Lecon 5 + type: 2 + id: '' weight: 1.0 diff --git a/Cours 1/Lecon2/Artist_8/public/blocks.js b/Cours 1 Code.org/Lecon 3/Artist_8/public/blocks.js similarity index 100% rename from Cours 1/Lecon2/Artist_8/public/blocks.js rename to Cours 1 Code.org/Lecon 3/Artist_8/public/blocks.js diff --git a/Cours 1 Code.org/Lecon 3/Artist_8/public/create_img.py b/Cours 1 Code.org/Lecon 3/Artist_8/public/create_img.py new file mode 100644 index 0000000..6615afa --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_8/public/create_img.py @@ -0,0 +1,103 @@ +import Image, ImageDraw +import math +import json +import os + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y - length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y + length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +penColour('#228b22') +for count in range(10): + moveForward(250) + jumpBackward(250) + turnLeft(90) + jumpForward(15) + turnRight(90) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1/Lecon2/Artist_8/public/interpreter.js b/Cours 1 Code.org/Lecon 3/Artist_8/public/interpreter.js similarity index 100% rename from Cours 1/Lecon2/Artist_8/public/interpreter.js rename to Cours 1 Code.org/Lecon 3/Artist_8/public/interpreter.js diff --git a/Cours 1 Code.org/Lecon 3/Artist_8/public/solution.png b/Cours 1 Code.org/Lecon 3/Artist_8/public/solution.png new file mode 100644 index 0000000..c7da9eb Binary files /dev/null and b/Cours 1 Code.org/Lecon 3/Artist_8/public/solution.png differ diff --git a/Cours 1 Code.org/Lecon 3/Artist_8/public/turtle.js b/Cours 1 Code.org/Lecon 3/Artist_8/public/turtle.js new file mode 100644 index 0000000..5a9e2e1 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_8/public/turtle.js @@ -0,0 +1,402 @@ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Turtle = {}; +window.Maze = {}; + +//Get the json file and its informations +var turtle_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + turtle_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"turtle_config.json" +}else { //When displaying the task + turtle_file = task_directory_path + "turtle_config.json"; +} +var request = new XMLHttpRequest(); +request.open("GET", turtle_file, false); +request.send(null) +var json = JSON.parse(request.responseText); + +var imagePath = "" +if(json.imageSolution) + imagePath = task_directory_path+json.imageName; + +//Code of the solution +var solution = function(){ + //Here, put the javascript corresponding to the solved exercice +} +var decoration = function(){ + //Here, put the code for any decor, not part of the exercice + Turtle.jumpForward(50); + Turtle.turnLeft(90); + Turtle.jumpForward(145); + Turtle.penWidth(4); + Turtle.moveForward(50); + Turtle.turnRight(90); + Turtle.jumpForward(15); + Turtle.turnRight(180); + Turtle.penColour("#FF0000") + for(var i = 0; i < 6; i++){ + Turtle.moveForward(30); + Turtle.turnRight(60); + } +} + +var randomColour = function(){ + var colour = Math.floor(Math.random()*16777215); + return "#"+colour.toString(16).toUpperCase() +} + +//Canvas size +Turtle.CANVAS_WIDTH = json.width; +Turtle.CANVAS_HEIGHT = json.height; + +//Starting position and radius of turtle +Turtle.START_X = json.startX; +Turtle.START_Y = json.startY; +Turtle.RADIUS = json.radius; + +//Current coordinates of turtle +Turtle.CURRENT_COORD = { + x:Turtle.START_X, + y:Turtle.START_Y +} + +//Current heading of turtle +Turtle.HEADING = json.startAngle; + +//Weater or not the pen is down and it's width +Turtle.PEN_DOWN = true; //At start, the pen is always down +Turtle.PEN_WIDTH = json.strokeWidth; +Turtle.PEN_COLOUR = json.strokeColour; + +//Variables used to draw on the solution canvas or the decor canvas +var sol = false; +var decor = false; + +//milisec between each frame +window.stepSpeed = json.animationRate; + +/** +* +* Animations functions +* +*/ + +Turtle.drawMap = function() { + var div = document.getElementById('visualization'); + + // Add the canvas for the turtle + var canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute("style", "border:1px solid #F1EEE7;") + canvas.setAttribute("id", "turtle-canvas"); + div.appendChild(canvas); + // Add the canvas for the user. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "user-canvas"); + div.appendChild(canvas); + // Add the canvas for the solution. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "solution-canvas"); + div.appendChild(canvas); + //Add the canvas for the decor + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none') + canvas.setAttribute("id", "decor-canvas"); + div.appendChild(canvas) + + //Draw the decor + Turtle.drawDecor(); + + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); + + //Draw the turtle + Turtle.updateImage(); + + +} + +Turtle.drawTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + + //Draw the turtle body + ctx.beginPath(); + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, Turtle.RADIUS, 0, 2 * Math.PI); + ctx.stroke(); + + // Draw the turtle head. + var WIDTH = 0.3; + var HEAD_TIP = 10; + var ARROW_TIP = 4; + var BEND = 6; + var radians = 2 * Math.PI * Turtle.HEADING / 360; + var tipX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + HEAD_TIP) * Math.sin(radians); + var tipY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + HEAD_TIP) * Math.cos(radians); + radians -= WIDTH; + var leftX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var leftY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + radians += WIDTH / 2; + var leftControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var leftControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH; + var rightControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var rightControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH / 2; + var rightX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var rightY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + + ctx.beginPath(); + ctx.moveTo(tipX, tipY); + ctx.lineTo(leftX, leftY); + ctx.bezierCurveTo(leftControlX, leftControlY, + rightControlX, rightControlY, rightX, rightY); + ctx.closePath(); + ctx.fillStyle = Turtle.PEN_COLOUR; + ctx.fill(); +} + +Turtle.resetTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + //Clear any previous turtle + ctx.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + +} + +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } +} + + +Turtle.drawSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + sol = true; + solution(); + sol = false; + Turtle.reset(true); +} + +Turtle.drawDecor = function(){ + var c = document.getElementById("decor-canvas"); + var ctx = c.getContext("2d") + decor = true; + decoration(); + decor = false; + Turtle.reset(true); +} + +Turtle.animate = function(id) { + switch(id){ + case "move" : + Turtle.updateImage(); + break; + case "turn" : + Turtle.updateImage(); + break; + case "colour": + Turtle.updateImage(); + break; + default: + //This should not happen + console.warn("Unknown animation"); + break; + } +} + +Turtle.updateImage = function(){ + Turtle.resetTurtle(); + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d") + var c2 = document.getElementById("user-canvas"); + var c3 = document.getElementById("solution-canvas"); + var c4 = document.getElementById("decor-canvas"); + ctx1.drawImage(c4, 0, 0); //Fuse any decor + ctx1.drawImage(c3, 0, 0); //Fuse solution canvas + ctx1.drawImage(c2, 0, 0); //Fuse user canvas + Turtle.drawTurtle(); //Add the turtle +} + +Turtle.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Turtle.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + } + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,penUp,penDown,penWidth,penColour,'); + + Turtle.drawMap(); +}; + +Turtle.reset = function(bool){ + if(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + } + Turtle.updateImage(); +} + +//Workaround current plugin implementation that uses Maze as a variable +Maze.reset = function(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + if(!bool){ + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d"); + var c2 = document.getElementById("user-canvas"); + var ctx2 = c2.getContext("2d"); + ctx1.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + ctx2.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + } + Turtle.updateImage(); +} + +/** +* +* Blocks functions +* +*/ +Turtle.move = function(length){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + if (Turtle.PEN_DOWN) { + ctx.beginPath(); + ctx.moveTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + } + if(length){ + Turtle.CURRENT_COORD.x += length * Math.sin(2 * Math.PI * Turtle.HEADING / 360); + Turtle.CURRENT_COORD.y -= length * Math.cos(2 * Math.PI * Turtle.HEADING / 360); + } + if(Turtle.PEN_DOWN){ + ctx.lineWidth = Turtle.PEN_WIDTH; + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.lineTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + ctx.stroke(); + } + Turtle.animate("move"); +} + +Turtle.moveForward = function(length){ + Turtle.move(length); +} + + +Turtle.moveBackward = function(length){ + Turtle.move(-length); +} + +Turtle.jumpForward = function(length){ + Turtle.penUp(); + Turtle.moveForward(length); + Turtle.penDown(); +} + +Turtle.jumpBackward = function(length){ + Turtle.penUp(); + Turtle.moveBackward(length); + Turtle.penDown(); +} + +Turtle.circle = function(radius){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + ctx.beginPath(); + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, radius, 0,2*Math.PI); + ctx.stroke(); + Turtle.updateImage(); +} + +Turtle.turn = function(angle, direction){ + switch (direction){ + case 0: + Turtle.HEADING = (Turtle.HEADING - angle) % 360; + if (Turtle.HEADING < 0) { + Turtle.HEADING += 360; + } + break; + case 1: + Turtle.HEADING = (Turtle.HEADING + angle) % 360; + break; + } + Turtle.animate("turn"); +} + +Turtle.turnRight = function(angle){ + Turtle.turn(angle, 1); +} + +Turtle.turnLeft = function(angle){ + Turtle.turn(angle, 0); +} + +Turtle.penWidth = function(width){ + Turtle.PEN_WIDTH = width; +} + +Turtle.penUp = function(){ + Turtle.PEN_DOWN = false; +} + +Turtle.penDown = function(){ + Turtle.PEN_DOWN = true; +} + +Turtle.penColour = function(colour){ + Turtle.PEN_COLOUR = colour; + Turtle.animate("colour"); +} + +//Called to draw +if (document.getElementById('visualization') != null) { + window.addEventListener('load', Turtle.init); +} else { + console.warn('Cannot find visualization element.'); +} \ No newline at end of file diff --git a/Cours 1/Lecon2/Artist_8/public/turtle_config.json b/Cours 1 Code.org/Lecon 3/Artist_8/public/turtle_config.json similarity index 71% rename from Cours 1/Lecon2/Artist_8/public/turtle_config.json rename to Cours 1 Code.org/Lecon 3/Artist_8/public/turtle_config.json index 5157902..8670a87 100644 --- a/Cours 1/Lecon2/Artist_8/public/turtle_config.json +++ b/Cours 1 Code.org/Lecon 3/Artist_8/public/turtle_config.json @@ -8,5 +8,7 @@ "radius":15, "animationRate":50, "width":300, - "height":300 + "height":300, + "imageSolution":true, + "imageName":"solution.png" } \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 3/Artist_8/run b/Cours 1 Code.org/Lecon 3/Artist_8/run new file mode 100644 index 0000000..5d51d53 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_8/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("turtle.py") + + p = subprocess.Popen(shlex.split("python3 turtle.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1/Lecon2/Artist_8/student/turtle.py b/Cours 1 Code.org/Lecon 3/Artist_8/student/turtle.py similarity index 100% rename from Cours 1/Lecon2/Artist_8/student/turtle.py rename to Cours 1 Code.org/Lecon 3/Artist_8/student/turtle.py diff --git a/Cours 1/Lecon2/Artist_2/task.yaml b/Cours 1 Code.org/Lecon 3/Artist_8/task.yaml similarity index 76% rename from Cours 1/Lecon2/Artist_2/task.yaml rename to Cours 1 Code.org/Lecon 3/Artist_8/task.yaml index 576c0a8..3a27181 100644 --- a/Cours 1/Lecon2/Artist_2/task.yaml +++ b/Cours 1 Code.org/Lecon 3/Artist_8/task.yaml @@ -1,43 +1,24 @@ accessible: true author: Celine Deknop -context: Ce parterre de fleurs rectangulaire a un périmètre de 600 pixels. Le côté - le plus long a une longueur de 200 pixels. Dessine le rectangle. +context: Complète les lignes vertes. Les lignes ont 250 pixels de long et sont à 15 + pixels l’une de l’autre. environment: default evaluate: best groups: false input_random: '0' limits: - time: '30' memory: '100' output: '2' -name: Exercice 2 + time: '30' +name: Exercice 8 network_grading: false order: 0 problems: code: - blocks_files: - - blocks.js - options: - maxBlocks: '45' - sounds: true - trashcan: true - visual: - position: left - media: /static/common/js/blockly/media/ - scrollbars: true - oneBasedIndex: true - toolboxPosition: start - css: true - files: - - turtle.js - - interpreter.js - header: '' - workspace: |- - - - toolbox: |- + + moveForward @@ -55,67 +36,87 @@ problems: turnLeft 90 + + + - - penUp + + #228b22 - - - - 0 - - - - - moveForward - 100 + + + + + ??? + + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: '45' + files: + - turtle.js + - interpreter.js type: blockly name: '' + blocks_files: + - blocks.js + workspace: |- + + + + header: '' stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: - '4': - description: '' - name: Boucle répéter X fois - id: '1' + '0': type: 0 visible: true - '5': - visible: true name: Instructions avec paramètres - type: 0 + id: '1' + description: '' + '1': id: '2' description: '' - '0': + type: 0 visible: true - name: Lecon 2 - type: 2 - description: Exercice faisant partie de la leçon 2 - id: '' - '1': + name: Boucles "répéter X fois" + '2': description: Faisant partie du parcours normal - type: 2 name: Normal + type: 2 visible: false id: '' - '2': - description: Faisant partie du parcours facile + '3': name: Facile + description: Faisant partie du parcours facile type: 2 visible: false id: '' - '3': + '4': type: 2 description: Faisant partie du parcours challenge name: Challenge visible: false id: '' + '5': + visible: true + description: '' + name: Lecon5 + type: 2 + id: '' weight: 1.0 diff --git a/Cours 1/Lecon2/Artist_9/public/blocks.js b/Cours 1 Code.org/Lecon 3/Artist_9/public/blocks.js similarity index 100% rename from Cours 1/Lecon2/Artist_9/public/blocks.js rename to Cours 1 Code.org/Lecon 3/Artist_9/public/blocks.js diff --git a/Cours 1/Lecon2/Artist_9/public/interpreter.js b/Cours 1 Code.org/Lecon 3/Artist_9/public/interpreter.js similarity index 100% rename from Cours 1/Lecon2/Artist_9/public/interpreter.js rename to Cours 1 Code.org/Lecon 3/Artist_9/public/interpreter.js diff --git a/Cours 1/Lecon2/Artist_9/public/turtle.js b/Cours 1 Code.org/Lecon 3/Artist_9/public/turtle.js similarity index 100% rename from Cours 1/Lecon2/Artist_9/public/turtle.js rename to Cours 1 Code.org/Lecon 3/Artist_9/public/turtle.js diff --git a/Cours 1/Lecon2/Artist_3/public/turtle_config.json b/Cours 1 Code.org/Lecon 3/Artist_9/public/turtle_config.json similarity index 100% rename from Cours 1/Lecon2/Artist_3/public/turtle_config.json rename to Cours 1 Code.org/Lecon 3/Artist_9/public/turtle_config.json diff --git a/Cours 1 Code.org/Lecon 3/Artist_9/run b/Cours 1 Code.org/Lecon 3/Artist_9/run new file mode 100644 index 0000000..5d51d53 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/Artist_9/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("turtle.py") + + p = subprocess.Popen(shlex.split("python3 turtle.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1/Lecon2/Artist_9/student/turtle.py b/Cours 1 Code.org/Lecon 3/Artist_9/student/turtle.py similarity index 100% rename from Cours 1/Lecon2/Artist_9/student/turtle.py rename to Cours 1 Code.org/Lecon 3/Artist_9/student/turtle.py diff --git a/Cours 1/Lecon2/Artist_9/task.yaml b/Cours 1 Code.org/Lecon 3/Artist_9/task.yaml similarity index 72% rename from Cours 1/Lecon2/Artist_9/task.yaml rename to Cours 1 Code.org/Lecon 3/Artist_9/task.yaml index ed9d5e0..97e534c 100644 --- a/Cours 1/Lecon2/Artist_9/task.yaml +++ b/Cours 1 Code.org/Lecon 3/Artist_9/task.yaml @@ -15,77 +15,77 @@ network_grading: false order: 0 problems: code: - blocks_files: - - blocks.js options: - css: true - sounds: true - toolboxPosition: start + scrollbars: true visual: position: left + oneBasedIndex: true media: /static/common/js/blockly/media/ - maxBlocks: '45' - scrollbars: true + toolboxPosition: start + css: true trashcan: true - oneBasedIndex: true + sounds: true + maxBlocks: '45' files: - turtle.js - interpreter.js + type: blockly + blocks_files: + - blocks.js workspace: |- - header: '' - type: blockly + name: '' toolbox: |- - - turnRight - 90 - - + + moveForward - - 250 - - - - - jumpForward - 100 + + turnRight + 90 + + + turnLeft + 90 + + + + - - #228b22 - + - - - - - 0 - - + + #228b22 + + + + + + ??? + - name: '' + header: '' stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: '0': - name: Facile type: 2 + name: Facile description: Faisant partie du parcours facile visible: false id: '' @@ -96,9 +96,15 @@ tags: visible: false id: '' '2': - type: 2 description: Faisant partie du parcours normal name: Normal + type: 2 visible: false id: '' + '3': + visible: true + name: Lecon5 + description: '' + type: 2 + id: '' weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 3/course.yaml b/Cours 1 Code.org/Lecon 3/course.yaml new file mode 100644 index 0000000..9e52195 --- /dev/null +++ b/Cours 1 Code.org/Lecon 3/course.yaml @@ -0,0 +1,18 @@ +accessible: true +name: Cours 5 - Drawing +description: '' +admins: +- '' +tutors: [] +groups_student_choice: false +use_classrooms: true +allow_unregister: true +allow_preview: false +registration: true +registration_password: null +registration_ac: null +registration_ac_list: +- '' +is_lti: false +lti_keys: {} +lti_send_back_grade: false diff --git a/Cours 1 Code.org/Lecon 5/Artist_1/public/blocks.js b/Cours 1 Code.org/Lecon 5/Artist_1/public/blocks.js new file mode 100644 index 0000000..57b6cb3 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_1/public/blocks.js @@ -0,0 +1,399 @@ +/** + * Blockly Games: Turtle Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Turtle application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Turtle.Blocks = {}; + +/** + * Common HSV hue for all blocks in this category. + */ +Turtle.Blocks.HUE = 160; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['turtle_move'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + this.appendValueInput("VALUE") + .setCheck('Number') + .appendField(new Blockly.FieldDropdown( + [ + ["avancer de","moveForward"], + ["reculer de","moveBackward"]] + ), "DIR"); + this.appendDummyInput() + .appendField("pixels"); + this.setColour(Turtle.Blocks.HUE); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setInputsInline(true); + this.setTooltip("Bouger"); + } +}; + +Blockly.JavaScript['turtle_move'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_move'] = function(block) { + // Generate Python for moving forward or backwards. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_move_internal'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['avancer de', 'moveForward'], + ['reculer de', 'moveBackward']]; + var VALUES = + [['20 pixels', '20'], + ['50 pixels', '50'], + ['100 pixels', '100'], + ['150 pixels', '150']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Bouger'); + } +}; + +Blockly.JavaScript['turtle_move_internal'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ');\n'; +}; + +Blockly.Python['turtle_move_internal'] = function(block) { + // Generate Python for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_jump'] = { + init: function() { + this.appendValueInput("VAL") + .setCheck("Number") + .appendField("saute en") + .appendField(new Blockly.FieldDropdown( + [ + ["avant de","jumpForward"], + ["arriere de","jumpBackward"] + ]), "DIR"); + this.appendDummyInput() + .appendField("pixels"); + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip("Bouge sans tracer de ligne"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['turtle_jump'] = function(block) { + var dropdown_dir = block.getFieldValue('DIR'); + var value_val = Blockly.JavaScript.valueToCode(block, 'VAL', Blockly.JavaScript.ORDER_ATOMIC); + var code = dropdown_dir+'('+value_val+');\n'; + return code; +} + +Blockly.Python['turtle_jump'] = function(block) { + var dropdown_dir = block.getFieldValue('DIR'); + var value_val = Blockly.Python.valueToCode(block, 'VAL', Blockly.JavaScript.ORDER_ATOMIC); + var code = dropdown_dir+'('+value_val+')\n'; + return code; +} + +Blockly.Blocks['turtle_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite', 'turnRight'], + ['tourner à gauche', 'turnLeft']]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Turtle.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Turtle.Blocks.LEFT_TURN; + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('VALUE') + .setCheck('Number') + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourner"); + } +}; + +Blockly.JavaScript['turtle_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn'] = function(block) { + // Generate Python for turning left or right. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.Python.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_turn_internal'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite de', 'turnRight'], + ['tourner à gauche de', 'turnLeft']]; + var VALUES = + [['1\u00B0', '1'], + ['45\u00B0', '45'], + ['72\u00B0', '72'], + ['90\u00B0', '90'], + ['120\u00B0', '120'], + ['144\u00B0', '144']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Tourner de X degrés'); + } +}; + +Blockly.JavaScript['turtle_turn_internal'] = function(block) { + // Generate JavaScript for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn_internal'] = function(block) { + // Generate Python for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_width'] = { + /** + * Block for setting the width. + * @this Blockly.Block + */ + init: function() { + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('WIDTH') + .setCheck('Number') + .appendField('Définis l\'épaisseur de la ligne'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Définis l\'épaisseur de la ligne'); + } +}; + +Blockly.JavaScript['turtle_width'] = function(block) { + // Generate JavaScript for setting the width. + var width = Blockly.JavaScript.valueToCode(block, 'WIDTH', + Blockly.JavaScript.ORDER_NONE) || '1'; + return 'penWidth(' + width + ');\n'; +}; + +Blockly.Python['turtle_width'] = function(block) { + // Generate Python for setting the width. + var width = Blockly.Python.valueToCode(block, 'WIDTH', + Blockly.Python.ORDER_NONE) || '1'; + return 'penWidth(' + width + ')\n'; +}; + +Blockly.Blocks['turtle_pen'] = { + /** + * Block for pen up/down. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "%1", + "args0": [ + { + "type": "field_dropdown", + "name": "PEN", + "options": [ + ['lever le crayon', "penUp"], + ['poser le crayon', "penDown"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Turtle.Blocks.HUE, + "tooltip": "Lever ou poser le crayon sur la feuille" + }); + } +}; + +Blockly.JavaScript['turtle_pen'] = function(block) { + // Generate JavaScript for pen up/down. + return block.getFieldValue('PEN') + + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_pen'] = function(block) { + // Generate Python for pen up/down. + return block.getFieldValue('PEN') + + '()\n'; +}; + +Blockly.Blocks['turtle_colour'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendValueInput('COLOUR') + .setCheck('Colour') + .appendField('définis la couleur'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Changer la couleur du trait"); + } +}; + +Blockly.JavaScript['turtle_colour'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = Blockly.JavaScript.valueToCode(block, 'COLOUR', + Blockly.JavaScript.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour'] = function(block) { + // Generate Python for setting the colour. + var colour = Blockly.Python.valueToCode(block, 'COLOUR', + Blockly.Python.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ')\n'; +}; + +Blockly.Blocks['turtle_colour_internal'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendDummyInput() + .appendField('définis la couleur') + .appendField(new Blockly.FieldColour('#ff0000'), 'COLOUR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Changer la couleur du trait'); + } +}; + +Blockly.JavaScript['turtle_colour_internal'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour_internal'] = function(block) { + // Generate Python for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\')\n'; +}; + +Blockly.Blocks['turtle_repeat_internal'] = { + /** + * Block for repeat n times (internal number). + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.CONTROLS_REPEAT_TITLE, + "args0": [ + { + "type": "field_dropdown", + "name": "TIMES", + "options": [ + ["3", "3"], + ["4", "4"], + ["5", "5"], + ["360", "360"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Blockly.Blocks.loops.HUE, + "tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP, + "helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL + }); + this.appendStatementInput('DO') + .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO); + } +}; + +Blockly.JavaScript['turtle_repeat_internal'] = +Blockly.JavaScript['controls_repeat']; + +Blockly.Python['turtle_repeat_internal'] = +Blockly.Python['controls_repeat']; \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_1/public/create_img.py b/Cours 1 Code.org/Lecon 5/Artist_1/public/create_img.py new file mode 100644 index 0000000..17b5016 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_1/public/create_img.py @@ -0,0 +1,102 @@ +import Image, ImageDraw +import math +import json +import os + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y - length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y + length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for count in range(4): + moveForward(75) + turnRight(90) +moveForward(125) +for count2 in range(4): + moveForward(75) + turnRight(90) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1 Code.org/Lecon 5/Artist_1/public/interpreter.js b/Cours 1 Code.org/Lecon 5/Artist_1/public/interpreter.js new file mode 100644 index 0000000..f4cf3c3 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_1/public/interpreter.js @@ -0,0 +1,63 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(length) { + Turtle.move(length); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.move(-length); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.jumpForward(length); + }; + interpreter.setProperty(scope, 'jumpForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.jumpBackward(length); + }; + interpreter.setProperty(scope, 'jumpBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 0); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 1); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(width) { + Turtle.penWidth(width); + }; + interpreter.setProperty(scope, 'penWidth', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penUp(); + }; + interpreter.setProperty(scope, 'penUp', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penDown(); + }; + interpreter.setProperty(scope, 'penDown', + interpreter.createNativeFunction(wrapper)); + wrapper = function(colour) { + Turtle.penColour(colour); + }; + interpreter.setProperty(scope, 'penColour', + interpreter.createNativeFunction(wrapper)); + + + + + Turtle.log = []; + Turtle.reset(false); +}; + +var animate = function() { + Turtle.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 5/Artist_1/public/solution.png b/Cours 1 Code.org/Lecon 5/Artist_1/public/solution.png new file mode 100644 index 0000000..ca58dc7 Binary files /dev/null and b/Cours 1 Code.org/Lecon 5/Artist_1/public/solution.png differ diff --git a/Cours 1 Code.org/Lecon 5/Artist_1/public/turtle.js b/Cours 1 Code.org/Lecon 5/Artist_1/public/turtle.js new file mode 100644 index 0000000..fcc1521 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_1/public/turtle.js @@ -0,0 +1,402 @@ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Turtle = {}; +window.Maze = {}; + +//Get the json file and it's information +var turtle_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + turtle_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"turtle_config.json" +}else { //When displaying the task + turtle_file = task_directory_path + "turtle_config.json"; +} +var request = new XMLHttpRequest(); +request.open("GET", turtle_file, false); +request.send(null) +var json = JSON.parse(request.responseText); + +var imagePath = "" +if(json.imageSolution) + imagePath = task_directory_path+json.imageName; + +//Code of the solution +var solution = function(){ + //Here, put the javascript corresponding to the solved exercice (or use the image) +} +//Code of the decor +var decoration = function(){ + //Here, put the code for any decor, not part of the exercice + Turtle.turnLeft(30); + Turtle.moveForward(100); + Turtle.turnRight(120); + Turtle.moveForward(40); + Turtle.jumpBackward(40); + Turtle.turnLeft(120); + Turtle.jumpBackward(100); + Turtle.turnRight(30); + Turtle.jumpForward(200); + Turtle.turnLeft(30); + Turtle.moveForward(100); + Turtle.turnRight(120); + Turtle.moveForward(40); +} + +var randomColour = function(){ + var colour = Math.floor(Math.random()*16777215); + return "#"+colour.toString(16).toUpperCase() +} + +//Canvas size +Turtle.CANVAS_WIDTH = json.width; +Turtle.CANVAS_HEIGHT = json.height; + +//Starting position and radius of turtle +Turtle.START_X = json.startX; +Turtle.START_Y = json.startY; +Turtle.RADIUS = json.radius; + +//Current coordinates of turtle +Turtle.CURRENT_COORD = { + x:Turtle.START_X, + y:Turtle.START_Y +} + +//Current heading of turtle +Turtle.HEADING = json.startAngle; + +//Weater or not the pen is down and it's width +Turtle.PEN_DOWN = true; //At start, the pen is always down +Turtle.PEN_WIDTH = json.strokeWidth; +Turtle.PEN_COLOUR = json.strokeColour; + +//Variables used to draw on the solution canvas or the decor canvas +var sol = false; +var decor = false; + +//milisec between each frame +window.stepSpeed = json.animationRate; + +/** +* +* Animations functions +* +*/ + +Turtle.drawMap = function() { + var div = document.getElementById('visualization'); + + // Add the canvas for the turtle + var canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute("style", "border:1px solid #F1EEE7;") + canvas.setAttribute("id", "turtle-canvas"); + div.appendChild(canvas); + // Add the canvas for the user. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "user-canvas"); + div.appendChild(canvas); + // Add the canvas for the solution. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "solution-canvas"); + div.appendChild(canvas); + //Add the canvas for the decor + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none') + canvas.setAttribute("id", "decor-canvas"); + div.appendChild(canvas) + + //Draw the decor + Turtle.drawDecor(); + + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); + + //Draw the turtle + Turtle.updateImage(); + + +} + +Turtle.drawTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + + //Draw the turtle body + ctx.beginPath(); + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, Turtle.RADIUS, 0, 2 * Math.PI); + ctx.stroke(); + + // Draw the turtle head. + var WIDTH = 0.3; + var HEAD_TIP = 10; + var ARROW_TIP = 4; + var BEND = 6; + var radians = 2 * Math.PI * Turtle.HEADING / 360; + var tipX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + HEAD_TIP) * Math.sin(radians); + var tipY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + HEAD_TIP) * Math.cos(radians); + radians -= WIDTH; + var leftX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var leftY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + radians += WIDTH / 2; + var leftControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var leftControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH; + var rightControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var rightControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH / 2; + var rightX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var rightY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + + ctx.beginPath(); + ctx.moveTo(tipX, tipY); + ctx.lineTo(leftX, leftY); + ctx.bezierCurveTo(leftControlX, leftControlY, + rightControlX, rightControlY, rightX, rightY); + ctx.closePath(); + ctx.fillStyle = Turtle.PEN_COLOUR; + ctx.fill(); +} + +Turtle.resetTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + //Clear any previous turtle + ctx.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + +} + +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } +} + +Turtle.drawSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + sol = true; + solution(); + sol = false; + Turtle.reset(true); +} + +Turtle.drawDecor = function(){ + var c = document.getElementById("decor-canvas"); + var ctx = c.getContext("2d") + decor = true; + decoration(); + decor = false; + Turtle.reset(true); +} + +Turtle.animate = function(id) { + switch(id){ + case "move" : + Turtle.updateImage(); + break; + case "turn" : + Turtle.updateImage(); + break; + case "colour": + Turtle.updateImage(); + break; + default: + //This should not happen + console.warn("Unknown animation"); + break; + } +} + +Turtle.updateImage = function(){ + Turtle.resetTurtle(); + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d") + var c2 = document.getElementById("user-canvas"); + var c3 = document.getElementById("solution-canvas"); + var c4 = document.getElementById("decor-canvas"); + ctx1.drawImage(c4, 0, 0); //Fuse any decor + ctx1.drawImage(c3, 0, 0); //Fuse solution canvas + ctx1.drawImage(c2, 0, 0); //Fuse user canvas + Turtle.drawTurtle(); //Add the turtle +} + +Turtle.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Turtle.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + } + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,penUp,penDown,penWidth,penColour,'); + + Turtle.drawMap(); +}; + +Turtle.reset = function(bool){ + if(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + } + Turtle.updateImage(); +} + +//Workaround current plugin implementation that uses Maze as a variable +Maze.reset = function(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + if(!bool){ + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d"); + var c2 = document.getElementById("user-canvas"); + var ctx2 = c2.getContext("2d"); + ctx1.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + ctx2.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + } + Turtle.updateImage(); +} + +/** +* +* Blocks functions +* +*/ +Turtle.move = function(length){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + if (Turtle.PEN_DOWN) { + ctx.beginPath(); + ctx.moveTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + } + if(length){ + Turtle.CURRENT_COORD.x += length * Math.sin(2 * Math.PI * Turtle.HEADING / 360); + Turtle.CURRENT_COORD.y -= length * Math.cos(2 * Math.PI * Turtle.HEADING / 360); + } + if(Turtle.PEN_DOWN){ + ctx.lineWidth = Turtle.PEN_WIDTH; + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.lineTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + ctx.stroke(); + } + Turtle.animate("move"); +} + +Turtle.moveForward = function(length){ + Turtle.move(length); +} + + +Turtle.moveBackward = function(length){ + Turtle.move(-length); +} + +Turtle.jumpForward = function(length){ + Turtle.penUp(); + Turtle.moveForward(length); + Turtle.penDown(); +} + +Turtle.jumpBackward = function(length){ + Turtle.penUp(); + Turtle.moveBackward(length); + Turtle.penDown(); +} + +Turtle.circle = function(radius){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + ctx.beginPath(); + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, radius, 0,2*Math.PI); + ctx.stroke(); + Turtle.updateImage(); +} + +Turtle.turn = function(angle, direction){ + switch (direction){ + case 0: + Turtle.HEADING = (Turtle.HEADING - angle) % 360; + if (Turtle.HEADING < 0) { + Turtle.HEADING += 360; + } + break; + case 1: + Turtle.HEADING = (Turtle.HEADING + angle) % 360; + break; + } + Turtle.animate("turn"); +} + +Turtle.turnRight = function(angle){ + Turtle.turn(angle, 1); +} + +Turtle.turnLeft = function(angle){ + Turtle.turn(angle, 0); +} + +Turtle.penWidth = function(width){ + Turtle.PEN_WIDTH = width; +} + +Turtle.penUp = function(){ + Turtle.PEN_DOWN = false; +} + +Turtle.penDown = function(){ + Turtle.PEN_DOWN = true; +} + +Turtle.penColour = function(colour){ + Turtle.PEN_COLOUR = colour; + Turtle.animate("colour"); +} + +//Called to draw +if (document.getElementById('visualization') != null) { + window.addEventListener('load', Turtle.init); +} else { + console.warn('Cannot find visualization element.'); +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_1/public/turtle_config.json b/Cours 1 Code.org/Lecon 5/Artist_1/public/turtle_config.json new file mode 100644 index 0000000..d53584c --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_1/public/turtle_config.json @@ -0,0 +1,14 @@ +{ + "startX":10, + "startY":150, + "startAngle":90, + "strokeWidth":3, + "strokeColour":"#000000", + "colourSpecific":false, + "radius":15, + "animationRate":50, + "width":300, + "height":300, + "imageSolution":true, + "imageName":"solution.png" +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_1/run b/Cours 1 Code.org/Lecon 5/Artist_1/run new file mode 100644 index 0000000..5d51d53 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_1/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("turtle.py") + + p = subprocess.Popen(shlex.split("python3 turtle.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 5/Artist_1/student/turtle.py b/Cours 1 Code.org/Lecon 5/Artist_1/student/turtle.py new file mode 100644 index 0000000..e0f2f3c --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_1/student/turtle.py @@ -0,0 +1,127 @@ +import math +import json +import os + +#Reset the turtle variables to starting position +def resetTurtle(): + Turtle["x"] = start_x + Turtle["y"] = start_y + Turtle["heading"] = start_heading + Turtle["colour"] = start_pen_colour + Turtle["width"] = start_pen_width + Turtle["penDown"] = True + + +def student_code(): +@ @code@@ + +#Code of the solution +def solution(): + for count in range(4): + moveForward(75) + turnRight(90) + moveForward(125) + for count2 in range(4): + moveForward(75) + turnRight(90) + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +# +# Code to "draw" +# + +# Format stored in the dictionary : +# Point x - Point y (as int) : strokewidth strokeColour + +def moveForward(length): + addPoint() + for i in range(length): + Turtle["x"] += int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] -= int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def moveBackward(length): + addPoint() + for i in range(length): + Turtle["x"] -= int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] += int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def addPoint(): + if(sol): # We are computing the solution + if(count_colours): # Colour is important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: # Colour is not important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + else: # We are computing the student output + if(count_colours): + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + Turtle["heading"] = (Turtle["heading"] + angle) % 360; + +def turnLeft(angle): + Turtle["heading"] = (Turtle["heading"] - angle) % 360; + if (Turtle["heading"] < 0): + Turtle["heading"] += 360 + +def penWidth(width): + Turtle["width"] = width + +def penUp(): + Turtle["penDown"] = False + +def penDown(): + Turtle["penDown"] = True + +def penColour(colour): + Turtle["colour"] = colour + +# Get the json data +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'turtle_config.json') as f: + data = json.load(f) + +# Dictionaries to compare +sol_output = {} +user_output = {} + +# Variables to start +start_x = data["startX"] +start_y = data["startY"] +start_heading = data["startAngle"] +start_pen_width = data["strokeWidth"] +start_pen_colour = data["strokeColour"] + +count_colours = data["colourSpecific"] + +Turtle = {} # Current turtle state is stocked here +resetTurtle() # Set the variable +sol = True # We will execute the solution first. +solution() # Execute the solution +resetTurtle() # Reset the turtle again +sol = False # We are no longer executing the solution +student_code() # Execute the student code + +if(user_output == sol_output): #If the dicts are the same, success + print("True", end='', flush=True) +else: + print("Votre solution est incorrecte, essayez encore", end='', flush=True) \ No newline at end of file diff --git a/Cours 1/Lecon2/Artist_8/task.yaml b/Cours 1 Code.org/Lecon 5/Artist_1/task.yaml similarity index 81% rename from Cours 1/Lecon2/Artist_8/task.yaml rename to Cours 1 Code.org/Lecon 5/Artist_1/task.yaml index 1bfee73..cee462c 100644 --- a/Cours 1/Lecon2/Artist_8/task.yaml +++ b/Cours 1 Code.org/Lecon 5/Artist_1/task.yaml @@ -1,7 +1,7 @@ accessible: true author: Celine Deknop -context: Complète les lignes vertes. Les lignes ont 250 pixels de long et sont à 15 - pixels l’une de l’autre. +context: 'Dessine les verres carrés. Astuce : le pont entre les verres fait 25 pixels + de moins que la longueur du carré.' environment: default evaluate: best groups: false @@ -10,47 +10,20 @@ limits: memory: '100' output: '2' time: '30' -name: Exercice 8 +name: Exercice 1 network_grading: false order: 0 problems: code: - blocks_files: - - blocks.js - options: - css: true - sounds: true - toolboxPosition: start - visual: - position: left - trashcan: true - media: /static/common/js/blockly/media/ - maxBlocks: '45' - scrollbars: true - oneBasedIndex: true - files: - - turtle.js - - interpreter.js - workspace: |- - - - - header: '' - type: blockly toolbox: |- + + turnRight 90 - - - - #228b22 - - - moveForward @@ -67,54 +40,86 @@ problems: - - - - 0 - + + + + + + + #228b22 + + + + + + ??? + + + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: '45' + files: + - turtle.js + - interpreter.js + type: blockly name: '' + blocks_files: + - blocks.js + workspace: |- + + + + header: '' stored_submissions: 0 submission_limit: amount: -1 period: -1 tags: '0': - name: Instructions avec paramètres type: 0 visible: true + name: Instructions avec paramètres id: '1' description: '' '1': - description: '' id: '2' + description: '' type: 0 - name: Boucles "répéter X fois" visible: true + name: Boucles "répéter X fois" '2': - type: 2 - visible: true - description: Exercice faisant partie de la leçon 2 - name: Lecon 2 - id: '' - '3': description: Faisant partie du parcours normal name: Normal type: 2 visible: false id: '' - '4': - description: Faisant partie du parcours facile + '3': name: Facile + description: Faisant partie du parcours facile type: 2 visible: false id: '' - '5': + '4': type: 2 description: Faisant partie du parcours challenge name: Challenge visible: false id: '' + '5': + visible: true + description: '' + name: Lecon 3 + type: 2 + id: '' weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 5/Artist_2/public/blocks.js b/Cours 1 Code.org/Lecon 5/Artist_2/public/blocks.js new file mode 100644 index 0000000..57b6cb3 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_2/public/blocks.js @@ -0,0 +1,399 @@ +/** + * Blockly Games: Turtle Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Turtle application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Turtle.Blocks = {}; + +/** + * Common HSV hue for all blocks in this category. + */ +Turtle.Blocks.HUE = 160; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['turtle_move'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + this.appendValueInput("VALUE") + .setCheck('Number') + .appendField(new Blockly.FieldDropdown( + [ + ["avancer de","moveForward"], + ["reculer de","moveBackward"]] + ), "DIR"); + this.appendDummyInput() + .appendField("pixels"); + this.setColour(Turtle.Blocks.HUE); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setInputsInline(true); + this.setTooltip("Bouger"); + } +}; + +Blockly.JavaScript['turtle_move'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_move'] = function(block) { + // Generate Python for moving forward or backwards. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_move_internal'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['avancer de', 'moveForward'], + ['reculer de', 'moveBackward']]; + var VALUES = + [['20 pixels', '20'], + ['50 pixels', '50'], + ['100 pixels', '100'], + ['150 pixels', '150']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Bouger'); + } +}; + +Blockly.JavaScript['turtle_move_internal'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ');\n'; +}; + +Blockly.Python['turtle_move_internal'] = function(block) { + // Generate Python for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_jump'] = { + init: function() { + this.appendValueInput("VAL") + .setCheck("Number") + .appendField("saute en") + .appendField(new Blockly.FieldDropdown( + [ + ["avant de","jumpForward"], + ["arriere de","jumpBackward"] + ]), "DIR"); + this.appendDummyInput() + .appendField("pixels"); + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip("Bouge sans tracer de ligne"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['turtle_jump'] = function(block) { + var dropdown_dir = block.getFieldValue('DIR'); + var value_val = Blockly.JavaScript.valueToCode(block, 'VAL', Blockly.JavaScript.ORDER_ATOMIC); + var code = dropdown_dir+'('+value_val+');\n'; + return code; +} + +Blockly.Python['turtle_jump'] = function(block) { + var dropdown_dir = block.getFieldValue('DIR'); + var value_val = Blockly.Python.valueToCode(block, 'VAL', Blockly.JavaScript.ORDER_ATOMIC); + var code = dropdown_dir+'('+value_val+')\n'; + return code; +} + +Blockly.Blocks['turtle_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite', 'turnRight'], + ['tourner à gauche', 'turnLeft']]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Turtle.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Turtle.Blocks.LEFT_TURN; + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('VALUE') + .setCheck('Number') + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourner"); + } +}; + +Blockly.JavaScript['turtle_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn'] = function(block) { + // Generate Python for turning left or right. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.Python.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_turn_internal'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite de', 'turnRight'], + ['tourner à gauche de', 'turnLeft']]; + var VALUES = + [['1\u00B0', '1'], + ['45\u00B0', '45'], + ['72\u00B0', '72'], + ['90\u00B0', '90'], + ['120\u00B0', '120'], + ['144\u00B0', '144']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Tourner de X degrés'); + } +}; + +Blockly.JavaScript['turtle_turn_internal'] = function(block) { + // Generate JavaScript for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn_internal'] = function(block) { + // Generate Python for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_width'] = { + /** + * Block for setting the width. + * @this Blockly.Block + */ + init: function() { + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('WIDTH') + .setCheck('Number') + .appendField('Définis l\'épaisseur de la ligne'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Définis l\'épaisseur de la ligne'); + } +}; + +Blockly.JavaScript['turtle_width'] = function(block) { + // Generate JavaScript for setting the width. + var width = Blockly.JavaScript.valueToCode(block, 'WIDTH', + Blockly.JavaScript.ORDER_NONE) || '1'; + return 'penWidth(' + width + ');\n'; +}; + +Blockly.Python['turtle_width'] = function(block) { + // Generate Python for setting the width. + var width = Blockly.Python.valueToCode(block, 'WIDTH', + Blockly.Python.ORDER_NONE) || '1'; + return 'penWidth(' + width + ')\n'; +}; + +Blockly.Blocks['turtle_pen'] = { + /** + * Block for pen up/down. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "%1", + "args0": [ + { + "type": "field_dropdown", + "name": "PEN", + "options": [ + ['lever le crayon', "penUp"], + ['poser le crayon', "penDown"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Turtle.Blocks.HUE, + "tooltip": "Lever ou poser le crayon sur la feuille" + }); + } +}; + +Blockly.JavaScript['turtle_pen'] = function(block) { + // Generate JavaScript for pen up/down. + return block.getFieldValue('PEN') + + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_pen'] = function(block) { + // Generate Python for pen up/down. + return block.getFieldValue('PEN') + + '()\n'; +}; + +Blockly.Blocks['turtle_colour'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendValueInput('COLOUR') + .setCheck('Colour') + .appendField('définis la couleur'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Changer la couleur du trait"); + } +}; + +Blockly.JavaScript['turtle_colour'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = Blockly.JavaScript.valueToCode(block, 'COLOUR', + Blockly.JavaScript.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour'] = function(block) { + // Generate Python for setting the colour. + var colour = Blockly.Python.valueToCode(block, 'COLOUR', + Blockly.Python.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ')\n'; +}; + +Blockly.Blocks['turtle_colour_internal'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendDummyInput() + .appendField('définis la couleur') + .appendField(new Blockly.FieldColour('#ff0000'), 'COLOUR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Changer la couleur du trait'); + } +}; + +Blockly.JavaScript['turtle_colour_internal'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour_internal'] = function(block) { + // Generate Python for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\')\n'; +}; + +Blockly.Blocks['turtle_repeat_internal'] = { + /** + * Block for repeat n times (internal number). + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.CONTROLS_REPEAT_TITLE, + "args0": [ + { + "type": "field_dropdown", + "name": "TIMES", + "options": [ + ["3", "3"], + ["4", "4"], + ["5", "5"], + ["360", "360"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Blockly.Blocks.loops.HUE, + "tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP, + "helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL + }); + this.appendStatementInput('DO') + .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO); + } +}; + +Blockly.JavaScript['turtle_repeat_internal'] = +Blockly.JavaScript['controls_repeat']; + +Blockly.Python['turtle_repeat_internal'] = +Blockly.Python['controls_repeat']; \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_2/public/create_img.py b/Cours 1 Code.org/Lecon 5/Artist_2/public/create_img.py new file mode 100644 index 0000000..153c5ea --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_2/public/create_img.py @@ -0,0 +1,103 @@ +import Image, ImageDraw +import math +import json +import os + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y - length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y + length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for count in range(4): + moveForward(75) + turnRight(90) +moveForward(125) +for count2 in range(4): + moveForward(75) + turnRight(90) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1 Code.org/Lecon 5/Artist_2/public/interpreter.js b/Cours 1 Code.org/Lecon 5/Artist_2/public/interpreter.js new file mode 100644 index 0000000..f4cf3c3 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_2/public/interpreter.js @@ -0,0 +1,63 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(length) { + Turtle.move(length); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.move(-length); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.jumpForward(length); + }; + interpreter.setProperty(scope, 'jumpForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.jumpBackward(length); + }; + interpreter.setProperty(scope, 'jumpBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 0); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 1); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(width) { + Turtle.penWidth(width); + }; + interpreter.setProperty(scope, 'penWidth', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penUp(); + }; + interpreter.setProperty(scope, 'penUp', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penDown(); + }; + interpreter.setProperty(scope, 'penDown', + interpreter.createNativeFunction(wrapper)); + wrapper = function(colour) { + Turtle.penColour(colour); + }; + interpreter.setProperty(scope, 'penColour', + interpreter.createNativeFunction(wrapper)); + + + + + Turtle.log = []; + Turtle.reset(false); +}; + +var animate = function() { + Turtle.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 5/Artist_2/public/solution.png b/Cours 1 Code.org/Lecon 5/Artist_2/public/solution.png new file mode 100644 index 0000000..ca58dc7 Binary files /dev/null and b/Cours 1 Code.org/Lecon 5/Artist_2/public/solution.png differ diff --git a/Cours 1 Code.org/Lecon 5/Artist_2/public/turtle.js b/Cours 1 Code.org/Lecon 5/Artist_2/public/turtle.js new file mode 100644 index 0000000..213643a --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_2/public/turtle.js @@ -0,0 +1,403 @@ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Turtle = {}; +window.Maze = {}; + +//Get the json file and it's information +var turtle_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + turtle_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"turtle_config.json" +}else { //When displaying the task + turtle_file = task_directory_path + "turtle_config.json"; +} +var request = new XMLHttpRequest(); +request.open("GET", turtle_file, false); +request.send(null) +var json = JSON.parse(request.responseText); + +var imagePath = "" +if(json.imageSolution) + imagePath = task_directory_path+json.imageName; + +//Code of the solution +var solution = function(){ + //Here, put the javascript corresponding to the solved exercice (or use the image) +} +//Code of the decor +var decoration = function(){ + //Here, put the code for any decor, not part of the exercice + Turtle.turnLeft(30); + Turtle.moveForward(100); + Turtle.turnRight(120); + Turtle.moveForward(40); + Turtle.jumpBackward(40); + Turtle.turnLeft(120); + Turtle.jumpBackward(100); + Turtle.turnRight(30); + Turtle.jumpForward(200); + Turtle.turnLeft(30); + Turtle.moveForward(100); + Turtle.turnRight(120); + Turtle.moveForward(40); +} + +var randomColour = function(){ + var colour = Math.floor(Math.random()*16777215); + return "#"+colour.toString(16).toUpperCase() +} + +//Canvas size +Turtle.CANVAS_WIDTH = json.width; +Turtle.CANVAS_HEIGHT = json.height; + +//Starting position and radius of turtle +Turtle.START_X = json.startX; +Turtle.START_Y = json.startY; +Turtle.RADIUS = json.radius; + +//Current coordinates of turtle +Turtle.CURRENT_COORD = { + x:Turtle.START_X, + y:Turtle.START_Y +} + +//Current heading of turtle +Turtle.HEADING = json.startAngle; + +//Weater or not the pen is down and it's width +Turtle.PEN_DOWN = true; //At start, the pen is always down +Turtle.PEN_WIDTH = json.strokeWidth; +Turtle.PEN_COLOUR = json.strokeColour; + +//Variables used to draw on the solution canvas or the decor canvas +var sol = false; +var decor = false; + +//milisec between each frame +window.stepSpeed = json.animationRate; + +/** +* +* Animations functions +* +*/ + +Turtle.drawMap = function() { + var div = document.getElementById('visualization'); + + // Add the canvas for the turtle + var canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute("style", "border:1px solid #F1EEE7;") + canvas.setAttribute("id", "turtle-canvas"); + div.appendChild(canvas); + // Add the canvas for the user. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "user-canvas"); + div.appendChild(canvas); + // Add the canvas for the solution. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "solution-canvas"); + div.appendChild(canvas); + //Add the canvas for the decor + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none') + canvas.setAttribute("id", "decor-canvas"); + div.appendChild(canvas) + + //Draw the decor + Turtle.drawDecor(); + + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); + + + //Draw the turtle + Turtle.updateImage(); + + +} + +Turtle.drawTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + + //Draw the turtle body + ctx.beginPath(); + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, Turtle.RADIUS, 0, 2 * Math.PI); + ctx.stroke(); + + // Draw the turtle head. + var WIDTH = 0.3; + var HEAD_TIP = 10; + var ARROW_TIP = 4; + var BEND = 6; + var radians = 2 * Math.PI * Turtle.HEADING / 360; + var tipX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + HEAD_TIP) * Math.sin(radians); + var tipY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + HEAD_TIP) * Math.cos(radians); + radians -= WIDTH; + var leftX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var leftY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + radians += WIDTH / 2; + var leftControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var leftControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH; + var rightControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var rightControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH / 2; + var rightX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var rightY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + + ctx.beginPath(); + ctx.moveTo(tipX, tipY); + ctx.lineTo(leftX, leftY); + ctx.bezierCurveTo(leftControlX, leftControlY, + rightControlX, rightControlY, rightX, rightY); + ctx.closePath(); + ctx.fillStyle = Turtle.PEN_COLOUR; + ctx.fill(); +} + +Turtle.resetTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + //Clear any previous turtle + ctx.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + +} + +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } +} + +Turtle.drawSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + sol = true; + solution(); + sol = false; + Turtle.reset(true); +} + +Turtle.drawDecor = function(){ + var c = document.getElementById("decor-canvas"); + var ctx = c.getContext("2d") + decor = true; + decoration(); + decor = false; + Turtle.reset(true); +} + +Turtle.animate = function(id) { + switch(id){ + case "move" : + Turtle.updateImage(); + break; + case "turn" : + Turtle.updateImage(); + break; + case "colour": + Turtle.updateImage(); + break; + default: + //This should not happen + console.warn("Unknown animation"); + break; + } +} + +Turtle.updateImage = function(){ + Turtle.resetTurtle(); + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d") + var c2 = document.getElementById("user-canvas"); + var c3 = document.getElementById("solution-canvas"); + var c4 = document.getElementById("decor-canvas"); + ctx1.drawImage(c4, 0, 0); //Fuse any decor + ctx1.drawImage(c3, 0, 0); //Fuse solution canvas + ctx1.drawImage(c2, 0, 0); //Fuse user canvas + Turtle.drawTurtle(); //Add the turtle +} + +Turtle.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Turtle.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + } + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,penUp,penDown,penWidth,penColour,'); + + Turtle.drawMap(); +}; + +Turtle.reset = function(bool){ + if(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + } + Turtle.updateImage(); +} + +//Workaround current plugin implementation that uses Maze as a variable +Maze.reset = function(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + if(!bool){ + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d"); + var c2 = document.getElementById("user-canvas"); + var ctx2 = c2.getContext("2d"); + ctx1.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + ctx2.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + } + Turtle.updateImage(); +} + +/** +* +* Blocks functions +* +*/ +Turtle.move = function(length){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + if (Turtle.PEN_DOWN) { + ctx.beginPath(); + ctx.moveTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + } + if(length){ + Turtle.CURRENT_COORD.x += length * Math.sin(2 * Math.PI * Turtle.HEADING / 360); + Turtle.CURRENT_COORD.y -= length * Math.cos(2 * Math.PI * Turtle.HEADING / 360); + } + if(Turtle.PEN_DOWN){ + ctx.lineWidth = Turtle.PEN_WIDTH; + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.lineTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + ctx.stroke(); + } + Turtle.animate("move"); +} + +Turtle.moveForward = function(length){ + Turtle.move(length); +} + + +Turtle.moveBackward = function(length){ + Turtle.move(-length); +} + +Turtle.jumpForward = function(length){ + Turtle.penUp(); + Turtle.moveForward(length); + Turtle.penDown(); +} + +Turtle.jumpBackward = function(length){ + Turtle.penUp(); + Turtle.moveBackward(length); + Turtle.penDown(); +} + +Turtle.circle = function(radius){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + ctx.beginPath(); + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, radius, 0,2*Math.PI); + ctx.stroke(); + Turtle.updateImage(); +} + +Turtle.turn = function(angle, direction){ + switch (direction){ + case 0: + Turtle.HEADING = (Turtle.HEADING - angle) % 360; + if (Turtle.HEADING < 0) { + Turtle.HEADING += 360; + } + break; + case 1: + Turtle.HEADING = (Turtle.HEADING + angle) % 360; + break; + } + Turtle.animate("turn"); +} + +Turtle.turnRight = function(angle){ + Turtle.turn(angle, 1); +} + +Turtle.turnLeft = function(angle){ + Turtle.turn(angle, 0); +} + +Turtle.penWidth = function(width){ + Turtle.PEN_WIDTH = width; +} + +Turtle.penUp = function(){ + Turtle.PEN_DOWN = false; +} + +Turtle.penDown = function(){ + Turtle.PEN_DOWN = true; +} + +Turtle.penColour = function(colour){ + Turtle.PEN_COLOUR = colour; + Turtle.animate("colour"); +} + +//Called to draw +if (document.getElementById('visualization') != null) { + window.addEventListener('load', Turtle.init); +} else { + console.warn('Cannot find visualization element.'); +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_2/public/turtle_config.json b/Cours 1 Code.org/Lecon 5/Artist_2/public/turtle_config.json new file mode 100644 index 0000000..d53584c --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_2/public/turtle_config.json @@ -0,0 +1,14 @@ +{ + "startX":10, + "startY":150, + "startAngle":90, + "strokeWidth":3, + "strokeColour":"#000000", + "colourSpecific":false, + "radius":15, + "animationRate":50, + "width":300, + "height":300, + "imageSolution":true, + "imageName":"solution.png" +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_2/run b/Cours 1 Code.org/Lecon 5/Artist_2/run new file mode 100644 index 0000000..5d51d53 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_2/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("turtle.py") + + p = subprocess.Popen(shlex.split("python3 turtle.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 5/Artist_2/student/turtle.py b/Cours 1 Code.org/Lecon 5/Artist_2/student/turtle.py new file mode 100644 index 0000000..e0f2f3c --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_2/student/turtle.py @@ -0,0 +1,127 @@ +import math +import json +import os + +#Reset the turtle variables to starting position +def resetTurtle(): + Turtle["x"] = start_x + Turtle["y"] = start_y + Turtle["heading"] = start_heading + Turtle["colour"] = start_pen_colour + Turtle["width"] = start_pen_width + Turtle["penDown"] = True + + +def student_code(): +@ @code@@ + +#Code of the solution +def solution(): + for count in range(4): + moveForward(75) + turnRight(90) + moveForward(125) + for count2 in range(4): + moveForward(75) + turnRight(90) + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +# +# Code to "draw" +# + +# Format stored in the dictionary : +# Point x - Point y (as int) : strokewidth strokeColour + +def moveForward(length): + addPoint() + for i in range(length): + Turtle["x"] += int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] -= int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def moveBackward(length): + addPoint() + for i in range(length): + Turtle["x"] -= int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] += int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def addPoint(): + if(sol): # We are computing the solution + if(count_colours): # Colour is important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: # Colour is not important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + else: # We are computing the student output + if(count_colours): + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + Turtle["heading"] = (Turtle["heading"] + angle) % 360; + +def turnLeft(angle): + Turtle["heading"] = (Turtle["heading"] - angle) % 360; + if (Turtle["heading"] < 0): + Turtle["heading"] += 360 + +def penWidth(width): + Turtle["width"] = width + +def penUp(): + Turtle["penDown"] = False + +def penDown(): + Turtle["penDown"] = True + +def penColour(colour): + Turtle["colour"] = colour + +# Get the json data +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'turtle_config.json') as f: + data = json.load(f) + +# Dictionaries to compare +sol_output = {} +user_output = {} + +# Variables to start +start_x = data["startX"] +start_y = data["startY"] +start_heading = data["startAngle"] +start_pen_width = data["strokeWidth"] +start_pen_colour = data["strokeColour"] + +count_colours = data["colourSpecific"] + +Turtle = {} # Current turtle state is stocked here +resetTurtle() # Set the variable +sol = True # We will execute the solution first. +solution() # Execute the solution +resetTurtle() # Reset the turtle again +sol = False # We are no longer executing the solution +student_code() # Execute the student code + +if(user_output == sol_output): #If the dicts are the same, success + print("True", end='', flush=True) +else: + print("Votre solution est incorrecte, essayez encore", end='', flush=True) \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_2/task.yaml b/Cours 1 Code.org/Lecon 5/Artist_2/task.yaml new file mode 100644 index 0000000..21aabdc --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_2/task.yaml @@ -0,0 +1,157 @@ +accessible: true +author: Celine Deknop +context: Les fonctions te permettent de définir de nouveaux blocs ! Nous avons déplacé + les blocs pour dessiner un carré dans une fonction appelée «Dessiner un carré». + Peut-tu utiliser la fonction «Dessiner un carré» pour créer les lunettes ? Les + carrés sont toujours à 50 pixels l'un de l'autre. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 2 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + + turnRight + 90 + + + moveForward + + + 250 + + + + + jumpForward + + + 100 + + + + + + + + ??? + + + + + + + + + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + css: true + trashcan: true + toolboxPosition: start + sounds: true + maxBlocks: '45' + files: + - turtle.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + Dessiner un carré + Dessine un carré de 75 sur 75 + + + + + 4 + + + + + moveForward + + + 75 + + + + + turnRight + 90 + + + + + + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + visible: true + name: Instructions avec paramètres + type: 0 + id: '1' + description: '' + '1': + id: '2' + description: '' + type: 0 + visible: true + name: Boucles "répéter X fois" + '2': + description: Utilisation d'une fonction + name: Appel de fonctions + id: '3' + visible: true + type: 0 + '3': + name: Normal + description: Faisant partie du parcours normal + type: 2 + visible: false + id: '' + '4': + type: 2 + description: Faisant partie du parcours facile + name: Facile + visible: false + id: '' + '5': + description: Faisant partie du parcours challenge + name: Challenge + type: 2 + visible: false + id: '' + '6': + description: '' + type: 2 + name: Lecon 3 + visible: true + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 5/Artist_3/public/blocks.js b/Cours 1 Code.org/Lecon 5/Artist_3/public/blocks.js new file mode 100644 index 0000000..57b6cb3 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_3/public/blocks.js @@ -0,0 +1,399 @@ +/** + * Blockly Games: Turtle Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Turtle application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Turtle.Blocks = {}; + +/** + * Common HSV hue for all blocks in this category. + */ +Turtle.Blocks.HUE = 160; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['turtle_move'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + this.appendValueInput("VALUE") + .setCheck('Number') + .appendField(new Blockly.FieldDropdown( + [ + ["avancer de","moveForward"], + ["reculer de","moveBackward"]] + ), "DIR"); + this.appendDummyInput() + .appendField("pixels"); + this.setColour(Turtle.Blocks.HUE); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setInputsInline(true); + this.setTooltip("Bouger"); + } +}; + +Blockly.JavaScript['turtle_move'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_move'] = function(block) { + // Generate Python for moving forward or backwards. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_move_internal'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['avancer de', 'moveForward'], + ['reculer de', 'moveBackward']]; + var VALUES = + [['20 pixels', '20'], + ['50 pixels', '50'], + ['100 pixels', '100'], + ['150 pixels', '150']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Bouger'); + } +}; + +Blockly.JavaScript['turtle_move_internal'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ');\n'; +}; + +Blockly.Python['turtle_move_internal'] = function(block) { + // Generate Python for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_jump'] = { + init: function() { + this.appendValueInput("VAL") + .setCheck("Number") + .appendField("saute en") + .appendField(new Blockly.FieldDropdown( + [ + ["avant de","jumpForward"], + ["arriere de","jumpBackward"] + ]), "DIR"); + this.appendDummyInput() + .appendField("pixels"); + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip("Bouge sans tracer de ligne"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['turtle_jump'] = function(block) { + var dropdown_dir = block.getFieldValue('DIR'); + var value_val = Blockly.JavaScript.valueToCode(block, 'VAL', Blockly.JavaScript.ORDER_ATOMIC); + var code = dropdown_dir+'('+value_val+');\n'; + return code; +} + +Blockly.Python['turtle_jump'] = function(block) { + var dropdown_dir = block.getFieldValue('DIR'); + var value_val = Blockly.Python.valueToCode(block, 'VAL', Blockly.JavaScript.ORDER_ATOMIC); + var code = dropdown_dir+'('+value_val+')\n'; + return code; +} + +Blockly.Blocks['turtle_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite', 'turnRight'], + ['tourner à gauche', 'turnLeft']]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Turtle.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Turtle.Blocks.LEFT_TURN; + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('VALUE') + .setCheck('Number') + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourner"); + } +}; + +Blockly.JavaScript['turtle_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn'] = function(block) { + // Generate Python for turning left or right. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.Python.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_turn_internal'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite de', 'turnRight'], + ['tourner à gauche de', 'turnLeft']]; + var VALUES = + [['1\u00B0', '1'], + ['45\u00B0', '45'], + ['72\u00B0', '72'], + ['90\u00B0', '90'], + ['120\u00B0', '120'], + ['144\u00B0', '144']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Tourner de X degrés'); + } +}; + +Blockly.JavaScript['turtle_turn_internal'] = function(block) { + // Generate JavaScript for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn_internal'] = function(block) { + // Generate Python for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_width'] = { + /** + * Block for setting the width. + * @this Blockly.Block + */ + init: function() { + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('WIDTH') + .setCheck('Number') + .appendField('Définis l\'épaisseur de la ligne'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Définis l\'épaisseur de la ligne'); + } +}; + +Blockly.JavaScript['turtle_width'] = function(block) { + // Generate JavaScript for setting the width. + var width = Blockly.JavaScript.valueToCode(block, 'WIDTH', + Blockly.JavaScript.ORDER_NONE) || '1'; + return 'penWidth(' + width + ');\n'; +}; + +Blockly.Python['turtle_width'] = function(block) { + // Generate Python for setting the width. + var width = Blockly.Python.valueToCode(block, 'WIDTH', + Blockly.Python.ORDER_NONE) || '1'; + return 'penWidth(' + width + ')\n'; +}; + +Blockly.Blocks['turtle_pen'] = { + /** + * Block for pen up/down. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "%1", + "args0": [ + { + "type": "field_dropdown", + "name": "PEN", + "options": [ + ['lever le crayon', "penUp"], + ['poser le crayon', "penDown"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Turtle.Blocks.HUE, + "tooltip": "Lever ou poser le crayon sur la feuille" + }); + } +}; + +Blockly.JavaScript['turtle_pen'] = function(block) { + // Generate JavaScript for pen up/down. + return block.getFieldValue('PEN') + + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_pen'] = function(block) { + // Generate Python for pen up/down. + return block.getFieldValue('PEN') + + '()\n'; +}; + +Blockly.Blocks['turtle_colour'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendValueInput('COLOUR') + .setCheck('Colour') + .appendField('définis la couleur'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Changer la couleur du trait"); + } +}; + +Blockly.JavaScript['turtle_colour'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = Blockly.JavaScript.valueToCode(block, 'COLOUR', + Blockly.JavaScript.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour'] = function(block) { + // Generate Python for setting the colour. + var colour = Blockly.Python.valueToCode(block, 'COLOUR', + Blockly.Python.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ')\n'; +}; + +Blockly.Blocks['turtle_colour_internal'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendDummyInput() + .appendField('définis la couleur') + .appendField(new Blockly.FieldColour('#ff0000'), 'COLOUR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Changer la couleur du trait'); + } +}; + +Blockly.JavaScript['turtle_colour_internal'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour_internal'] = function(block) { + // Generate Python for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\')\n'; +}; + +Blockly.Blocks['turtle_repeat_internal'] = { + /** + * Block for repeat n times (internal number). + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.CONTROLS_REPEAT_TITLE, + "args0": [ + { + "type": "field_dropdown", + "name": "TIMES", + "options": [ + ["3", "3"], + ["4", "4"], + ["5", "5"], + ["360", "360"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Blockly.Blocks.loops.HUE, + "tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP, + "helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL + }); + this.appendStatementInput('DO') + .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO); + } +}; + +Blockly.JavaScript['turtle_repeat_internal'] = +Blockly.JavaScript['controls_repeat']; + +Blockly.Python['turtle_repeat_internal'] = +Blockly.Python['controls_repeat']; \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_3/public/create_img.py b/Cours 1 Code.org/Lecon 5/Artist_3/public/create_img.py new file mode 100644 index 0000000..c6b5dc6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_3/public/create_img.py @@ -0,0 +1,101 @@ +import Image, ImageDraw +import math +import json +import os + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y - length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y + length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for count2 in range(3): + turnRight(120) + for count in range(4): + moveForward(75) + turnRight(90) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1 Code.org/Lecon 5/Artist_3/public/interpreter.js b/Cours 1 Code.org/Lecon 5/Artist_3/public/interpreter.js new file mode 100644 index 0000000..f4cf3c3 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_3/public/interpreter.js @@ -0,0 +1,63 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(length) { + Turtle.move(length); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.move(-length); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.jumpForward(length); + }; + interpreter.setProperty(scope, 'jumpForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.jumpBackward(length); + }; + interpreter.setProperty(scope, 'jumpBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 0); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 1); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(width) { + Turtle.penWidth(width); + }; + interpreter.setProperty(scope, 'penWidth', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penUp(); + }; + interpreter.setProperty(scope, 'penUp', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penDown(); + }; + interpreter.setProperty(scope, 'penDown', + interpreter.createNativeFunction(wrapper)); + wrapper = function(colour) { + Turtle.penColour(colour); + }; + interpreter.setProperty(scope, 'penColour', + interpreter.createNativeFunction(wrapper)); + + + + + Turtle.log = []; + Turtle.reset(false); +}; + +var animate = function() { + Turtle.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 5/Artist_3/public/solution.png b/Cours 1 Code.org/Lecon 5/Artist_3/public/solution.png new file mode 100644 index 0000000..5bd2ffe Binary files /dev/null and b/Cours 1 Code.org/Lecon 5/Artist_3/public/solution.png differ diff --git a/Cours 1/Lecon2/Artist_8/public/turtle.js b/Cours 1 Code.org/Lecon 5/Artist_3/public/turtle.js similarity index 94% rename from Cours 1/Lecon2/Artist_8/public/turtle.js rename to Cours 1 Code.org/Lecon 5/Artist_3/public/turtle.js index 6a7531d..ee3aec3 100644 --- a/Cours 1/Lecon2/Artist_8/public/turtle.js +++ b/Cours 1 Code.org/Lecon 5/Artist_3/public/turtle.js @@ -16,35 +16,17 @@ request.open("GET", turtle_file, false); request.send(null) var json = JSON.parse(request.responseText); +var imagePath = "" +if(json.imageSolution) + imagePath = task_directory_path+json.imageName; + //Code of the solution var solution = function(){ //Here, put the javascript corresponding to the solved exercice - Turtle.penColour('#228b22'); - var i; - for(i = 0; i < 10; i++){ - Turtle.moveForward(250); - Turtle.moveBackward(250); - Turtle.turnLeft(90); - Turtle.jumpForward(15); - Turtle.turnRight(90); - } } //Code of the decor var decoration = function(){ //Here, put the code for any decor, not part of the exercice - Turtle.jumpForward(50); - Turtle.turnLeft(90); - Turtle.jumpForward(145); - Turtle.penWidth(4); - Turtle.moveForward(50); - Turtle.turnRight(90); - Turtle.jumpForward(15); - Turtle.turnRight(180); - Turtle.penColour("#FF0000") - for(var i = 0; i < 6; i++){ - Turtle.moveForward(30); - Turtle.turnRight(60); - } } var randomColour = function(){ @@ -123,8 +105,10 @@ Turtle.drawMap = function() { //Draw the decor Turtle.drawDecor(); - //Draw the solution - Turtle.drawSolution(); + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); //Draw the turtle Turtle.updateImage(); @@ -181,6 +165,18 @@ Turtle.resetTurtle = function(){ } +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } +} + Turtle.drawSolution = function(){ var c = document.getElementById("solution-canvas"); var ctx = c.getContext("2d") diff --git a/Cours 1 Code.org/Lecon 5/Artist_3/public/turtle_config.json b/Cours 1 Code.org/Lecon 5/Artist_3/public/turtle_config.json new file mode 100644 index 0000000..812b2c5 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_3/public/turtle_config.json @@ -0,0 +1,14 @@ +{ + "startX":150, + "startY":150, + "startAngle":90, + "strokeWidth":3, + "strokeColour":"#000000", + "colourSpecific":false, + "radius":15, + "animationRate":50, + "width":300, + "height":300, + "imageSolution":true, + "imageName":"solution.png" +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_3/run b/Cours 1 Code.org/Lecon 5/Artist_3/run new file mode 100644 index 0000000..5d51d53 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_3/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("turtle.py") + + p = subprocess.Popen(shlex.split("python3 turtle.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 5/Artist_3/student/turtle.py b/Cours 1 Code.org/Lecon 5/Artist_3/student/turtle.py new file mode 100644 index 0000000..7d72ebf --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_3/student/turtle.py @@ -0,0 +1,125 @@ +import math +import json +import os + +#Reset the turtle variables to starting position +def resetTurtle(): + Turtle["x"] = start_x + Turtle["y"] = start_y + Turtle["heading"] = start_heading + Turtle["colour"] = start_pen_colour + Turtle["width"] = start_pen_width + Turtle["penDown"] = True + + +def student_code(): +@ @code@@ + +#Code of the solution +def solution(): + for count2 in range(3): + turnRight(120) + for count in range(4): + moveForward(75) + turnRight(90) + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +# +# Code to "draw" +# + +# Format stored in the dictionary : +# Point x - Point y (as int) : strokewidth strokeColour + +def moveForward(length): + addPoint() + for i in range(length): + Turtle["x"] += int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] -= int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def moveBackward(length): + addPoint() + for i in range(length): + Turtle["x"] -= int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] += int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def addPoint(): + if(sol): # We are computing the solution + if(count_colours): # Colour is important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: # Colour is not important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + else: # We are computing the student output + if(count_colours): + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + Turtle["heading"] = (Turtle["heading"] + angle) % 360; + +def turnLeft(angle): + Turtle["heading"] = (Turtle["heading"] - angle) % 360; + if (Turtle["heading"] < 0): + Turtle["heading"] += 360 + +def penWidth(width): + Turtle["width"] = width + +def penUp(): + Turtle["penDown"] = False + +def penDown(): + Turtle["penDown"] = True + +def penColour(colour): + Turtle["colour"] = colour + +# Get the json data +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'turtle_config.json') as f: + data = json.load(f) + +# Dictionaries to compare +sol_output = {} +user_output = {} + +# Variables to start +start_x = data["startX"] +start_y = data["startY"] +start_heading = data["startAngle"] +start_pen_width = data["strokeWidth"] +start_pen_colour = data["strokeColour"] + +count_colours = data["colourSpecific"] + +Turtle = {} # Current turtle state is stocked here +resetTurtle() # Set the variable +sol = True # We will execute the solution first. +solution() # Execute the solution +resetTurtle() # Reset the turtle again +sol = False # We are no longer executing the solution +student_code() # Execute the student code + +if(user_output == sol_output): #If the dicts are the same, success + print("True", end='', flush=True) +else: + print("Votre solution est incorrecte, essayez encore", end='', flush=True) \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_3/task.yaml b/Cours 1 Code.org/Lecon 5/Artist_3/task.yaml new file mode 100644 index 0000000..73e98b0 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_3/task.yaml @@ -0,0 +1,154 @@ +accessible: true +author: Celine Deknop +context: Utilise le bloc "répéter" pour dessiner trois carrés +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 3 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + + turnRight + 90 + + + moveForward + + + 250 + + + + + jumpForward + + + 100 + + + + + + + + ??? + + + + + + + + + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + css: true + trashcan: true + toolboxPosition: start + sounds: true + maxBlocks: '45' + files: + - turtle.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + Dessiner un carré + Décrire cette fonction… + + + + + 4 + + + + + moveForward + + + 75 + + + + + turnRight + 90 + + + + + + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + visible: true + name: Instructions avec paramètres + type: 0 + id: '1' + description: '' + '1': + id: '2' + description: '' + type: 0 + visible: true + name: Boucles "répéter X fois" + '2': + description: Utilisation d'une fonction + name: Appel de fonctions + id: '3' + visible: true + type: 0 + '3': + name: Normal + description: Faisant partie du parcours normal + type: 2 + visible: false + id: '' + '4': + type: 2 + description: Faisant partie du parcours facile + name: Facile + visible: false + id: '' + '5': + description: Faisant partie du parcours challenge + name: Challenge + type: 2 + visible: false + id: '' + '6': + description: '' + type: 2 + name: Lecon 3 + visible: true + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 5/Artist_4/public/blocks.js b/Cours 1 Code.org/Lecon 5/Artist_4/public/blocks.js new file mode 100644 index 0000000..8451353 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_4/public/blocks.js @@ -0,0 +1,400 @@ +/** + * Blockly Games: Turtle Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Turtle application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Turtle.Blocks = {}; + +/** + * Common HSV hue for all blocks in this category. + */ +Turtle.Blocks.HUE = 160; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['turtle_move'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + this.appendValueInput("VALUE") + .setCheck('Number') + .appendField(new Blockly.FieldDropdown( + [ + ["avancer de","moveForward"], + ["reculer de","moveBackward"]] + ), "DIR"); + this.appendDummyInput() + .appendField("pixels"); + this.setColour(Turtle.Blocks.HUE); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setInputsInline(true); + this.setTooltip("Bouger"); + } +}; + +Blockly.JavaScript['turtle_move'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_move'] = function(block) { + // Generate Python for moving forward or backwards. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_move_internal'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['avancer de', 'moveForward'], + ['reculer de', 'moveBackward']]; + var VALUES = + [['20 pixels', '20'], + ['50 pixels', '50'], + ['100 pixels', '100'], + ['150 pixels', '150']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Bouger'); + } +}; + +Blockly.JavaScript['turtle_move_internal'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ');\n'; +}; + +Blockly.Python['turtle_move_internal'] = function(block) { + // Generate Python for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_jump'] = { + init: function() { + this.appendValueInput("VAL") + .setCheck("Number") + .appendField("saute en") + .appendField(new Blockly.FieldDropdown( + [ + ["avant de","jumpForward"], + ["arriere de","jumpBackward"] + ]), "DIR"); + this.appendDummyInput() + .appendField("pixels"); + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip("Bouge sans tracer de ligne"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['turtle_jump'] = function(block) { + var dropdown_dir = block.getFieldValue('DIR'); + var value_val = Blockly.JavaScript.valueToCode(block, 'VAL', Blockly.JavaScript.ORDER_ATOMIC); + var code = dropdown_dir+'('+value_val+');\n'; + return code; +} + +Blockly.Python['turtle_jump'] = function(block) { + var dropdown_dir = block.getFieldValue('DIR'); + var value_val = Blockly.Python.valueToCode(block, 'VAL', Blockly.JavaScript.ORDER_ATOMIC); + var code = dropdown_dir+'('+value_val+')\n'; + return code; +} + +Blockly.Blocks['turtle_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite', 'turnRight'], + ['tourner à gauche', 'turnLeft']]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Turtle.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Turtle.Blocks.LEFT_TURN; + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('VALUE') + .setCheck('Number') + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourner"); + } +}; + +Blockly.JavaScript['turtle_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn'] = function(block) { + // Generate Python for turning left or right. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.Python.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_turn_internal'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite de', 'turnRight'], + ['tourner à gauche de', 'turnLeft']]; + var VALUES = + [['1\u00B0', '1'], + ['45\u00B0', '45'], + ['60\u00B0', '60'], + ['72\u00B0', '72'], + ['90\u00B0', '90'], + ['120\u00B0', '120'], + ['144\u00B0', '144']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Tourner de X degrés'); + } +}; + +Blockly.JavaScript['turtle_turn_internal'] = function(block) { + // Generate JavaScript for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn_internal'] = function(block) { + // Generate Python for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_width'] = { + /** + * Block for setting the width. + * @this Blockly.Block + */ + init: function() { + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('WIDTH') + .setCheck('Number') + .appendField('Définis l\'épaisseur de la ligne'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Définis l\'épaisseur de la ligne'); + } +}; + +Blockly.JavaScript['turtle_width'] = function(block) { + // Generate JavaScript for setting the width. + var width = Blockly.JavaScript.valueToCode(block, 'WIDTH', + Blockly.JavaScript.ORDER_NONE) || '1'; + return 'penWidth(' + width + ');\n'; +}; + +Blockly.Python['turtle_width'] = function(block) { + // Generate Python for setting the width. + var width = Blockly.Python.valueToCode(block, 'WIDTH', + Blockly.Python.ORDER_NONE) || '1'; + return 'penWidth(' + width + ')\n'; +}; + +Blockly.Blocks['turtle_pen'] = { + /** + * Block for pen up/down. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "%1", + "args0": [ + { + "type": "field_dropdown", + "name": "PEN", + "options": [ + ['lever le crayon', "penUp"], + ['poser le crayon', "penDown"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Turtle.Blocks.HUE, + "tooltip": "Lever ou poser le crayon sur la feuille" + }); + } +}; + +Blockly.JavaScript['turtle_pen'] = function(block) { + // Generate JavaScript for pen up/down. + return block.getFieldValue('PEN') + + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_pen'] = function(block) { + // Generate Python for pen up/down. + return block.getFieldValue('PEN') + + '()\n'; +}; + +Blockly.Blocks['turtle_colour'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendValueInput('COLOUR') + .setCheck('Colour') + .appendField('définis la couleur'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Changer la couleur du trait"); + } +}; + +Blockly.JavaScript['turtle_colour'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = Blockly.JavaScript.valueToCode(block, 'COLOUR', + Blockly.JavaScript.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour'] = function(block) { + // Generate Python for setting the colour. + var colour = Blockly.Python.valueToCode(block, 'COLOUR', + Blockly.Python.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ')\n'; +}; + +Blockly.Blocks['turtle_colour_internal'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendDummyInput() + .appendField('définis la couleur') + .appendField(new Blockly.FieldColour('#ff0000'), 'COLOUR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Changer la couleur du trait'); + } +}; + +Blockly.JavaScript['turtle_colour_internal'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour_internal'] = function(block) { + // Generate Python for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\')\n'; +}; + +Blockly.Blocks['turtle_repeat_internal'] = { + /** + * Block for repeat n times (internal number). + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.CONTROLS_REPEAT_TITLE, + "args0": [ + { + "type": "field_dropdown", + "name": "TIMES", + "options": [ + ["3", "3"], + ["4", "4"], + ["5", "5"], + ["360", "360"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Blockly.Blocks.loops.HUE, + "tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP, + "helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL + }); + this.appendStatementInput('DO') + .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO); + } +}; + +Blockly.JavaScript['turtle_repeat_internal'] = +Blockly.JavaScript['controls_repeat']; + +Blockly.Python['turtle_repeat_internal'] = +Blockly.Python['controls_repeat']; \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_4/public/create_img.py b/Cours 1 Code.org/Lecon 5/Artist_4/public/create_img.py new file mode 100644 index 0000000..37f624e --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_4/public/create_img.py @@ -0,0 +1,101 @@ +import Image, ImageDraw +import math +import json +import os + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y - length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y + length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for count2 in range(3): + turnRight(120) + for count in range(6): + moveForward(50) + turnRight(60) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1 Code.org/Lecon 5/Artist_4/public/interpreter.js b/Cours 1 Code.org/Lecon 5/Artist_4/public/interpreter.js new file mode 100644 index 0000000..f4cf3c3 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_4/public/interpreter.js @@ -0,0 +1,63 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(length) { + Turtle.move(length); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.move(-length); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.jumpForward(length); + }; + interpreter.setProperty(scope, 'jumpForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.jumpBackward(length); + }; + interpreter.setProperty(scope, 'jumpBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 0); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 1); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(width) { + Turtle.penWidth(width); + }; + interpreter.setProperty(scope, 'penWidth', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penUp(); + }; + interpreter.setProperty(scope, 'penUp', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penDown(); + }; + interpreter.setProperty(scope, 'penDown', + interpreter.createNativeFunction(wrapper)); + wrapper = function(colour) { + Turtle.penColour(colour); + }; + interpreter.setProperty(scope, 'penColour', + interpreter.createNativeFunction(wrapper)); + + + + + Turtle.log = []; + Turtle.reset(false); +}; + +var animate = function() { + Turtle.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 5/Artist_4/public/solution.png b/Cours 1 Code.org/Lecon 5/Artist_4/public/solution.png new file mode 100644 index 0000000..af34022 Binary files /dev/null and b/Cours 1 Code.org/Lecon 5/Artist_4/public/solution.png differ diff --git a/Cours 1 Code.org/Lecon 5/Artist_4/public/turtle.js b/Cours 1 Code.org/Lecon 5/Artist_4/public/turtle.js new file mode 100644 index 0000000..efbecc9 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_4/public/turtle.js @@ -0,0 +1,389 @@ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Turtle = {}; +window.Maze = {}; + +//Get the json file and it's information +var turtle_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + turtle_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"turtle_config.json" +}else { //When displaying the task + turtle_file = task_directory_path + "turtle_config.json"; +} +var request = new XMLHttpRequest(); +request.open("GET", turtle_file, false); +request.send(null) +var json = JSON.parse(request.responseText); + +var imagePath = "" +if(json.imageSolution) + imagePath = task_directory_path+json.imageName; + +//Code of the solution +var solution = function(){ + //Here, put the javascript corresponding to the solved exercice (or use the image) +} +//Code of the decor +var decoration = function(){ + //Here, put the code for any decor, not part of the exercice +} + +var randomColour = function(){ + var colour = Math.floor(Math.random()*16777215); + return "#"+colour.toString(16).toUpperCase() +} + +//Canvas size +Turtle.CANVAS_WIDTH = json.width; +Turtle.CANVAS_HEIGHT = json.height; + +//Starting position and radius of turtle +Turtle.START_X = json.startX; +Turtle.START_Y = json.startY; +Turtle.RADIUS = json.radius; + +//Current coordinates of turtle +Turtle.CURRENT_COORD = { + x:Turtle.START_X, + y:Turtle.START_Y +} + +//Current heading of turtle +Turtle.HEADING = json.startAngle; + +//Weater or not the pen is down and it's width +Turtle.PEN_DOWN = true; //At start, the pen is always down +Turtle.PEN_WIDTH = json.strokeWidth; +Turtle.PEN_COLOUR = json.strokeColour; + +//Variables used to draw on the solution canvas or the decor canvas +var sol = false; +var decor = false; + +//milisec between each frame +window.stepSpeed = json.animationRate; + +/** +* +* Animations functions +* +*/ + +Turtle.drawMap = function() { + var div = document.getElementById('visualization'); + + // Add the canvas for the turtle + var canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute("style", "border:1px solid #F1EEE7;") + canvas.setAttribute("id", "turtle-canvas"); + div.appendChild(canvas); + // Add the canvas for the user. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "user-canvas"); + div.appendChild(canvas); + // Add the canvas for the solution. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "solution-canvas"); + div.appendChild(canvas); + //Add the canvas for the decor + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none') + canvas.setAttribute("id", "decor-canvas"); + div.appendChild(canvas) + + //Draw the decor + Turtle.drawDecor(); + + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); + + //Draw the turtle + Turtle.updateImage(); + + +} + +Turtle.drawTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + + //Draw the turtle body + ctx.beginPath(); + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, Turtle.RADIUS, 0, 2 * Math.PI); + ctx.stroke(); + + // Draw the turtle head. + var WIDTH = 0.3; + var HEAD_TIP = 10; + var ARROW_TIP = 4; + var BEND = 6; + var radians = 2 * Math.PI * Turtle.HEADING / 360; + var tipX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + HEAD_TIP) * Math.sin(radians); + var tipY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + HEAD_TIP) * Math.cos(radians); + radians -= WIDTH; + var leftX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var leftY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + radians += WIDTH / 2; + var leftControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var leftControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH; + var rightControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var rightControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH / 2; + var rightX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var rightY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + + ctx.beginPath(); + ctx.moveTo(tipX, tipY); + ctx.lineTo(leftX, leftY); + ctx.bezierCurveTo(leftControlX, leftControlY, + rightControlX, rightControlY, rightX, rightY); + ctx.closePath(); + ctx.fillStyle = Turtle.PEN_COLOUR; + ctx.fill(); +} + +Turtle.resetTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + //Clear any previous turtle + ctx.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + +} + +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } +} + +Turtle.drawSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + sol = true; + solution(); + sol = false; + Turtle.reset(true); +} + +Turtle.drawDecor = function(){ + var c = document.getElementById("decor-canvas"); + var ctx = c.getContext("2d") + decor = true; + decoration(); + decor = false; + Turtle.reset(true); +} + +Turtle.animate = function(id) { + switch(id){ + case "move" : + Turtle.updateImage(); + break; + case "turn" : + Turtle.updateImage(); + break; + case "colour": + Turtle.updateImage(); + break; + default: + //This should not happen + console.warn("Unknown animation"); + break; + } +} + +Turtle.updateImage = function(){ + Turtle.resetTurtle(); + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d") + var c2 = document.getElementById("user-canvas"); + var c3 = document.getElementById("solution-canvas"); + var c4 = document.getElementById("decor-canvas"); + ctx1.drawImage(c4, 0, 0); //Fuse any decor + ctx1.drawImage(c3, 0, 0); //Fuse solution canvas + ctx1.drawImage(c2, 0, 0); //Fuse user canvas + Turtle.drawTurtle(); //Add the turtle +} + +Turtle.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Turtle.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + } + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,penUp,penDown,penWidth,penColour,'); + + Turtle.drawMap(); +}; + +Turtle.reset = function(bool){ + if(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + } + Turtle.updateImage(); +} + +//Workaround current plugin implementation that uses Maze as a variable +Maze.reset = function(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + if(!bool){ + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d"); + var c2 = document.getElementById("user-canvas"); + var ctx2 = c2.getContext("2d"); + ctx1.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + ctx2.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + } + Turtle.updateImage(); +} + +/** +* +* Blocks functions +* +*/ +Turtle.move = function(length){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + if (Turtle.PEN_DOWN) { + ctx.beginPath(); + ctx.moveTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + } + if(length){ + Turtle.CURRENT_COORD.x += length * Math.sin(2 * Math.PI * Turtle.HEADING / 360); + Turtle.CURRENT_COORD.y -= length * Math.cos(2 * Math.PI * Turtle.HEADING / 360); + } + if(Turtle.PEN_DOWN){ + ctx.lineWidth = Turtle.PEN_WIDTH; + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.lineTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + ctx.stroke(); + } + Turtle.animate("move"); +} + +Turtle.moveForward = function(length){ + Turtle.move(length); +} + + +Turtle.moveBackward = function(length){ + Turtle.move(-length); +} + +Turtle.jumpForward = function(length){ + Turtle.penUp(); + Turtle.moveForward(length); + Turtle.penDown(); +} + +Turtle.jumpBackward = function(length){ + Turtle.penUp(); + Turtle.moveBackward(length); + Turtle.penDown(); +} + +Turtle.circle = function(radius){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + ctx.beginPath(); + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, radius, 0,2*Math.PI); + ctx.stroke(); + Turtle.updateImage(); +} + +Turtle.turn = function(angle, direction){ + switch (direction){ + case 0: + Turtle.HEADING = (Turtle.HEADING - angle) % 360; + if (Turtle.HEADING < 0) { + Turtle.HEADING += 360; + } + break; + case 1: + Turtle.HEADING = (Turtle.HEADING + angle) % 360; + break; + } + Turtle.animate("turn"); +} + +Turtle.turnRight = function(angle){ + Turtle.turn(angle, 1); +} + +Turtle.turnLeft = function(angle){ + Turtle.turn(angle, 0); +} + +Turtle.penWidth = function(width){ + Turtle.PEN_WIDTH = width; +} + +Turtle.penUp = function(){ + Turtle.PEN_DOWN = false; +} + +Turtle.penDown = function(){ + Turtle.PEN_DOWN = true; +} + +Turtle.penColour = function(colour){ + Turtle.PEN_COLOUR = colour; + Turtle.animate("colour"); +} + +//Called to draw +if (document.getElementById('visualization') != null) { + window.addEventListener('load', Turtle.init); +} else { + console.warn('Cannot find visualization element.'); +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_4/public/turtle_config.json b/Cours 1 Code.org/Lecon 5/Artist_4/public/turtle_config.json new file mode 100644 index 0000000..812b2c5 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_4/public/turtle_config.json @@ -0,0 +1,14 @@ +{ + "startX":150, + "startY":150, + "startAngle":90, + "strokeWidth":3, + "strokeColour":"#000000", + "colourSpecific":false, + "radius":15, + "animationRate":50, + "width":300, + "height":300, + "imageSolution":true, + "imageName":"solution.png" +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_4/run b/Cours 1 Code.org/Lecon 5/Artist_4/run new file mode 100644 index 0000000..5d51d53 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_4/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("turtle.py") + + p = subprocess.Popen(shlex.split("python3 turtle.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 5/Artist_4/student/turtle.py b/Cours 1 Code.org/Lecon 5/Artist_4/student/turtle.py new file mode 100644 index 0000000..3d97b4b --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_4/student/turtle.py @@ -0,0 +1,125 @@ +import math +import json +import os + +#Reset the turtle variables to starting position +def resetTurtle(): + Turtle["x"] = start_x + Turtle["y"] = start_y + Turtle["heading"] = start_heading + Turtle["colour"] = start_pen_colour + Turtle["width"] = start_pen_width + Turtle["penDown"] = True + + +def student_code(): +@ @code@@ + +#Code of the solution +def solution(): + for count2 in range(3): + turnRight(120) + for count in range(6): + moveForward(50) + turnRight(60) + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +# +# Code to "draw" +# + +# Format stored in the dictionary : +# Point x - Point y (as int) : strokewidth strokeColour + +def moveForward(length): + addPoint() + for i in range(length): + Turtle["x"] += int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] -= int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def moveBackward(length): + addPoint() + for i in range(length): + Turtle["x"] -= int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] += int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def addPoint(): + if(sol): # We are computing the solution + if(count_colours): # Colour is important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: # Colour is not important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + else: # We are computing the student output + if(count_colours): + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + Turtle["heading"] = (Turtle["heading"] + angle) % 360; + +def turnLeft(angle): + Turtle["heading"] = (Turtle["heading"] - angle) % 360; + if (Turtle["heading"] < 0): + Turtle["heading"] += 360 + +def penWidth(width): + Turtle["width"] = width + +def penUp(): + Turtle["penDown"] = False + +def penDown(): + Turtle["penDown"] = True + +def penColour(colour): + Turtle["colour"] = colour + +# Get the json data +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'turtle_config.json') as f: + data = json.load(f) + +# Dictionaries to compare +sol_output = {} +user_output = {} + +# Variables to start +start_x = data["startX"] +start_y = data["startY"] +start_heading = data["startAngle"] +start_pen_width = data["strokeWidth"] +start_pen_colour = data["strokeColour"] + +count_colours = data["colourSpecific"] + +Turtle = {} # Current turtle state is stocked here +resetTurtle() # Set the variable +sol = True # We will execute the solution first. +solution() # Execute the solution +resetTurtle() # Reset the turtle again +sol = False # We are no longer executing the solution +student_code() # Execute the student code + +if(user_output == sol_output): #If the dicts are the same, success + print("True", end='', flush=True) +else: + print("Votre solution est incorrecte, essayez encore", end='', flush=True) \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_4/task.yaml b/Cours 1 Code.org/Lecon 5/Artist_4/task.yaml new file mode 100644 index 0000000..2521685 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_4/task.yaml @@ -0,0 +1,181 @@ +accessible: true +author: Celine Deknop +context: Nous avons renommé la fonction «Dessiner un carré» en «Dessiner un hexagone», + mais elle ne dessine encore que des carrés ! Peux-tu modifier la fonction «Dessine + un hexagone» pour dessiner un hexagone avec des côtés de 50 pixels et dessiner + l'image en utilisant la fonction ? +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 4 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + + turnRight + 90 + + + moveForward + + + 250 + + + + + jumpForward + + + 100 + + + + + + + + ??? + + + + + + + + + type: blockly + options: + scrollbars: true + visual: + position: left + media: /static/common/js/blockly/media/ + oneBasedIndex: true + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: '45' + files: + - turtle.js + - interpreter.js + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + Dessiner un hexagone + Décrire cette fonction… + + + + + 4 + + + + + moveForward + + + 75 + + + + + turnRight + 90 + + + + + + + + + + + 3 + + + + + + + + turnRight + 120 + + + + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + visible: true + name: Instructions avec paramètres + type: 0 + id: '1' + description: '' + '1': + id: '2' + description: '' + type: 0 + visible: true + name: Boucles "répéter X fois" + '2': + type: 0 + description: Utilisation d'une fonction + name: Appel de fonctions + id: '3' + visible: true + '3': + id: '4' + name: Édition de fonctions + visible: true + description: Modifier le corps d'une fonction + type: 0 + '4': + type: 2 + description: Faisant partie du parcours normal + name: Normal + visible: false + id: '' + '5': + description: Faisant partie du parcours facile + name: Facile + type: 2 + visible: false + id: '' + '6': + description: Faisant partie du parcours challenge + type: 2 + name: Challenge + visible: false + id: '' + '7': + description: '' + visible: true + name: Lecon 3 + type: 2 + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 5/Artist_5/public/blocks.js b/Cours 1 Code.org/Lecon 5/Artist_5/public/blocks.js new file mode 100644 index 0000000..f2176a5 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_5/public/blocks.js @@ -0,0 +1,398 @@ +/** + * Blockly Games: Turtle Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Turtle application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Turtle.Blocks = {}; + +/** + * Common HSV hue for all blocks in this category. + */ +Turtle.Blocks.HUE = 160; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Turtle.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['turtle_move'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + this.appendValueInput("VALUE") + .setCheck('Number') + .appendField(new Blockly.FieldDropdown( + [ + ["avancer de","moveForward"], + ["reculer de","moveBackward"]] + ), "DIR"); + this.appendDummyInput() + .appendField("pixels"); + this.setColour(Turtle.Blocks.HUE); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setInputsInline(true); + this.setTooltip("Bouger"); + } +}; + +Blockly.JavaScript['turtle_move'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_move'] = function(block) { + // Generate Python for moving forward or backwards. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_move_internal'] = { + /** + * Block for moving forward or backwards. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['avancer de', 'moveForward'], + ['reculer de', 'moveBackward']]; + var VALUES = + [['20 pixels', '20'], + ['50 pixels', '50'], + ['100 pixels', '100'], + ['150 pixels', '150']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Bouger'); + } +}; + +Blockly.JavaScript['turtle_move_internal'] = function(block) { + // Generate JavaScript for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ');\n'; +}; + +Blockly.Python['turtle_move_internal'] = function(block) { + // Generate Python for moving forward or backwards. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_jump'] = { + init: function() { + this.appendValueInput("VAL") + .setCheck("Number") + .appendField("saute en") + .appendField(new Blockly.FieldDropdown( + [ + ["avant de","jumpForward"], + ["arriere de","jumpBackward"] + ]), "DIR"); + this.appendDummyInput() + .appendField("pixels"); + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip("Bouge sans tracer de ligne"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['turtle_jump'] = function(block) { + var dropdown_dir = block.getFieldValue('DIR'); + var value_val = Blockly.JavaScript.valueToCode(block, 'VAL', Blockly.JavaScript.ORDER_ATOMIC); + var code = dropdown_dir+'('+value_val+');\n'; + return code; +} + +Blockly.Python['turtle_jump'] = function(block) { + var dropdown_dir = block.getFieldValue('DIR'); + var value_val = Blockly.Python.valueToCode(block, 'VAL', Blockly.JavaScript.ORDER_ATOMIC); + var code = dropdown_dir+'('+value_val+')\n'; + return code; +} + +Blockly.Blocks['turtle_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite', 'turnRight'], + ['tourner à gauche', 'turnLeft']]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Turtle.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Turtle.Blocks.LEFT_TURN; + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('VALUE') + .setCheck('Number') + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourner"); + } +}; + +Blockly.JavaScript['turtle_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var value = Blockly.JavaScript.valueToCode(block, 'VALUE', + Blockly.JavaScript.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn'] = function(block) { + // Generate Python for turning left or right. + var value = Blockly.Python.valueToCode(block, 'VALUE', + Blockly.Python.ORDER_NONE) || '0'; + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_turn_internal'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = + [['tourner à droite de', 'turnRight'], + ['tourner à gauche de', 'turnLeft']]; + var VALUES = + [['1\u00B0', '1'], + ['45\u00B0', '45'], + ['60\u00B0', '60'], + ['72\u00B0', '72'], + ['90\u00B0', '90'], + ['120\u00B0', '120'], + ['144\u00B0', '144']]; + this.setColour(Turtle.Blocks.HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR') + .appendField(new Blockly.FieldDropdown(VALUES), 'VALUE'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Tourner de X degrés'); + } +}; + +Blockly.JavaScript['turtle_turn_internal'] = function(block) { + // Generate JavaScript for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ', \'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_turn_internal'] = function(block) { + // Generate Python for turning left or right. + var value = block.getFieldValue('VALUE'); + return block.getFieldValue('DIR') + + '(' + value + ')\n'; +}; + +Blockly.Blocks['turtle_width'] = { + /** + * Block for setting the width. + * @this Blockly.Block + */ + init: function() { + this.setColour(Turtle.Blocks.HUE); + this.appendValueInput('WIDTH') + .setCheck('Number') + .appendField('Définis l\'épaisseur de la ligne'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Définis l\'épaisseur de la ligne'); + } +}; + +Blockly.JavaScript['turtle_width'] = function(block) { + // Generate JavaScript for setting the width. + var width = Blockly.JavaScript.valueToCode(block, 'WIDTH', + Blockly.JavaScript.ORDER_NONE) || '1'; + return 'penWidth(' + width + ');\n'; +}; + +Blockly.Python['turtle_width'] = function(block) { + // Generate Python for setting the width. + var width = Blockly.Python.valueToCode(block, 'WIDTH', + Blockly.Python.ORDER_NONE) || '1'; + return 'penWidth(' + width + ')\n'; +}; + +Blockly.Blocks['turtle_pen'] = { + /** + * Block for pen up/down. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "%1", + "args0": [ + { + "type": "field_dropdown", + "name": "PEN", + "options": [ + ['lever le crayon', "penUp"], + ['poser le crayon', "penDown"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Turtle.Blocks.HUE, + "tooltip": "Lever ou poser le crayon sur la feuille" + }); + } +}; + +Blockly.JavaScript['turtle_pen'] = function(block) { + // Generate JavaScript for pen up/down. + return block.getFieldValue('PEN') + + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['turtle_pen'] = function(block) { + // Generate Python for pen up/down. + return block.getFieldValue('PEN') + + '()\n'; +}; + +Blockly.Blocks['turtle_colour'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendValueInput('COLOUR') + .setCheck('Colour') + .appendField('définis la couleur'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Changer la couleur du trait"); + } +}; + +Blockly.JavaScript['turtle_colour'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = Blockly.JavaScript.valueToCode(block, 'COLOUR', + Blockly.JavaScript.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ');\n'; +}; + +Blockly.Python['turtle_colour'] = function(block) { + // Generate Python for setting the colour. + var colour = Blockly.Python.valueToCode(block, 'COLOUR', + Blockly.Python.ORDER_NONE) || '\'#000000\''; + return 'penColour(' + colour + ')\n'; +}; + +Blockly.Blocks['turtle_colour_internal'] = { + /** + * Block for setting the colour. + * @this Blockly.Block + */ + init: function() { + this.setColour(Blockly.Blocks.colour.HUE); + this.appendDummyInput() + .appendField('définis la couleur') + .appendField(new Blockly.FieldColour('#ff0000'), 'COLOUR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Changer la couleur du trait'); + } +}; + +Blockly.JavaScript['turtle_colour_internal'] = function(block) { + // Generate JavaScript for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ', \'block_id_' + + block.id + '\');\n'; +}; + +Blockly.Python['turtle_colour_internal'] = function(block) { + // Generate Python for setting the colour. + var colour = '\'' + block.getFieldValue('COLOUR') + '\''; + return 'penColour(' + colour + ')\n'; +}; + +Blockly.Blocks['turtle_repeat_internal'] = { + /** + * Block for repeat n times (internal number). + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.CONTROLS_REPEAT_TITLE, + "args0": [ + { + "type": "field_dropdown", + "name": "TIMES", + "options": [ + ["3", "3"], + ["4", "4"], + ["5", "5"], + ["360", "360"] + ] + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": Blockly.Blocks.loops.HUE, + "tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP, + "helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL + }); + this.appendStatementInput('DO') + .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO); + } +}; + +Blockly.JavaScript['turtle_repeat_internal'] = +Blockly.JavaScript['controls_repeat']; + +Blockly.Python['turtle_repeat_internal'] = +Blockly.Python['controls_repeat']; \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_5/public/create_img.py b/Cours 1 Code.org/Lecon 5/Artist_5/public/create_img.py new file mode 100644 index 0000000..d4d946e --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_5/public/create_img.py @@ -0,0 +1,117 @@ +import Image, ImageDraw +import math +import random +import json +import os + +def moveForward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x + length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y - length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + +def moveBackward(length): + global current_x, current_y, current_heading, colour, current_width + next_x = current_x - length * math.sin(2 * math.pi * current_heading / 360); + next_y = current_y + length * math.cos(2 * math.pi * current_heading / 360); + if(down): + draw.line([current_x, current_y, next_x, next_y], colour, width=current_width) + current_x = next_x + current_y = next_y + + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + global current_heading + current_heading = (current_heading + angle) % 360; + +def turnLeft(angle): + global current_heading + current_heading = (current_heading - angle) % 360; + if (current_heading < 0): + current_heading += 360 + +def penWidth(width): + global current_width + current_width = width + +def penUp(): + global down + down = False + +def penDown(): + global down + down = True + +def penColour(c): + global colour + colour = c + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +def penWidth(width): + global current_width + current_width = width + +def Dessiner_une_fleur(): + penColour('#228b22') + turnLeft(90) + moveForward(100) + for count in range(10): + penColour(randomColour()) + turnLeft(36) + moveForward(25) + turnLeft(36) + moveForward(25) + turnLeft(144) + moveForward(25) + turnLeft(36) + moveForward(25) + jumpBackward(100) + turnRight(90) + + +#Main + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path + '/turtle_config.json') as f: + data = json.load(f) + +width = data["width"] +height = data["height"] +current_x = data["startX"] +current_y = data["startY"] +current_heading = data["startAngle"] +current_width = data["strokeWidth"] +down = True +colour = data["strokeColour"] + + +# PIL create an empty image and draw object to draw on +# memory only, not visible +image1 = Image.new("RGBA", (width, height), (0,0,0,0)) +draw = ImageDraw.Draw(image1) + +# do the PIL image/draw (in memory) drawings +for count2 in range(3): + Dessiner_une_fleur() + jumpForward(100) + +# PIL image can be saved as .png .jpg .gif or .bmp file (among others) +filename = "solution.png" +image1.save(filename) diff --git a/Cours 1 Code.org/Lecon 5/Artist_5/public/interpreter.js b/Cours 1 Code.org/Lecon 5/Artist_5/public/interpreter.js new file mode 100644 index 0000000..f4cf3c3 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_5/public/interpreter.js @@ -0,0 +1,63 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(length) { + Turtle.move(length); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.move(-length); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.jumpForward(length); + }; + interpreter.setProperty(scope, 'jumpForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(length) { + Turtle.jumpBackward(length); + }; + interpreter.setProperty(scope, 'jumpBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 0); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(angle) { + Turtle.turn(angle, 1); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(width) { + Turtle.penWidth(width); + }; + interpreter.setProperty(scope, 'penWidth', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penUp(); + }; + interpreter.setProperty(scope, 'penUp', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + Turtle.penDown(); + }; + interpreter.setProperty(scope, 'penDown', + interpreter.createNativeFunction(wrapper)); + wrapper = function(colour) { + Turtle.penColour(colour); + }; + interpreter.setProperty(scope, 'penColour', + interpreter.createNativeFunction(wrapper)); + + + + + Turtle.log = []; + Turtle.reset(false); +}; + +var animate = function() { + Turtle.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 5/Artist_5/public/solution.png b/Cours 1 Code.org/Lecon 5/Artist_5/public/solution.png new file mode 100644 index 0000000..4d6d6f1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 5/Artist_5/public/solution.png differ diff --git a/Cours 1 Code.org/Lecon 5/Artist_5/public/turtle.js b/Cours 1 Code.org/Lecon 5/Artist_5/public/turtle.js new file mode 100644 index 0000000..e53df3a --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_5/public/turtle.js @@ -0,0 +1,416 @@ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Turtle = {}; +window.Maze = {}; + +//Get the json file and it's information +var turtle_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + turtle_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"turtle_config.json" +}else { //When displaying the task + turtle_file = task_directory_path + "turtle_config.json"; +} +var request = new XMLHttpRequest(); +request.open("GET", turtle_file, false); +request.send(null) +var json = JSON.parse(request.responseText); + +var imagePath = "" +if(json.imageSolution) + imagePath = task_directory_path+json.imageName; + +//Code of the solution +var solution = function(){ + //Here, put the javascript corresponding to the solved exercice (or use the image) +} +//Code of the decor +var decoration = function(){ + //Here, put the code for any decor, not part of the exercice + Turtle.turnLeft(90); + Turtle.jumpForward(100); + Turtle.turnRight(90); + Turtle.jumpForward(65); + drawFlower(); + Turtle.jumpForward(100); + drawFlower(); +} + +var drawFlower = function() { + Turtle.penColour('#228b22'); + Turtle.turnLeft(90); + Turtle.moveForward(100); + for (var count2 = 0; count2 < 10; count2++) { + Turtle.penColour(randomColour()); + Turtle.turnLeft(36); + Turtle.moveForward(25); + Turtle.turnLeft(36); + Turtle.moveForward(25); + Turtle.turnLeft(144); + Turtle.moveForward(25); + Turtle.turnLeft(36); + Turtle.moveForward(25); + Turtle.turnLeft(144); + } + Turtle.jumpBackward(100); + Turtle.turnRight(90); +} + +var randomColour = function(){ + var colour = Math.floor(Math.random()*16777215); + return "#"+colour.toString(16).toUpperCase() +} + +//Canvas size +Turtle.CANVAS_WIDTH = json.width; +Turtle.CANVAS_HEIGHT = json.height; + +//Starting position and radius of turtle +Turtle.START_X = json.startX; +Turtle.START_Y = json.startY; +Turtle.RADIUS = json.radius; + +//Current coordinates of turtle +Turtle.CURRENT_COORD = { + x:Turtle.START_X, + y:Turtle.START_Y +} + +//Current heading of turtle +Turtle.HEADING = json.startAngle; + +//Weater or not the pen is down and it's width +Turtle.PEN_DOWN = true; //At start, the pen is always down +Turtle.PEN_WIDTH = json.strokeWidth; +Turtle.PEN_COLOUR = json.strokeColour; + +//Variables used to draw on the solution canvas or the decor canvas +var sol = false; +var decor = false; + +//milisec between each frame +window.stepSpeed = json.animationRate; + +/** +* +* Animations functions +* +*/ + +Turtle.drawMap = function() { + var div = document.getElementById('visualization'); + + // Add the canvas for the turtle + var canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute("style", "border:1px solid #F1EEE7;") + canvas.setAttribute("id", "turtle-canvas"); + div.appendChild(canvas); + // Add the canvas for the user. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "user-canvas"); + div.appendChild(canvas); + // Add the canvas for the solution. + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none'); + canvas.setAttribute("id", "solution-canvas"); + div.appendChild(canvas); + //Add the canvas for the decor + canvas = document.createElement('canvas'); + canvas.setAttribute('width', Turtle.CANVAS_WIDTH); + canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); + canvas.setAttribute('style', 'display: none') + canvas.setAttribute("id", "decor-canvas"); + div.appendChild(canvas) + + //Draw the decor + Turtle.drawDecor(); + + if(json.imageSolution) //We have an image + Turtle.addSolution(); + else //Draw the solution using the code + Turtle.drawSolution(); + + //Draw the turtle + Turtle.updateImage(); + + +} + +Turtle.drawTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + + //Draw the turtle body + ctx.beginPath(); + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, Turtle.RADIUS, 0, 2 * Math.PI); + ctx.stroke(); + + // Draw the turtle head. + var WIDTH = 0.3; + var HEAD_TIP = 10; + var ARROW_TIP = 4; + var BEND = 6; + var radians = 2 * Math.PI * Turtle.HEADING / 360; + var tipX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + HEAD_TIP) * Math.sin(radians); + var tipY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + HEAD_TIP) * Math.cos(radians); + radians -= WIDTH; + var leftX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var leftY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + radians += WIDTH / 2; + var leftControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var leftControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH; + var rightControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); + var rightControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); + radians += WIDTH / 2; + var rightX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); + var rightY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); + + ctx.beginPath(); + ctx.moveTo(tipX, tipY); + ctx.lineTo(leftX, leftY); + ctx.bezierCurveTo(leftControlX, leftControlY, + rightControlX, rightControlY, rightX, rightY); + ctx.closePath(); + ctx.fillStyle = Turtle.PEN_COLOUR; + ctx.fill(); +} + +Turtle.resetTurtle = function(){ + var c = document.getElementById("turtle-canvas"); + var ctx = c.getContext("2d") + //Clear any previous turtle + ctx.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + +} + +Turtle.addSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + var img = new Image(); // Crée un nouvel élément Image + img.src = imagePath; + img.onload = function(){ + ctx.drawImage(img, 0, 0); + Turtle.updateImage(); + } +} + +Turtle.drawSolution = function(){ + var c = document.getElementById("solution-canvas"); + var ctx = c.getContext("2d") + ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent + sol = true; + solution(); + sol = false; + Turtle.reset(true); +} + +Turtle.drawDecor = function(){ + var c = document.getElementById("decor-canvas"); + var ctx = c.getContext("2d") + decor = true; + decoration(); + decor = false; + Turtle.reset(true); +} + +Turtle.animate = function(id) { + switch(id){ + case "move" : + Turtle.updateImage(); + break; + case "turn" : + Turtle.updateImage(); + break; + case "colour": + Turtle.updateImage(); + break; + default: + //This should not happen + console.warn("Unknown animation"); + break; + } +} + +Turtle.updateImage = function(){ + Turtle.resetTurtle(); + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d") + var c2 = document.getElementById("user-canvas"); + var c3 = document.getElementById("solution-canvas"); + var c4 = document.getElementById("decor-canvas"); + ctx1.drawImage(c4, 0, 0); //Fuse any decor + ctx1.drawImage(c3, 0, 0); //Fuse solution canvas + ctx1.drawImage(c2, 0, 0); //Fuse user canvas + Turtle.drawTurtle(); //Add the turtle +} + +Turtle.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Turtle.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + } + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,penUp,penDown,penWidth,penColour,'); + + Turtle.drawMap(); +}; + +Turtle.reset = function(bool){ + if(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + } + Turtle.updateImage(); +} + +//Workaround current plugin implementation that uses Maze as a variable +Maze.reset = function(bool){ + Turtle.CURRENT_COORD.x = Turtle.START_X; + Turtle.CURRENT_COORD.y = Turtle.START_Y; + + Turtle.PEN_DOWN = true; + Turtle.PEN_WIDTH = json.strokeWidth; + Turtle.PEN_COLOUR = json.strokeColour; + + Turtle.HEADING = json.startAngle; + if(!bool){ + var c1 = document.getElementById("turtle-canvas"); + var ctx1 = c1.getContext("2d"); + var c2 = document.getElementById("user-canvas"); + var ctx2 = c2.getContext("2d"); + ctx1.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + ctx2.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); + } + Turtle.updateImage(); +} + +/** +* +* Blocks functions +* +*/ +Turtle.move = function(length){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + if (Turtle.PEN_DOWN) { + ctx.beginPath(); + ctx.moveTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + } + if(length){ + Turtle.CURRENT_COORD.x += length * Math.sin(2 * Math.PI * Turtle.HEADING / 360); + Turtle.CURRENT_COORD.y -= length * Math.cos(2 * Math.PI * Turtle.HEADING / 360); + } + if(Turtle.PEN_DOWN){ + ctx.lineWidth = Turtle.PEN_WIDTH; + ctx.strokeStyle = Turtle.PEN_COLOUR; + ctx.lineTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); + ctx.stroke(); + } + Turtle.animate("move"); +} + +Turtle.moveForward = function(length){ + Turtle.move(length); +} + + +Turtle.moveBackward = function(length){ + Turtle.move(-length); +} + +Turtle.jumpForward = function(length){ + Turtle.penUp(); + Turtle.moveForward(length); + Turtle.penDown(); +} + +Turtle.jumpBackward = function(length){ + Turtle.penUp(); + Turtle.moveBackward(length); + Turtle.penDown(); +} + +Turtle.circle = function(radius){ + var c; + if(sol) + c = document.getElementById("solution-canvas"); + else if (decor) + c = document.getElementById("decor-canvas"); + else + c = document.getElementById("user-canvas"); + var ctx = c.getContext("2d") + ctx.beginPath(); + ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, radius, 0,2*Math.PI); + ctx.stroke(); + Turtle.updateImage(); +} + +Turtle.turn = function(angle, direction){ + switch (direction){ + case 0: + Turtle.HEADING = (Turtle.HEADING - angle) % 360; + if (Turtle.HEADING < 0) { + Turtle.HEADING += 360; + } + break; + case 1: + Turtle.HEADING = (Turtle.HEADING + angle) % 360; + break; + } + Turtle.animate("turn"); +} + +Turtle.turnRight = function(angle){ + Turtle.turn(angle, 1); +} + +Turtle.turnLeft = function(angle){ + Turtle.turn(angle, 0); +} + +Turtle.penWidth = function(width){ + Turtle.PEN_WIDTH = width; +} + +Turtle.penUp = function(){ + Turtle.PEN_DOWN = false; +} + +Turtle.penDown = function(){ + Turtle.PEN_DOWN = true; +} + +Turtle.penColour = function(colour){ + Turtle.PEN_COLOUR = colour; + Turtle.animate("colour"); +} + +//Called to draw +if (document.getElementById('visualization') != null) { + window.addEventListener('load', Turtle.init); +} else { + console.warn('Cannot find visualization element.'); +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_5/public/turtle_config.json b/Cours 1 Code.org/Lecon 5/Artist_5/public/turtle_config.json new file mode 100644 index 0000000..fae4b2c --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_5/public/turtle_config.json @@ -0,0 +1,14 @@ +{ + "startX":50, + "startY":275, + "startAngle":90, + "strokeWidth":3, + "strokeColour":"#000000", + "colourSpecific":false, + "radius":15, + "animationRate":50, + "width":300, + "height":300, + "imageSolution":true, + "imageName":"solution.png" +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_5/run b/Cours 1 Code.org/Lecon 5/Artist_5/run new file mode 100644 index 0000000..5d51d53 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_5/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("turtle.py") + + p = subprocess.Popen(shlex.split("python3 turtle.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 5/Artist_5/student/turtle.py b/Cours 1 Code.org/Lecon 5/Artist_5/student/turtle.py new file mode 100644 index 0000000..b01d91c --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_5/student/turtle.py @@ -0,0 +1,141 @@ +import math +import json +import os +import random + +#Reset the turtle variables to starting position +def resetTurtle(): + Turtle["x"] = start_x + Turtle["y"] = start_y + Turtle["heading"] = start_heading + Turtle["colour"] = start_pen_colour + Turtle["width"] = start_pen_width + Turtle["penDown"] = True + + +def student_code(): +@ @code@@ + +#Code of the solution +def solution(): + for count2 in range(3): + Dessiner_une_fleur() + jumpForward(100) + +def Dessiner_une_fleur(): + penColour('#228b22') + turnLeft(90) + moveForward(100) + for count in range(10): + penColour(randomColour()) + turnLeft(36) + moveForward(25) + turnLeft(36) + moveForward(25) + turnLeft(144) + moveForward(25) + turnLeft(36) + moveForward(25) + jumpBackward(100) + turnRight(90) + +def randomColour(): + colour = "%06x" % random.randint(0, 0xFFFFFF) + return "#" + colour + +# +# Code to "draw" +# + +# Format stored in the dictionary : +# Point x - Point y (as int) : strokewidth strokeColour + +def moveForward(length): + addPoint() + for i in range(length): + Turtle["x"] += int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] -= int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def moveBackward(length): + addPoint() + for i in range(length): + Turtle["x"] -= int(1 * math.sin(2 * math.pi * Turtle["heading"] / 360)); + Turtle["y"] += int(1 * math.cos(2 * math.pi * Turtle["heading"] / 360)); + if(Turtle["penDown"]): + addPoint() + +def addPoint(): + if(sol): # We are computing the solution + if(count_colours): # Colour is important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: # Colour is not important + sol_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + else: # We are computing the student output + if(count_colours): + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"])+" "+str(Turtle["colour"]) + else: + user_output[str(Turtle["x"])+"-"+str(Turtle["y"])] = str(Turtle["width"]) + +def jumpForward(length): + penUp() + moveForward(length) + penDown() + +def jumpBackward(length): + penUp() + moveBackward(length) + penDown() + +def turnRight(angle): + Turtle["heading"] = (Turtle["heading"] + angle) % 360; + +def turnLeft(angle): + Turtle["heading"] = (Turtle["heading"] - angle) % 360; + if (Turtle["heading"] < 0): + Turtle["heading"] += 360 + +def penWidth(width): + Turtle["width"] = width + +def penUp(): + Turtle["penDown"] = False + +def penDown(): + Turtle["penDown"] = True + +def penColour(colour): + Turtle["colour"] = colour + +# Get the json data +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'turtle_config.json') as f: + data = json.load(f) + +# Dictionaries to compare +sol_output = {} +user_output = {} + +# Variables to start +start_x = data["startX"] +start_y = data["startY"] +start_heading = data["startAngle"] +start_pen_width = data["strokeWidth"] +start_pen_colour = data["strokeColour"] + +count_colours = data["colourSpecific"] + +Turtle = {} # Current turtle state is stocked here +resetTurtle() # Set the variable +sol = True # We will execute the solution first. +solution() # Execute the solution +resetTurtle() # Reset the turtle again +sol = False # We are no longer executing the solution +student_code() # Execute the student code + +if(user_output == sol_output): #If the dicts are the same, success + print("True", end='', flush=True) +else: + print("Votre solution est incorrecte, essayez encore", end='', flush=True) \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 5/Artist_5/task.yaml b/Cours 1 Code.org/Lecon 5/Artist_5/task.yaml new file mode 100644 index 0000000..9ec3433 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/Artist_5/task.yaml @@ -0,0 +1,271 @@ +accessible: true +author: Celine Deknop +context: 'Les fonctions sont vraiment très biens pour dessiner des choses complexes + plusieurs fois. Peux-tu utiliser la fonction «Dessiner une fleur» pour remplir + le reste du parterre ? (Indice : les plantes sont séparées les unes des autres + d’une longueur de 100 pixels) ' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 5 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + + turnRight + 90 + + + moveForward + + + 250 + + + + + jumpForward + + + 100 + + + + + + + + + + #228b22 + + + + + + + + + + ??? + + + + + + + + + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + css: true + trashcan: true + toolboxPosition: start + sounds: true + maxBlocks: '45' + files: + - turtle.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + Dessiner une fleur + Décrire cette fonction… + + + #228b22 + + + turnLeft + 90 + + + moveForward + + + 100 + + + + + + + 10 + + + + + + + + + + turnLeft + + + 36 + + + + + moveForward + + + 25 + + + + + turnLeft + + + 36 + + + + + moveForward + + + 25 + + + + + turnLeft + + + 144 + + + + + moveForward + + + 25 + + + + + turnLeft + + + 36 + + + + + moveForward + + + 25 + + + + + + + + + + + + + + + + + + + + + + + jumpBackward + + + 100 + + + + + turnRight + 90 + + + + + + + + + + + + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + visible: true + name: Instructions avec paramètres + description: '' + id: '1' + '1': + id: '2' + description: '' + type: 0 + visible: true + name: Boucles "répéter X fois" + '2': + description: Utilisation d'une fonction + name: Appel de fonctions + id: '3' + visible: true + type: 0 + '3': + name: Normal + description: Faisant partie du parcours normal + type: 2 + visible: false + id: '' + '4': + type: 2 + name: Facile + description: Faisant partie du parcours facile + visible: false + id: '' + '5': + visible: true + description: '' + name: Lecon 3 + type: 2 + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 5/course.yaml b/Cours 1 Code.org/Lecon 5/course.yaml new file mode 100644 index 0000000..a190bc2 --- /dev/null +++ b/Cours 1 Code.org/Lecon 5/course.yaml @@ -0,0 +1,18 @@ +accessible: true +name: Cours 3 - Drawing +description: '' +admins: +- '' +tutors: [] +groups_student_choice: false +use_classrooms: true +allow_unregister: true +allow_preview: false +registration: true +registration_password: null +registration_ac: null +registration_ac_list: +- '' +is_lti: false +lti_keys: {} +lti_send_back_grade: false diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/blocks.js b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/blocks.js new file mode 100644 index 0000000..ac85a5b --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/blocks.js @@ -0,0 +1,333 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/interpreter.js b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/interpreter.js new file mode 100644 index 0000000..1aaf6d7 --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/interpreter.js @@ -0,0 +1,71 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1/Lecon1/03_maze/public/maze.js b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze.js similarity index 72% rename from Cours 1/Lecon1/03_maze/public/maze.js rename to Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze.js index 099132f..6799e4e 100644 --- a/Cours 1/Lecon1/03_maze/public/maze.js +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze.js @@ -1,89 +1,95 @@ -/** - * Blockly Games: Maze - * - * Copyright 2012 Google Inc. - * https://github.com/google/blockly-games - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview JavaScript for Blockly's Maze application. - * @author fraser@google.com (Neil Fraser) - */ -"use strict"; - var task_directory_path = window.location.pathname + "/"; - +var res_path = task_directory_path+ "maze/"; window.Maze = {}; -Maze.MAX_BLOCKS = Infinity; +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); -// Crash type constants. Maze.CRASH_STOP = 1; -Maze.CRASH_SPIN = 2; -Maze.CRASH_FALL = 3; Maze.SKIN = { - sprite: task_directory_path + 'maze/avatar.png', - tiles: task_directory_path + 'maze/tiles.png', - marker: task_directory_path + 'maze/goalIdle.gif', - goalAnimation: task_directory_path + 'maze/goal.gif', - obstacleIdle: task_directory_path + 'maze/obstacleIdle.gif', - obstacleAnimation: task_directory_path + 'maze/obstacle.gif', - obstacleScale: 1.4, - background: task_directory_path + 'maze/background.png', - graph: false, + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + + //Unused look: '#000', - obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], - winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], - crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + movePegmanAnimation: res_path + 'move_avatar.png', + movePegmanAnimationFrameNumber: 9, + movePegmanAnimationSpeedScale: 1.5, + nectarSound: [res_path + 'getNectar.mp3', res_path + 'getNectar.ogg'], + nonDisappearingPegmanHittingObstacle: true, + turnAfterVictory: false, + wall0Sound: [res_path + 'wall0.mp3', res_path + 'wall0.ogg'], + wall1Sound: [res_path + 'wall1.mp3', res_path + 'wall1.ogg'], + wall2Sound: [res_path + 'wall2.mp3', res_path + 'wall2.ogg'], + wall3Sound: [res_path + 'wall3.mp3', res_path + 'wall3.ogg'], + wall4Sound: [res_path + 'wall4.mp3', res_path + 'wall4.ogg'], + wallPegmanAnimation: res_path + 'wall_avatar.png', + wallSound: [res_path + 'wall.mp3', res_path + 'wall.ogg'], + beeSound: true, + danceOnLoad: false, + hittingWallAnimation: res_path + 'wall.gif', + honeySound: [res_path + 'makeHoney.mp3', res_path + 'makeHoney.ogg'], + avatarIdle: res_path + 'idle_avatar.gif', + crashType: Maze.CRASH_STOP }; /** * Milliseconds between each animation frame. */ -window.stepSpeed = 250; +window.stepSpeed = json.map.animationSpeed; /** * The types of squares in the maze, which is represented * as a 2D array of SquareType values. * @enum {number} */ -Maze.SquareType = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4, - STARTANDFINISH: 5 -}; - -// The maze square constants defined above are inlined here -// for ease of reading and writing the static mazes. -Maze.map = - // Level 3. - [ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 2, 0, 0], - [0, 4, 1, 1, 1, 1, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0], - [0, 0, 3, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0] - ]; +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout[0]; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + cell.remainingValue = cell.value; + } + } +} /** * Measure maze dimensions and set sizes. @@ -93,9 +99,10 @@ Maze.map = */ Maze.ROWS = Maze.map.length; Maze.COLS = Maze.map[0].length; -Maze.SQUARE_SIZE = 50; -Maze.PEGMAN_HEIGHT = 52; -Maze.PEGMAN_WIDTH = 49; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; @@ -132,7 +139,7 @@ Maze.result = Maze.ResultType.UNSET; /** * Starting direction. */ -Maze.startDirection = Maze.DirectionType.SOUTH; +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; /** * PIDs of animation tasks currently executing. @@ -184,7 +191,7 @@ Maze.drawMap = function() { square.setAttribute('stroke', '#CCB'); svg.appendChild(square); - if (Maze.SKIN.background) { + if (Maze.SKIN.background) { //Use an image as background var tile = document.createElementNS(Blockly.SVG_NS, 'image'); tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.background); @@ -195,34 +202,7 @@ Maze.drawMap = function() { svg.appendChild(tile); } - if (Maze.SKIN.graph) { - // Draw the grid lines. - // The grid lines are offset so that the lines pass through the centre of - // each square. A half-pixel offset is also added to as standard SVG - // practice to avoid blurriness. - var offset = Maze.SQUARE_SIZE / 2 + 0.5; - for (var k = 0; k < Maze.ROWS; k++) { - var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); - h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('x2', Maze.MAZE_WIDTH); - h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('stroke', Maze.SKIN.graph); - h_line.setAttribute('stroke-width', 1); - svg.appendChild(h_line); - } - for (var k = 0; k < Maze.COLS; k++) { - var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); - v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('y2', Maze.MAZE_HEIGHT); - v_line.setAttribute('stroke', Maze.SKIN.graph); - v_line.setAttribute('stroke-width', 1); - svg.appendChild(v_line); - } - } - // Draw the tiles making up the maze map. - // Return a value of '0' if the specified square is wall or out of bounds, // '1' otherwise (empty, start, finish). var normalize = function(x, y) { @@ -282,15 +262,6 @@ Maze.drawMap = function() { } } - // Add finish marker. - var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); - finishMarker.setAttribute('id', 'finish'); - finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.marker); - finishMarker.setAttribute('height', 43); - finishMarker.setAttribute('width', 50); - svg.appendChild(finishMarker); - // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); pegmanClip.setAttribute('id', 'pegmanClipPath'); @@ -324,6 +295,28 @@ Maze.drawMap = function() { } } + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + // Add Pegman. var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); pegmanIcon.setAttribute('id', 'pegman'); @@ -346,13 +339,9 @@ Maze.init = function() { return; } - // - // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); - // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); - - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.winSound, 'win'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.crashSound, 'fail'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.obstacleSound, 'obstacle'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); // Not really needed, there are no user-defined functions or variables. Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); @@ -378,8 +367,6 @@ Maze.init = function() { Maze.reset(true); - // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); - // Switch to zero-based indexing so that later JS levels match the blocks. Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); @@ -414,14 +401,6 @@ Maze.reset = function(first) { Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); } - // Move the finish icon into position. - var finishIcon = document.getElementById('finish'); - finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - - finishIcon.getAttribute('width') / 2); - finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - - finishIcon.getAttribute('height')); - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); - // Reset pegman's visibility. var pegmanIcon = document.getElementById('pegman'); pegmanIcon.setAttribute('opacity', 1); @@ -439,8 +418,56 @@ Maze.reset = function(first) { ++obsId; } } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move }; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var val = Math.round(Math.random()); //Pick 1 or 0 at random + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} /** @@ -454,7 +481,10 @@ Maze.animate = function() { // } return; } - + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } switch (action[0]) { case 'north': Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); @@ -501,10 +531,12 @@ Maze.animate = function() { case 'finish': Maze.scheduleFinish(true); break; - // TODO maybe add this - // case 'plant': - // Maze.animatePlant(); - // break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; } }; @@ -535,16 +567,6 @@ Maze.schedule = function(startPos, endPos) { Maze.displayPegman(endPos[0], endPos[1], Maze.constrainDirection16(endPos[2])); }, window.stepSpeed * 3)); - - if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { - Maze.pidList.push(setTimeout(function() { - var finishIcon = document.getElementById('finish'); - if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); - Blockly.getMainWorkspace().playAudio('win', 0.3); - } - }, window.stepSpeed * 4)); - } }; /** @@ -573,15 +595,14 @@ Maze.scheduleFail = function(forward) { deltaY = -deltaY; } - var targetX = Maze.pegmanX + deltaX; + var targetX = Maze.pegmanX + deltaX + 1; var targetY = Maze.pegmanY + deltaY; var squareType = Maze.map[targetY][targetX]; - console.log("pegmanD=", Maze.pegmanD, "forward=", forward, "targetX=", targetX, " targetY=", targetY ,"squareType=", squareType); - if (squareType == Maze.SquareType.OBSTACLE) { - BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); // Play the sound - Blockly.getMainWorkspace().playAudio('obstacle'); + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); // Play the animation var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); @@ -604,10 +625,11 @@ Maze.scheduleFail = function(forward) { }, window.stepSpeed * 2)); Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('failure'); + Blockly.getMainWorkspace().getAudioManager().play('failure'); }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { - BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); // Bounce bounce. deltaX /= 4; deltaY /= 4; @@ -615,7 +637,7 @@ Maze.scheduleFail = function(forward) { Maze.displayPegman(Maze.pegmanX + deltaX, Maze.pegmanY + deltaY, direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); Maze.pidList.push(setTimeout(function() { Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, @@ -625,11 +647,12 @@ Maze.scheduleFail = function(forward) { Maze.displayPegman(Maze.pegmanX + deltaX, Maze.pegmanY + deltaY, direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); }, window.stepSpeed * 2)); Maze.pidList.push(setTimeout(function() { Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); }, window.stepSpeed * 3)); + } else { // Add a small random delta away from the grid. var deltaZ = (Math.random() - 0.5) * 10; @@ -643,7 +666,7 @@ Maze.scheduleFail = function(forward) { acceleration = 0.01; } Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('fail', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); }, window.stepSpeed * 2)); var setPosition = function(n) { return function() { @@ -672,7 +695,7 @@ Maze.scheduleFinish = function(sound) { var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); if (sound) { - Blockly.getMainWorkspace().playAudio('win', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); } window.stepSpeed = 250; // Slow down victory animation a bit. Maze.pidList.push(setTimeout(function() { @@ -803,7 +826,6 @@ Maze.constrainDirection16 = function(d) { Maze.move = function(direction, id) { var isNotAPath = !Maze.isPath(direction, null); if (isNotAPath) { - console.log("not a path"); Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); Maze.result = Maze.ResultType.ERROR; return; @@ -901,6 +923,91 @@ Maze.notDone = function() { return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; }; +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + if (document.getElementById('blocklySvgZone') != null) { window.addEventListener('load', Maze.init); } else { diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/background.png b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/cloud.png b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/honey.png b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/start.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/tiles.png b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/tree.png b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/wall.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/wall.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/win.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze_config.json b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze_config.json new file mode 100644 index 0000000..f43abfe --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_01/public/maze_config.json @@ -0,0 +1,55 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0], + [0, 0, 2, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "specialCells":{ + "honey":[], + "redFlower":[ + { + "x":3, + "y":4, + "value":3 + }, + { + "x":4, + "y":3, + "value":3 + } + + ], + "purpleFlower":[], + "cloud":[] + }, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3, + "STARTANDFINISH": 4 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/run b/Cours 1 Code.org/Lecon 6/Maze_bee_01/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_01/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/student/maze.tpl.py b/Cours 1 Code.org/Lecon 6/Maze_bee_01/student/maze.tpl.py new file mode 100644 index 0000000..95f3a0f --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_01/student/maze.tpl.py @@ -0,0 +1,267 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"][0] + +MAP_CELLS = data["map"]["specialCells"] +for flowers in MAP_CELLS["purpleFlower"]: # Add the random value to the purple flowers + val = random.randrange(0, 2) + flowers["value"] = val + flowers["remainingValue"] = val +for flowers in MAP_CELLS["redFlower"]: + flowers["remainingValue"] = flowers["value"] +for honey in MAP_CELLS["honey"]: + honey["remainingValue"] = honey["value"] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + pass + print("True", end='', flush=True) + else: + pass + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_01/task.yaml b/Cours 1 Code.org/Lecon 6/Maze_bee_01/task.yaml new file mode 100644 index 0000000..1795fc9 --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_01/task.yaml @@ -0,0 +1,113 @@ +accessible: true +author: Florian Thuin +context: Récolte tout le nectar de chaque fleur. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 1 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + spacing: 20 + snap: true + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + css: true + toolboxPosition: start + trashcan: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + header: |4+ + +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + visible: true + name: Boucles répéter X fois + id: '1' + type: 0 + description: '' + '1': + description: Fait partie de la leçon 6 + type: 2 + visible: true + name: Lecon 6 + id: '' + '2': + description: Fait partie du parcours facile + name: Facile + type: 2 + visible: false + id: '' + '3': + description: Fait partie du parcours normal + name: Normal + type: 2 + visible: false + id: '' + '4': + type: 2 + description: Fait partie du parcours challenge + name: Challenge + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/blocks.js b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/blocks.js new file mode 100644 index 0000000..ac85a5b --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/blocks.js @@ -0,0 +1,333 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/interpreter.js b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/interpreter.js new file mode 100644 index 0000000..1aaf6d7 --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/interpreter.js @@ -0,0 +1,71 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze.js b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze.js new file mode 100644 index 0000000..6799e4e --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze.js @@ -0,0 +1,1015 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + + //Unused + look: '#000', + movePegmanAnimation: res_path + 'move_avatar.png', + movePegmanAnimationFrameNumber: 9, + movePegmanAnimationSpeedScale: 1.5, + nectarSound: [res_path + 'getNectar.mp3', res_path + 'getNectar.ogg'], + nonDisappearingPegmanHittingObstacle: true, + turnAfterVictory: false, + wall0Sound: [res_path + 'wall0.mp3', res_path + 'wall0.ogg'], + wall1Sound: [res_path + 'wall1.mp3', res_path + 'wall1.ogg'], + wall2Sound: [res_path + 'wall2.mp3', res_path + 'wall2.ogg'], + wall3Sound: [res_path + 'wall3.mp3', res_path + 'wall3.ogg'], + wall4Sound: [res_path + 'wall4.mp3', res_path + 'wall4.ogg'], + wallPegmanAnimation: res_path + 'wall_avatar.png', + wallSound: [res_path + 'wall.mp3', res_path + 'wall.ogg'], + beeSound: true, + danceOnLoad: false, + hittingWallAnimation: res_path + 'wall.gif', + honeySound: [res_path + 'makeHoney.mp3', res_path + 'makeHoney.ogg'], + avatarIdle: res_path + 'idle_avatar.gif', + + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout[0]; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var val = Math.round(Math.random()); //Pick 1 or 0 at random + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/background.png b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/cloud.png b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/honey.png b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/start.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/tiles.png b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/tree.png b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/wall.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/wall.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/win.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze_config.json b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze_config.json new file mode 100644 index 0000000..986ccc5 --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_02/public/maze_config.json @@ -0,0 +1,60 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 2, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "specialCells":{ + "honey":[], + "redFlower":[ + { + "x":4, + "y":2, + "value":5 + }, + { + "x":4, + "y":3, + "value":5 + }, + { + "x":4, + "y":4, + "value":5 + } + + ], + "purpleFlower":[], + "cloud":[] + }, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3, + "STARTANDFINISH": 4 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/run b/Cours 1 Code.org/Lecon 6/Maze_bee_02/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_02/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/student/maze.tpl.py b/Cours 1 Code.org/Lecon 6/Maze_bee_02/student/maze.tpl.py new file mode 100644 index 0000000..95f3a0f --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_02/student/maze.tpl.py @@ -0,0 +1,267 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"][0] + +MAP_CELLS = data["map"]["specialCells"] +for flowers in MAP_CELLS["purpleFlower"]: # Add the random value to the purple flowers + val = random.randrange(0, 2) + flowers["value"] = val + flowers["remainingValue"] = val +for flowers in MAP_CELLS["redFlower"]: + flowers["remainingValue"] = flowers["value"] +for honey in MAP_CELLS["honey"]: + honey["remainingValue"] = honey["value"] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + pass + print("True", end='', flush=True) + else: + pass + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_02/task.yaml b/Cours 1 Code.org/Lecon 6/Maze_bee_02/task.yaml new file mode 100644 index 0000000..5be4fc8 --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_02/task.yaml @@ -0,0 +1,143 @@ +accessible: true +author: Florian Thuin +context: Les fonctions sont des blocs de code qui effectuent une tâche. Utilise la + fonction « Obtenir 5 » pour obtenir 5 nectars à la fois. Veille également à utiliser + une boucle pour obtenir un compte de bloc idéal. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 2 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + + + + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + snap: true + spacing: 20 + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + trashcan: true + css: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + Obtenir 5 + Récupère 5 nectar sur une fleur + + + + + 5 + + + + + + + + + + header: |4+ + +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + description: '' + type: 0 + visible: true + name: Boucles répéter X fois + id: '1' + '1': + id: '2' + description: '' + type: 0 + visible: true + name: Utilisation de fonction + '2': + type: 2 + description: Fait partie de la leçon 6 + name: Lecon 6 + visible: true + id: '' + '3': + description: Fait partie du parcours facile + type: 2 + name: Facile + visible: false + id: '' + '4': + type: 2 + description: Fait partie du parcours normal + name: Normal + visible: false + id: '' + '5': + description: Fait partie du parcours challenge + name: Challenge + type: 2 + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/blocks.js b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/blocks.js new file mode 100644 index 0000000..ac85a5b --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/blocks.js @@ -0,0 +1,333 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/interpreter.js b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/interpreter.js new file mode 100644 index 0000000..1aaf6d7 --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/interpreter.js @@ -0,0 +1,71 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze.js b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze.js new file mode 100644 index 0000000..6799e4e --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze.js @@ -0,0 +1,1015 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + + //Unused + look: '#000', + movePegmanAnimation: res_path + 'move_avatar.png', + movePegmanAnimationFrameNumber: 9, + movePegmanAnimationSpeedScale: 1.5, + nectarSound: [res_path + 'getNectar.mp3', res_path + 'getNectar.ogg'], + nonDisappearingPegmanHittingObstacle: true, + turnAfterVictory: false, + wall0Sound: [res_path + 'wall0.mp3', res_path + 'wall0.ogg'], + wall1Sound: [res_path + 'wall1.mp3', res_path + 'wall1.ogg'], + wall2Sound: [res_path + 'wall2.mp3', res_path + 'wall2.ogg'], + wall3Sound: [res_path + 'wall3.mp3', res_path + 'wall3.ogg'], + wall4Sound: [res_path + 'wall4.mp3', res_path + 'wall4.ogg'], + wallPegmanAnimation: res_path + 'wall_avatar.png', + wallSound: [res_path + 'wall.mp3', res_path + 'wall.ogg'], + beeSound: true, + danceOnLoad: false, + hittingWallAnimation: res_path + 'wall.gif', + honeySound: [res_path + 'makeHoney.mp3', res_path + 'makeHoney.ogg'], + avatarIdle: res_path + 'idle_avatar.gif', + + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout[0]; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var val = Math.round(Math.random()); //Pick 1 or 0 at random + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/background.png b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/cloud.png b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/honey.png b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/start.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/tiles.png b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/tree.png b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/wall.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/wall.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/win.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze_config.json b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze_config.json new file mode 100644 index 0000000..4701325 --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_03/public/maze_config.json @@ -0,0 +1,60 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 2, 1, 1, 1, 1, 0], + [0, 0, 0, 1, 1, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "specialCells":{ + "honey":[], + "redFlower":[ + { + "x":3, + "y":3, + "value":1 + }, + { + "x":4, + "y":3, + "value":1 + }, + { + "x":6, + "y":3, + "value":1 + } + + ], + "purpleFlower":[], + "cloud":[] + }, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3, + "STARTANDFINISH": 4 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/run b/Cours 1 Code.org/Lecon 6/Maze_bee_03/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_03/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/student/maze.tpl.py b/Cours 1 Code.org/Lecon 6/Maze_bee_03/student/maze.tpl.py new file mode 100644 index 0000000..95f3a0f --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_03/student/maze.tpl.py @@ -0,0 +1,267 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"][0] + +MAP_CELLS = data["map"]["specialCells"] +for flowers in MAP_CELLS["purpleFlower"]: # Add the random value to the purple flowers + val = random.randrange(0, 2) + flowers["value"] = val + flowers["remainingValue"] = val +for flowers in MAP_CELLS["redFlower"]: + flowers["remainingValue"] = flowers["value"] +for honey in MAP_CELLS["honey"]: + honey["remainingValue"] = honey["value"] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + pass + print("True", end='', flush=True) + else: + pass + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_03/task.yaml b/Cours 1 Code.org/Lecon 6/Maze_bee_03/task.yaml new file mode 100644 index 0000000..2a3d984 --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_03/task.yaml @@ -0,0 +1,155 @@ +accessible: true +author: Florian Thuin +context: La fonction «Déplacer et obtenir le nectar» déplace l'abeille, recueille + le nectar et renvoie l'abeille là où elle a commencé. Utilise-la pour récolter + tout le nectar. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 3 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + + + + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + snap: true + spacing: 20 + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + trashcan: true + css: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + Déplacer et obtenir le nectar + Récupère 1 nectar sur une fleur puis revient au point de départ + + + turnRight + + + moveForward + + + + + moveBackward + + + turnLeft + + + + + + + + + + + + + header: |4+ + +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + description: '' + type: 0 + visible: true + name: Boucles répéter X fois + id: '1' + '1': + id: '2' + description: '' + type: 0 + visible: true + name: Utilisation de fonction + '2': + type: 2 + description: Fait partie de la leçon 6 + name: Lecon 6 + visible: true + id: '' + '3': + description: Fait partie du parcours facile + type: 2 + name: Facile + visible: false + id: '' + '4': + type: 2 + description: Fait partie du parcours normal + name: Normal + visible: false + id: '' + '5': + description: Fait partie du parcours challenge + name: Challenge + type: 2 + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/blocks.js b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/blocks.js new file mode 100644 index 0000000..ac85a5b --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/blocks.js @@ -0,0 +1,333 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/interpreter.js b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/interpreter.js new file mode 100644 index 0000000..1aaf6d7 --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/interpreter.js @@ -0,0 +1,71 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze.js b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze.js new file mode 100644 index 0000000..6799e4e --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze.js @@ -0,0 +1,1015 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + + //Unused + look: '#000', + movePegmanAnimation: res_path + 'move_avatar.png', + movePegmanAnimationFrameNumber: 9, + movePegmanAnimationSpeedScale: 1.5, + nectarSound: [res_path + 'getNectar.mp3', res_path + 'getNectar.ogg'], + nonDisappearingPegmanHittingObstacle: true, + turnAfterVictory: false, + wall0Sound: [res_path + 'wall0.mp3', res_path + 'wall0.ogg'], + wall1Sound: [res_path + 'wall1.mp3', res_path + 'wall1.ogg'], + wall2Sound: [res_path + 'wall2.mp3', res_path + 'wall2.ogg'], + wall3Sound: [res_path + 'wall3.mp3', res_path + 'wall3.ogg'], + wall4Sound: [res_path + 'wall4.mp3', res_path + 'wall4.ogg'], + wallPegmanAnimation: res_path + 'wall_avatar.png', + wallSound: [res_path + 'wall.mp3', res_path + 'wall.ogg'], + beeSound: true, + danceOnLoad: false, + hittingWallAnimation: res_path + 'wall.gif', + honeySound: [res_path + 'makeHoney.mp3', res_path + 'makeHoney.ogg'], + avatarIdle: res_path + 'idle_avatar.gif', + + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout[0]; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var val = Math.round(Math.random()); //Pick 1 or 0 at random + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/background.png b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/cloud.png b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/honey.png b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/start.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/tiles.png b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/tree.png b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/wall.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/wall.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/win.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze_config.json b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze_config.json new file mode 100644 index 0000000..1044bfe --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_04/public/maze_config.json @@ -0,0 +1,50 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 2, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "specialCells":{ + "honey":[], + "redFlower":[ + { + "x":4, + "y":3, + "value":4 + } + + ], + "purpleFlower":[], + "cloud":[] + }, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3, + "STARTANDFINISH": 4 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/run b/Cours 1 Code.org/Lecon 6/Maze_bee_04/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_04/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/student/maze.tpl.py b/Cours 1 Code.org/Lecon 6/Maze_bee_04/student/maze.tpl.py new file mode 100644 index 0000000..95f3a0f --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_04/student/maze.tpl.py @@ -0,0 +1,267 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"][0] + +MAP_CELLS = data["map"]["specialCells"] +for flowers in MAP_CELLS["purpleFlower"]: # Add the random value to the purple flowers + val = random.randrange(0, 2) + flowers["value"] = val + flowers["remainingValue"] = val +for flowers in MAP_CELLS["redFlower"]: + flowers["remainingValue"] = flowers["value"] +for honey in MAP_CELLS["honey"]: + honey["remainingValue"] = honey["value"] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + pass + print("True", end='', flush=True) + else: + pass + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_04/task.yaml b/Cours 1 Code.org/Lecon 6/Maze_bee_04/task.yaml new file mode 100644 index 0000000..184b39c --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_04/task.yaml @@ -0,0 +1,155 @@ +accessible: true +author: Florian Thuin +context: Il y a maintenant davantage de nectar dans les fleurs. Modifie la fonction + donnée afin qu'elle recueille 4 nectars et utilise-la pour recueillir l'ensemble + du nectar de la fleur. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 4 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + + + + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + snap: true + spacing: 20 + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + trashcan: true + css: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + Déplacer et obtenir 4 + Récupère 4 nectar sur une fleur puis revient au point de départ + + + turnRight + + + moveForward + + + + + moveBackward + + + turnLeft + + + + + + + + + + + + + header: |4+ + +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + description: '' + type: 0 + visible: true + name: Boucles répéter X fois + id: '1' + '1': + id: '2' + description: '' + type: 0 + visible: true + name: Utilisation de fonction + '2': + type: 2 + description: Fait partie de la leçon 6 + name: Lecon 6 + visible: true + id: '' + '3': + description: Fait partie du parcours facile + type: 2 + name: Facile + visible: false + id: '' + '4': + type: 2 + description: Fait partie du parcours normal + name: Normal + visible: false + id: '' + '5': + description: Fait partie du parcours challenge + name: Challenge + type: 2 + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/blocks.js b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/blocks.js new file mode 100644 index 0000000..0a938c4 --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/blocks.js @@ -0,0 +1,369 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['custom_if_bee'] = { + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([["nectar","nectarRemaining"], ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([["<","<"], [">",">"], ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(210); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/interpreter.js b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/interpreter.js new file mode 100644 index 0000000..90b48ab --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/interpreter.js @@ -0,0 +1,82 @@ +var initInterpreterApi = function(interpreter, scope) { + console.log("hello ?") + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(nectarRemaining()); + }; + interpreter.setProperty(scope, 'nectarRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(honeyRemaining()); + }; + interpreter.setProperty(scope, 'honeyRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + console.log("hello ?") + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze.js b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze.js new file mode 100644 index 0000000..495e4e4 --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze.js @@ -0,0 +1,1056 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + + //Unused + look: '#000', + movePegmanAnimation: res_path + 'move_avatar.png', + movePegmanAnimationFrameNumber: 9, + movePegmanAnimationSpeedScale: 1.5, + nectarSound: [res_path + 'getNectar.mp3', res_path + 'getNectar.ogg'], + nonDisappearingPegmanHittingObstacle: true, + turnAfterVictory: false, + wall0Sound: [res_path + 'wall0.mp3', res_path + 'wall0.ogg'], + wall1Sound: [res_path + 'wall1.mp3', res_path + 'wall1.ogg'], + wall2Sound: [res_path + 'wall2.mp3', res_path + 'wall2.ogg'], + wall3Sound: [res_path + 'wall3.mp3', res_path + 'wall3.ogg'], + wall4Sound: [res_path + 'wall4.mp3', res_path + 'wall4.ogg'], + wallPegmanAnimation: res_path + 'wall_avatar.png', + wallSound: [res_path + 'wall.mp3', res_path + 'wall.ogg'], + beeSound: true, + danceOnLoad: false, + hittingWallAnimation: res_path + 'wall.gif', + honeySound: [res_path + 'makeHoney.mp3', res_path + 'makeHoney.ogg'], + avatarIdle: res_path + 'idle_avatar.gif', + + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout[0]; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var val = Math.round(Math.random()); //Pick 1 or 0 at random + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +//Helper functions +var nectarRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var honeyRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/background.png b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/cloud.png b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/honey.png b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/start.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/tiles.png b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/tree.png b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/wall.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/wall.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/win.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze_config.json b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze_config.json new file mode 100644 index 0000000..5ba234d --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_05/public/maze_config.json @@ -0,0 +1,66 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 2, 1, 1, 1, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "specialCells":{ + "honey":[ + { + "x":3, + "y":4, + "value":7 + }, + { + "x":6, + "y":4, + "value":7 + } + ], + "redFlower":[ + { + "x":2, + "y":4, + "value":7 + }, + { + "x":5, + "y":4, + "value":7 + } + + ], + "purpleFlower":[], + "cloud":[] + }, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3, + "STARTANDFINISH": 4 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/run b/Cours 1 Code.org/Lecon 6/Maze_bee_05/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_05/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/student/maze.tpl.py b/Cours 1 Code.org/Lecon 6/Maze_bee_05/student/maze.tpl.py new file mode 100644 index 0000000..4aeee9e --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_05/student/maze.tpl.py @@ -0,0 +1,297 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"][0] + +MAP_CELLS = data["map"]["specialCells"] +for flowers in MAP_CELLS["purpleFlower"]: # Add the random value to the purple flowers + val = random.randrange(0, 2) + flowers["value"] = val + flowers["remainingValue"] = val +for flowers in MAP_CELLS["redFlower"]: + flowers["remainingValue"] = flowers["value"] +for honey in MAP_CELLS["honey"]: + honey["remainingValue"] = honey["value"] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def nectarRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def honeyRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for cell in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_05/task.yaml b/Cours 1 Code.org/Lecon 6/Maze_bee_05/task.yaml new file mode 100644 index 0000000..0855601 --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_05/task.yaml @@ -0,0 +1,160 @@ +accessible: true +author: Florian Thuin +context: Crée ta propre fonction qui obtiendra 7 nectars, se déplacera vers l'avant + et fera 7 unités de miel. Utilise la fonction pour recueillir le nectar de chaque + fleur et fabriquer du miel dans chaque gâteau de miel. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 5 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + + + + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + snap: true + spacing: 20 + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + trashcan: true + toolboxPosition: start + css: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + blocks_files: + - blocks.js + workspace: |- + + + + Obtenir 7 faire 7 + Récupère 7 nectar sur une fleur, avance puis fait 7 gateaux de miel + + + moveForward + + + + + + moveForward + + + moveForward + + + + + + + + + + + + + + name: '' + header: |4+ + +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + description: '' + type: 0 + visible: true + name: Boucles répéter X fois + id: '1' + '1': + id: '2' + description: '' + type: 0 + visible: true + name: Utilisation de fonction + '2': + type: 0 + description: '' + name: Création de fonction + id: '3' + visible: true + '3': + description: Fait partie de la leçon 6 + visible: true + type: 2 + name: Lecon 6 + id: '' + '4': + type: 2 + description: Fait partie du parcours facile + name: Facile + visible: false + id: '' + '5': + description: Fait partie du parcours normal + name: Normal + type: 2 + visible: false + id: '' + '6': + description: Fait partie du parcours challenge + type: 2 + name: Challenge + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/blocks.js b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/blocks.js new file mode 100644 index 0000000..0a938c4 --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/blocks.js @@ -0,0 +1,369 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['custom_if_bee'] = { + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([["nectar","nectarRemaining"], ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([["<","<"], [">",">"], ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(210); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/interpreter.js b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/interpreter.js new file mode 100644 index 0000000..1c77c42 --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/interpreter.js @@ -0,0 +1,92 @@ +var initInterpreterApi = function(interpreter, scope) { + console.log("hello ?") + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(nectarRemaining()); + }; + interpreter.setProperty(scope, 'nectarRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(honeyRemaining()); + }; + interpreter.setProperty(scope, 'honeyRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnFlower()); + }; + interpreter.setProperty(scope, 'isOnFlower', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnHoney()); + }; + interpreter.setProperty(scope, 'isOnHoney', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + console.log("hello ?") + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze.js b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze.js new file mode 100644 index 0000000..c3b85fe --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze.js @@ -0,0 +1,1083 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + + //Unused + look: '#000', + movePegmanAnimation: res_path + 'move_avatar.png', + movePegmanAnimationFrameNumber: 9, + movePegmanAnimationSpeedScale: 1.5, + nectarSound: [res_path + 'getNectar.mp3', res_path + 'getNectar.ogg'], + nonDisappearingPegmanHittingObstacle: true, + turnAfterVictory: false, + wall0Sound: [res_path + 'wall0.mp3', res_path + 'wall0.ogg'], + wall1Sound: [res_path + 'wall1.mp3', res_path + 'wall1.ogg'], + wall2Sound: [res_path + 'wall2.mp3', res_path + 'wall2.ogg'], + wall3Sound: [res_path + 'wall3.mp3', res_path + 'wall3.ogg'], + wall4Sound: [res_path + 'wall4.mp3', res_path + 'wall4.ogg'], + wallPegmanAnimation: res_path + 'wall_avatar.png', + wallSound: [res_path + 'wall.mp3', res_path + 'wall.ogg'], + beeSound: true, + danceOnLoad: false, + hittingWallAnimation: res_path + 'wall.gif', + honeySound: [res_path + 'makeHoney.mp3', res_path + 'makeHoney.ogg'], + avatarIdle: res_path + 'idle_avatar.gif', + + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout[0]; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var val = Math.round(Math.random()); //Pick 1 or 0 at random + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +//Helper functions +var nectarRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var honeyRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var isOnFlower = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +var isOnHoney = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/background.png b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/cloud.png b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/honey.png b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/start.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/tiles.png b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/tree.png b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/wall.gif b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/wall.gif differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/win.ogg b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze_config.json b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze_config.json new file mode 100644 index 0000000..f0e54fb --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_06/public/maze_config.json @@ -0,0 +1,56 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 2, 0], + [0, 0, 0, 0, 0, 1, 1, 0], + [0, 0, 0, 0, 1, 1, 0, 0], + [0, 0, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "specialCells":{ + "honey":[], + "redFlower":[], + "purpleFlower":[ + { + "x":5, + "y":3 + }, + { + "x":4, + "y":4 + }, + { + "x":3, + "y":5 + } + ], + "cloud":[] + }, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3, + "STARTANDFINISH": 4 + }, + "startDirection":"SOUTH", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/run b/Cours 1 Code.org/Lecon 6/Maze_bee_06/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_06/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/student/maze.tpl.py b/Cours 1 Code.org/Lecon 6/Maze_bee_06/student/maze.tpl.py new file mode 100644 index 0000000..4aeee9e --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_06/student/maze.tpl.py @@ -0,0 +1,297 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"][0] + +MAP_CELLS = data["map"]["specialCells"] +for flowers in MAP_CELLS["purpleFlower"]: # Add the random value to the purple flowers + val = random.randrange(0, 2) + flowers["value"] = val + flowers["remainingValue"] = val +for flowers in MAP_CELLS["redFlower"]: + flowers["remainingValue"] = flowers["value"] +for honey in MAP_CELLS["honey"]: + honey["remainingValue"] = honey["value"] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def nectarRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def honeyRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for cell in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 6/Maze_bee_06/task.yaml b/Cours 1 Code.org/Lecon 6/Maze_bee_06/task.yaml new file mode 100644 index 0000000..3bdf91f --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/Maze_bee_06/task.yaml @@ -0,0 +1,150 @@ +accessible: true +author: Florian Thuin +context: Les fleurs pourpres peuvent avoir 1 ou 0 nectar. Crée une fonction qui récolte + du nectar seulement sur les fleurs pourpres qui en ont. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + time: '30' + output: '2' +name: Exercice 6 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + + + + nectarRemaining + > + 0 + + + + + + + + + options: + scrollbars: true + grid: + length: 3 + snap: true + spacing: 20 + colour: '#ccc' + css: true + toolboxPosition: start + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + trashcan: true + sounds: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + Obtenir nectar pourpre + Récupère le nectar des fleurs pourpre + + + header: |4+ + +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + visible: true + type: 0 + name: Boucles répéter X fois + id: '1' + description: '' + '1': + id: '2' + visible: true + name: Utilisation de fonction + description: '' + type: 0 + '2': + visible: true + id: '3' + type: 0 + description: '' + name: Création de fonction + '3': + type: 0 + id: '4' + visible: true + description: '' + name: Condition + '4': + type: 2 + visible: true + name: Lecon 6 + description: Fait partie de la leçon 6 + id: '' + '5': + description: Fait partie du parcours facile + name: Facile + type: 2 + visible: false + id: '' + '6': + description: Fait partie du parcours normal + name: Normal + type: 2 + visible: false + id: '' + '7': + description: Fait partie du parcours challenge + type: 2 + name: Challenge + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 6/course.yaml b/Cours 1 Code.org/Lecon 6/course.yaml new file mode 100644 index 0000000..9bf98e9 --- /dev/null +++ b/Cours 1 Code.org/Lecon 6/course.yaml @@ -0,0 +1,18 @@ +accessible: true +name: Cours 6 - Collect and construct +description: '' +admins: +- '' +tutors: [] +groups_student_choice: false +use_classrooms: true +allow_unregister: true +allow_preview: false +registration: true +registration_password: null +registration_ac: null +registration_ac_list: +- '' +is_lti: false +lti_keys: {} +lti_send_back_grade: false diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/blocks.js b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/blocks.js new file mode 100644 index 0000000..4ac2453 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/blocks.js @@ -0,0 +1,406 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['custom_if_bee'] = { + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(210); + this.setTooltip("Execute le code si le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_be_if_on'] = { + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["à la fleur","isOnFlower"], + ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le code si le personnage est sur une fleur ou sur un gâteau de miel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_type+'()){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_type+'():\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/interpreter.js b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/interpreter.js new file mode 100644 index 0000000..bfc0171 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/interpreter.js @@ -0,0 +1,90 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(nectarRemaining()); + }; + interpreter.setProperty(scope, 'nectarRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(honeyRemaining()); + }; + interpreter.setProperty(scope, 'honeyRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnFlower()); + }; + interpreter.setProperty(scope, 'isOnFlower', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnHoney()); + }; + interpreter.setProperty(scope, 'isOnHoney', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze.js b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze.js new file mode 100644 index 0000000..948a4e7 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze.js @@ -0,0 +1,1094 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + + //Unused + look: '#000', + movePegmanAnimation: res_path + 'move_avatar.png', + movePegmanAnimationFrameNumber: 9, + movePegmanAnimationSpeedScale: 1.5, + nectarSound: [res_path + 'getNectar.mp3', res_path + 'getNectar.ogg'], + nonDisappearingPegmanHittingObstacle: true, + turnAfterVictory: false, + wall0Sound: [res_path + 'wall0.mp3', res_path + 'wall0.ogg'], + wall1Sound: [res_path + 'wall1.mp3', res_path + 'wall1.ogg'], + wall2Sound: [res_path + 'wall2.mp3', res_path + 'wall2.ogg'], + wall3Sound: [res_path + 'wall3.mp3', res_path + 'wall3.ogg'], + wall4Sound: [res_path + 'wall4.mp3', res_path + 'wall4.ogg'], + wallPegmanAnimation: res_path + 'wall_avatar.png', + wallSound: [res_path + 'wall.mp3', res_path + 'wall.ogg'], + beeSound: true, + danceOnLoad: false, + hittingWallAnimation: res_path + 'wall.gif', + honeySound: [res_path + 'makeHoney.mp3', res_path + 'makeHoney.ogg'], + avatarIdle: res_path + 'idle_avatar.gif', + + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout[0]; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + if(cell.optional){ //If this cell is optional, generate a random number to remove it or not + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, remove it + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + } + } + else + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var val = Math.round(Math.random()); //Pick 1 or 0 at random + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +//Helper functions +var nectarRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var honeyRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var isOnFlower = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +var isOnHoney = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/background.png b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/cloud.png b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/honey.png b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/start.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/tiles.png b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/tree.png b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/wall.gif b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/wall.gif differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/win.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze_config.json b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze_config.json new file mode 100644 index 0000000..13297f6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_01/public/maze_config.json @@ -0,0 +1,55 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 2, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "specialCells":{ + "honey":[], + "redFlower":[ + { + "x":5, + "y":3, + "value":1, + "optional":true + } + ], + "purpleFlower":[], + "cloud":[ + { + "x":5, + "y":3 + } + ] + }, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3, + "STARTANDFINISH": 4 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/run b/Cours 1 Code.org/Lecon 7/Maze_bee_01/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_01/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/student/maze.tpl.py b/Cours 1 Code.org/Lecon 7/Maze_bee_01/student/maze.tpl.py new file mode 100644 index 0000000..570df99 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_01/student/maze.tpl.py @@ -0,0 +1,326 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"][0] + +toRemove = [] + +MAP_CELLS = data["map"]["specialCells"] +for kind in MAP_CELLS: # Add the random value to the purple flowers + for item in MAP_CELLS[kind]: + if "optional" in item: # May remove item + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: #Remove + toRemove.append((item,kind)) + if(kind == "purpleFlower"): + val = random.randrange(0, 2) + item["value"] = val + if(kind != "cloud"): + item["remainingValue"] = item["value"] +for (item,kind) in toRemove: + MAP_CELLS[kind].remove(item) + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def nectarRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def honeyRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for cell in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def isOnFlower(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + return True + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + return True + +def isOnHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for cell in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + return True + + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_01/task.yaml b/Cours 1 Code.org/Lecon 7/Maze_bee_01/task.yaml new file mode 100644 index 0000000..5bffc77 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_01/task.yaml @@ -0,0 +1,128 @@ +accessible: true +author: Florian Thuin +context: L'abeille ne peut pas savoir ce qui se trouve sous le nuage. Recueille le + nectar seulement sur les fleurs, mais vérifie d'abord si c'est une fleur ou un + gâteau de miel. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 1 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + + + + isOnFlower + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + snap: true + spacing: 20 + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + trashcan: true + css: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + header: |4+ + +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + description: '' + type: 0 + visible: true + name: Boucles répéter X fois + id: '1' + '1': + id: '4' + description: '' + type: 0 + visible: true + name: Condition + '2': + type: 2 + description: Fait partie de la leçon 7 + name: Lecon 7 + visible: true + id: '' + '3': + description: Fait partie du parcours facile + type: 2 + name: Facile + visible: false + id: '' + '4': + type: 2 + description: Fait partie du parcours normal + name: Normal + visible: false + id: '' + '5': + description: Fait partie du parcours challenge + name: Challenge + type: 2 + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/blocks.js b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/blocks.js new file mode 100644 index 0000000..4ac2453 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/blocks.js @@ -0,0 +1,406 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['custom_if_bee'] = { + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(210); + this.setTooltip("Execute le code si le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_be_if_on'] = { + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["à la fleur","isOnFlower"], + ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le code si le personnage est sur une fleur ou sur un gâteau de miel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_type+'()){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_type+'():\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/interpreter.js b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/interpreter.js new file mode 100644 index 0000000..bfc0171 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/interpreter.js @@ -0,0 +1,90 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(nectarRemaining()); + }; + interpreter.setProperty(scope, 'nectarRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(honeyRemaining()); + }; + interpreter.setProperty(scope, 'honeyRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnFlower()); + }; + interpreter.setProperty(scope, 'isOnFlower', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnHoney()); + }; + interpreter.setProperty(scope, 'isOnHoney', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze.js b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze.js new file mode 100644 index 0000000..948a4e7 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze.js @@ -0,0 +1,1094 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + + //Unused + look: '#000', + movePegmanAnimation: res_path + 'move_avatar.png', + movePegmanAnimationFrameNumber: 9, + movePegmanAnimationSpeedScale: 1.5, + nectarSound: [res_path + 'getNectar.mp3', res_path + 'getNectar.ogg'], + nonDisappearingPegmanHittingObstacle: true, + turnAfterVictory: false, + wall0Sound: [res_path + 'wall0.mp3', res_path + 'wall0.ogg'], + wall1Sound: [res_path + 'wall1.mp3', res_path + 'wall1.ogg'], + wall2Sound: [res_path + 'wall2.mp3', res_path + 'wall2.ogg'], + wall3Sound: [res_path + 'wall3.mp3', res_path + 'wall3.ogg'], + wall4Sound: [res_path + 'wall4.mp3', res_path + 'wall4.ogg'], + wallPegmanAnimation: res_path + 'wall_avatar.png', + wallSound: [res_path + 'wall.mp3', res_path + 'wall.ogg'], + beeSound: true, + danceOnLoad: false, + hittingWallAnimation: res_path + 'wall.gif', + honeySound: [res_path + 'makeHoney.mp3', res_path + 'makeHoney.ogg'], + avatarIdle: res_path + 'idle_avatar.gif', + + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout[0]; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + if(cell.optional){ //If this cell is optional, generate a random number to remove it or not + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, remove it + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + } + } + else + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var val = Math.round(Math.random()); //Pick 1 or 0 at random + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +//Helper functions +var nectarRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var honeyRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var isOnFlower = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +var isOnHoney = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/background.png b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/cloud.png b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/honey.png b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/start.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/tiles.png b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/tree.png b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/wall.gif b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/wall.gif differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/win.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze_config.json b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze_config.json new file mode 100644 index 0000000..94e7435 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_02/public/maze_config.json @@ -0,0 +1,65 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 2, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "specialCells":{ + "honey":[], + "redFlower":[ + { + "x":4, + "y":4, + "value":1, + "optional":true + }, + { + "x":4, + "y":5, + "value":1, + "optional":true + } + ], + "purpleFlower":[], + "cloud":[ + { + "x":4, + "y":5 + }, + { + "x":4, + "y":4 + } + ] + }, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3, + "STARTANDFINISH": 4 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/run b/Cours 1 Code.org/Lecon 7/Maze_bee_02/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_02/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/student/maze.tpl.py b/Cours 1 Code.org/Lecon 7/Maze_bee_02/student/maze.tpl.py new file mode 100644 index 0000000..570df99 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_02/student/maze.tpl.py @@ -0,0 +1,326 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"][0] + +toRemove = [] + +MAP_CELLS = data["map"]["specialCells"] +for kind in MAP_CELLS: # Add the random value to the purple flowers + for item in MAP_CELLS[kind]: + if "optional" in item: # May remove item + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: #Remove + toRemove.append((item,kind)) + if(kind == "purpleFlower"): + val = random.randrange(0, 2) + item["value"] = val + if(kind != "cloud"): + item["remainingValue"] = item["value"] +for (item,kind) in toRemove: + MAP_CELLS[kind].remove(item) + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def nectarRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def honeyRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for cell in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def isOnFlower(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + return True + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + return True + +def isOnHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for cell in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + return True + + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_02/task.yaml b/Cours 1 Code.org/Lecon 7/Maze_bee_02/task.yaml new file mode 100644 index 0000000..dee2506 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_02/task.yaml @@ -0,0 +1,122 @@ +accessible: true +author: Florian Thuin +context: L'abeille veut seulement du nectar. Tous les objets ne sont pas des fleurs, + alors vérifie sous CHAQUE NUAGE pour voir s’il s’y cache une fleur. Si c'est le + cas, alors tu peux recueillir du nectar. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 2 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + + + + isOnFlower + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + spacing: 20 + snap: true + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + css: true + toolboxPosition: start + trashcan: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + header: |4+ + +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + visible: true + name: Boucles répéter X fois + id: '1' + type: 0 + description: '' + '1': + id: '4' + description: '' + type: 0 + visible: true + name: Condition + '2': + description: Fait partie du parcours normal + name: Normal + type: 2 + visible: false + id: '' + '3': + description: Fait partie du parcours challenge + name: Challenge + type: 2 + visible: false + id: '' + '4': + type: 2 + description: Fait partie de la leçon 7 + name: Lecon 7 + visible: true + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/blocks.js b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/blocks.js new file mode 100644 index 0000000..a0a1504 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/blocks.js @@ -0,0 +1,441 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['custom_if_bee'] = { + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(210); + this.setTooltip("Execute le code si le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_bee_if_else'] = { + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([["à la fleur","isOnFlower"], ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT_IF") + .setCheck(null) + .appendField("faire"); + this.appendStatementInput("STAT_ELSE") + .setCheck(null) + .appendField("sinon"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le premier code si le personnage est sur une fleur, sinon, exécute le secondel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.JavaScript.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.JavaScript.statementToCode(block, 'STAT_ELSE'); + var code = 'if ('+dropdown_type+'()){\n'+statements_stat_if+"}\nelse{"+statements_stat_else+"}\n"; + return code; +}; + +Blockly.Python['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.Python.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.Python.statementToCode(block, 'STAT_ELSE'); + var code = 'if '+dropdown_type+'():\n'+statements_stat_if+"\nelse:"+statements_stat_else; + return code; +}; + +Blockly.Blocks['custom_be_if_on'] = { + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["à la fleur","isOnFlower"], + ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le code si le personnage est sur une fleur ou sur un gâteau de miel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_type+'()){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_type+'():\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/interpreter.js b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/interpreter.js new file mode 100644 index 0000000..bfc0171 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/interpreter.js @@ -0,0 +1,90 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(nectarRemaining()); + }; + interpreter.setProperty(scope, 'nectarRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(honeyRemaining()); + }; + interpreter.setProperty(scope, 'honeyRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnFlower()); + }; + interpreter.setProperty(scope, 'isOnFlower', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnHoney()); + }; + interpreter.setProperty(scope, 'isOnHoney', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze.js b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze.js new file mode 100644 index 0000000..a1061e3 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze.js @@ -0,0 +1,1106 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + + //Unused + look: '#000', + movePegmanAnimation: res_path + 'move_avatar.png', + movePegmanAnimationFrameNumber: 9, + movePegmanAnimationSpeedScale: 1.5, + nectarSound: [res_path + 'getNectar.mp3', res_path + 'getNectar.ogg'], + nonDisappearingPegmanHittingObstacle: true, + turnAfterVictory: false, + wall0Sound: [res_path + 'wall0.mp3', res_path + 'wall0.ogg'], + wall1Sound: [res_path + 'wall1.mp3', res_path + 'wall1.ogg'], + wall2Sound: [res_path + 'wall2.mp3', res_path + 'wall2.ogg'], + wall3Sound: [res_path + 'wall3.mp3', res_path + 'wall3.ogg'], + wall4Sound: [res_path + 'wall4.mp3', res_path + 'wall4.ogg'], + wallPegmanAnimation: res_path + 'wall_avatar.png', + wallSound: [res_path + 'wall.mp3', res_path + 'wall.ogg'], + beeSound: true, + danceOnLoad: false, + hittingWallAnimation: res_path + 'wall.gif', + honeySound: [res_path + 'makeHoney.mp3', res_path + 'makeHoney.ogg'], + avatarIdle: res_path + 'idle_avatar.gif', + + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout[0]; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + if(cell.optional){ //If this cell is optional, generate a random number to remove it or not + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, remove it + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + } + } + else if(cell.or){ //If this cell is either this kind or another + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, change the type + cell.remainingValue = cell.value; + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + Maze.mapCells[cell.or].push(cell) + } + } + else + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var val = Math.round(Math.random()); //Pick 1 or 0 at random + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +//Helper functions +var nectarRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var honeyRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var isOnFlower = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +var isOnHoney = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/background.png b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/cloud.png b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/honey.png b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/start.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/tiles.png b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/tree.png b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/wall.gif b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/wall.gif differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/win.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze_config.json b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze_config.json new file mode 100644 index 0000000..cbfe930 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_03/public/maze_config.json @@ -0,0 +1,55 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 2, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "specialCells":{ + "honey":[], + "redFlower":[ + { + "x":2, + "y":3, + "value":1, + "or":"honey" + } + ], + "purpleFlower":[], + "cloud":[ + { + "x":2, + "y":3 + } + ] + }, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3, + "STARTANDFINISH": 4 + }, + "startDirection":"WEST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/run b/Cours 1 Code.org/Lecon 7/Maze_bee_03/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_03/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/student/maze.tpl.py b/Cours 1 Code.org/Lecon 7/Maze_bee_03/student/maze.tpl.py new file mode 100644 index 0000000..663df63 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_03/student/maze.tpl.py @@ -0,0 +1,338 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"][0] + +toRemove = [] +toAdd = [] + +MAP_CELLS = data["map"]["specialCells"] +for kind in MAP_CELLS: # Add the random value to the purple flowers + for item in MAP_CELLS[kind]: + if "optional" in item: # May remove item + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Remove + toRemove.append((item,kind)) + elif "or" in item: # May switch kind + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Switch + toRemove.append((item,kind)) + toAdd.append((item, item["or"])) + if(kind == "purpleFlower"): + val = random.randrange(0, 2) + item["value"] = val + if(kind != "cloud"): + item["remainingValue"] = item["value"] + +#Remove and add items after the loop +for (item,kind) in toRemove: + MAP_CELLS[kind].remove(item) +for (item,kind) in toAdd: + MAP_CELLS[kind].append(item) + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def nectarRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def honeyRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for cell in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def isOnFlower(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + return True + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + return True + +def isOnHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for cell in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + return True + + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_03/task.yaml b/Cours 1 Code.org/Lecon 7/Maze_bee_03/task.yaml new file mode 100644 index 0000000..035af20 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_03/task.yaml @@ -0,0 +1,128 @@ +accessible: true +author: Florian Thuin +context: L'objet inconnu est soit une fleur, soit un gâteau de miel. Utilise le bloc + «Si/Sinon» pour récupérer le nectar si c'est une fleur, ou pour fabriquer du + miel (parce que c'est un gâteau de miel). +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 3 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + + + + isOnFlower + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + snap: true + spacing: 20 + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + trashcan: true + css: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + header: |4+ + +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + description: '' + type: 0 + visible: true + name: Boucles répéter X fois + id: '1' + '1': + id: '4' + description: '' + type: 0 + visible: true + name: Condition + '2': + type: 0 + description: '' + name: Si/Sinon + id: '5' + visible: true + '3': + description: Fait partie du parcours normal + type: 2 + name: Normal + visible: false + id: '' + '4': + type: 2 + description: Fait partie du parcours challenge + name: Challenge + visible: false + id: '' + '5': + visible: true + description: Fait partie de la leçon 7 + name: Lecon 7 + type: 2 + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/blocks.js b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/blocks.js new file mode 100644 index 0000000..a0a1504 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/blocks.js @@ -0,0 +1,441 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['custom_if_bee'] = { + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(210); + this.setTooltip("Execute le code si le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_bee_if_else'] = { + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([["à la fleur","isOnFlower"], ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT_IF") + .setCheck(null) + .appendField("faire"); + this.appendStatementInput("STAT_ELSE") + .setCheck(null) + .appendField("sinon"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le premier code si le personnage est sur une fleur, sinon, exécute le secondel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.JavaScript.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.JavaScript.statementToCode(block, 'STAT_ELSE'); + var code = 'if ('+dropdown_type+'()){\n'+statements_stat_if+"}\nelse{"+statements_stat_else+"}\n"; + return code; +}; + +Blockly.Python['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.Python.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.Python.statementToCode(block, 'STAT_ELSE'); + var code = 'if '+dropdown_type+'():\n'+statements_stat_if+"\nelse:"+statements_stat_else; + return code; +}; + +Blockly.Blocks['custom_be_if_on'] = { + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["à la fleur","isOnFlower"], + ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le code si le personnage est sur une fleur ou sur un gâteau de miel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_type+'()){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_type+'():\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/interpreter.js b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/interpreter.js new file mode 100644 index 0000000..bfc0171 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/interpreter.js @@ -0,0 +1,90 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(nectarRemaining()); + }; + interpreter.setProperty(scope, 'nectarRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(honeyRemaining()); + }; + interpreter.setProperty(scope, 'honeyRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnFlower()); + }; + interpreter.setProperty(scope, 'isOnFlower', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnHoney()); + }; + interpreter.setProperty(scope, 'isOnHoney', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze.js b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze.js new file mode 100644 index 0000000..8b68c8f --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze.js @@ -0,0 +1,1108 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + + //Unused + look: '#000', + movePegmanAnimation: res_path + 'move_avatar.png', + movePegmanAnimationFrameNumber: 9, + movePegmanAnimationSpeedScale: 1.5, + nectarSound: [res_path + 'getNectar.mp3', res_path + 'getNectar.ogg'], + nonDisappearingPegmanHittingObstacle: true, + turnAfterVictory: false, + wall0Sound: [res_path + 'wall0.mp3', res_path + 'wall0.ogg'], + wall1Sound: [res_path + 'wall1.mp3', res_path + 'wall1.ogg'], + wall2Sound: [res_path + 'wall2.mp3', res_path + 'wall2.ogg'], + wall3Sound: [res_path + 'wall3.mp3', res_path + 'wall3.ogg'], + wall4Sound: [res_path + 'wall4.mp3', res_path + 'wall4.ogg'], + wallPegmanAnimation: res_path + 'wall_avatar.png', + wallSound: [res_path + 'wall.mp3', res_path + 'wall.ogg'], + beeSound: true, + danceOnLoad: false, + hittingWallAnimation: res_path + 'wall.gif', + honeySound: [res_path + 'makeHoney.mp3', res_path + 'makeHoney.ogg'], + avatarIdle: res_path + 'idle_avatar.gif', + + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout[0]; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; +// Set the remainingValue fields +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + if(cell.optional){ //If this cell is optional, generate a random number to remove it or not + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, remove it + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + } + } + else if(cell.or){ //If this cell is either this kind or another + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, change the type + cell.remainingValue = cell.value; + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + Maze.mapCells[cell.or].push(cell) + } + } + else + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var min = cell.range[0]; + var max = cell.range[1]; + var val = Math.floor(Math.random() * (max - min + 1)) + min;; //Pick a random number in range + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); + + // TODO maybe add this + // if (Maze.shouldCheckSuccessOnMove()) { + // Maze.checkSuccess(); + // } +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +//Helper functions +var nectarRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var honeyRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var isOnFlower = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +var isOnHoney = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/background.png b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/cloud.png b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/honey.png b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/start.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/tiles.png b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/tree.png b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/wall.gif b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/wall.gif differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/win.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze_config.json b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze_config.json new file mode 100644 index 0000000..a878cd7 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_04/public/maze_config.json @@ -0,0 +1,49 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 1, 0, 0, 0], + [0, 0, 1, 2, 1, 0, 0, 0], + [0, 0, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "specialCells":{ + "honey":[], + "redFlower":[], + "purpleFlower":[ + { + "x":3, + "y":4, + "range":[1,3] + } + ], + "cloud":[] + }, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3, + "STARTANDFINISH": 4 + }, + "startDirection":"SOUTH", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/run b/Cours 1 Code.org/Lecon 7/Maze_bee_04/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_04/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/student/maze.tpl.py b/Cours 1 Code.org/Lecon 7/Maze_bee_04/student/maze.tpl.py new file mode 100644 index 0000000..529acf1 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_04/student/maze.tpl.py @@ -0,0 +1,340 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"][0] + +toRemove = [] +toAdd = [] + +MAP_CELLS = data["map"]["specialCells"] +for kind in MAP_CELLS: # Add the random value to the purple flowers + for item in MAP_CELLS[kind]: + if "optional" in item: # May remove item + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Remove + toRemove.append((item,kind)) + elif "or" in item: # May switch kind + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Switch + toRemove.append((item,kind)) + toAdd.append((item, item["or"])) + if(kind == "purpleFlower"): + min = item["range"][0] + max = item["range"][1] + val = random.randrange(min, max+1) + item["value"] = val + if(kind != "cloud"): + item["remainingValue"] = item["value"] + +#Remove and add items after the loop +for (item,kind) in toRemove: + MAP_CELLS[kind].remove(item) +for (item,kind) in toAdd: + MAP_CELLS[kind].append(item) + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def nectarRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def honeyRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for cell in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def isOnFlower(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + return True + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + return True + +def isOnHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for cell in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + return True + + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_04/task.yaml b/Cours 1 Code.org/Lecon 7/Maze_bee_04/task.yaml new file mode 100644 index 0000000..b7b1d66 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_04/task.yaml @@ -0,0 +1,142 @@ +accessible: true +author: Florian Thuin +context: Cette fleur pourpre peut avoir 3, 2 ou 1 nectar(s). Tu vas de nouveau avoir + besoin du bloc "si" et d'une boucle. Tous les éléments sont sur le plan de travail, + mais pas emboîtés ! +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 4 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + + + + nectarRemaining + > + 0 + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + snap: true + spacing: 20 + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + trashcan: true + css: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + moveForward + + + 3 + + + nectarRemaining + > + 0 + + + + header: |4+ + +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + description: '' + type: 0 + visible: true + name: Boucles répéter X fois + id: '1' + '1': + id: '4' + description: '' + type: 0 + visible: true + name: Condition + '2': + type: 0 + description: '' + name: Si/Sinon + id: '5' + visible: true + '3': + description: Fait partie du parcours normal + type: 2 + name: Normal + visible: false + id: '' + '4': + type: 2 + description: Fait partie du parcours challenge + name: Challenge + visible: false + id: '' + '5': + visible: true + description: Fait partie de la leçon 7 + name: Lecon 7 + type: 2 + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/blocks.js b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/blocks.js new file mode 100644 index 0000000..ea8e6ed --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/blocks.js @@ -0,0 +1,482 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks.maze_move = { + /** + * Block for moving forward/backward. + * @this Blockly.Block + */ + + init: function() { + var DIRECTIONS = [ + ["avancer plus", "moveForward"], + ["reculer", "moveBackward"] + ]; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Avance ou recule le personnage.'); + } +}; + +Blockly.JavaScript['maze_move'] = function(block) { + var dir = this.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_move'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['custom_if_bee'] = { + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(210); + this.setTooltip("Execute le code si le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_if_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_while_bee'] = { + init: function() { + this.appendDummyInput() + .appendField("tant que") + .appendField(new Blockly.FieldDropdown([ + ["nectar","nectarRemaining"], + ["miel","honeyRemaining"]]), "KIND") + .appendField(new Blockly.FieldDropdown([ + ["<","<"], + [">",">"], + ["=","=="]]), "COMP") + .appendField(new Blockly.FieldNumber(0), "NUMBER"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(Maze.Blocks.LOOPS_HUE); + this.setTooltip("Execute le code tant que le personnage est sur une fleur avec du nectar"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'while('+dropdown_kind+'() '+dropdown_comp+' '+number_number+'){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_while_bee'] = function(block) { + var dropdown_kind = block.getFieldValue('KIND'); + var dropdown_comp = block.getFieldValue('COMP'); + var number_number = block.getFieldValue('NUMBER'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'while '+dropdown_kind+'() '+dropdown_comp+' '+number_number+':\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['custom_bee_if_else'] = { + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([["à la fleur","isOnFlower"], ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT_IF") + .setCheck(null) + .appendField("faire"); + this.appendStatementInput("STAT_ELSE") + .setCheck(null) + .appendField("sinon"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le premier code si le personnage est sur une fleur, sinon, exécute le secondel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.JavaScript.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.JavaScript.statementToCode(block, 'STAT_ELSE'); + var code = 'if ('+dropdown_type+'()){\n'+statements_stat_if+"}\nelse{"+statements_stat_else+"}\n"; + return code; +}; + +Blockly.Python['custom_bee_if_else'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat_if = Blockly.Python.statementToCode(block, 'STAT_IF'); + var statements_stat_else = Blockly.Python.statementToCode(block, 'STAT_ELSE'); + var code = 'if '+dropdown_type+'():\n'+statements_stat_if+"\nelse:"+statements_stat_else; + return code; +}; + +Blockly.Blocks['custom_be_if_on'] = { + init: function() { + this.appendDummyInput() + .appendField("si") + .appendField(new Blockly.FieldDropdown([ + ["à la fleur","isOnFlower"], + ["au gâteau de miel","isOnHoney"]]), "TYPE"); + this.appendStatementInput("STAT") + .setCheck(null) + .appendField("faire"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(180); + this.setTooltip("Execute le code si le personnage est sur une fleur ou sur un gâteau de miel"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'if('+dropdown_type+'()){\n'+statements_stat+"}\n"; + return code; +}; + +Blockly.Python['custom_be_if_on'] = function(block) { + var dropdown_type = block.getFieldValue('TYPE'); + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'if '+dropdown_type+'():\n'+statements_stat+"\n"; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; + +Blockly.Blocks['maze_nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 1 nectar'); + } +}; + +Blockly.JavaScript['maze_nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'getNectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_nectar'] = function(block) { + return 'getNectar()\n'; +}; + +Blockly.Blocks['maze_2nectar'] = { + /** + * Block for collecting nectar + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('récolter 2x du nectar'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Récolte 2 nectar'); + } +}; + +Blockly.JavaScript['maze_2nectar'] = function(block) { + // Generate javascript for collecting nectar + return 'get2Nectar(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_2nectar'] = function(block) { + return 'get2Nectar()\n'; +}; + +Blockly.Blocks['maze_honey'] = { + /** + * Block for making honey + */ + init: function() { + this.setColour(184); + this.appendDummyInput().appendField('fabriquer du miel'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip('Fabrique 1 quantité de miel'); + } +}; + +Blockly.JavaScript['maze_honey'] = function(block) { + // Generate javascript for collecting nectar + return 'makeHoney(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_honey'] = function(block) { + // Generate Python for making honey + return 'makeHoney()\n'; +}; diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/interpreter.js b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/interpreter.js new file mode 100644 index 0000000..bfc0171 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/interpreter.js @@ -0,0 +1,90 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.getNectar(id.toString()); + }; + interpreter.setProperty(scope, 'getNectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(nectarRemaining()); + }; + interpreter.setProperty(scope, 'nectarRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(honeyRemaining()); + }; + interpreter.setProperty(scope, 'honeyRemaining', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnFlower()); + }; + interpreter.setProperty(scope, 'isOnFlower', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(isOnHoney()); + }; + interpreter.setProperty(scope, 'isOnHoney', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.get2Nectar(id.toString()); + }; + interpreter.setProperty(scope, 'get2Nectar', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.makeHoney(id.toString()); + }; + interpreter.setProperty(scope, 'makeHoney', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze.js b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze.js new file mode 100644 index 0000000..c46175c --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze.js @@ -0,0 +1,1081 @@ +var task_directory_path = window.location.pathname + "/"; +var res_path = task_directory_path+ "maze/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +Maze.CRASH_STOP = 1; + +Maze.SKIN = { + // This is required when move pegman animation is set + actionSpeedScale: { + nectar: 1, + }, + background: res_path + json.visuals.background, + tiles: res_path + json.visuals.tiles, + sprite: res_path + json.visuals.sprite, + cloud: res_path + json.visuals.cloud, + cloudAnimation: res_path + json.visuals.cloudAnimation, + honey: res_path + json.visuals.honey, + purpleFlower: res_path + json.visuals.purpleFlower, + redFlower: res_path + json.visuals.redFlower, + obstacleScale: json.visuals.obstacleScale, + + //Sounds + winGoalSound: [res_path + 'win.mp3', res_path + 'win.ogg'], + failureSound: [res_path + 'failure.mp3', res_path + 'failure.ogg'], + obstacleSound: [res_path + 'obstacle.mp3', res_path + 'obstacle.ogg'], + + //Never called + obstacleIdle: res_path + 'obstacle.png', + obstacleAnimation: '', + + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze map +Maze.map = json.map.layout[0]; +// The special cells (flowers, honey and cloud) +Maze.mapCells = json.map.specialCells; + +// Set the remainingValue fields and other options +for (var kind in Maze.mapCells) { //For each kind of cell + if(kind != "cloud"){ + for(var cell of Maze.mapCells[kind]){ + if(cell.optional){ //If this cell is optional, generate a random number to remove it or not + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, remove it + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + } + } + else if(cell.or){ //If this cell is either this kind or another + var val = Math.round(Math.random()); + if(val == 0) //0, let the cell + cell.remainingValue = cell.value; + else{ //1, change the type + cell.remainingValue = cell.value; + var index = Maze.mapCells[kind].indexOf(cell); + if (index > -1) + Maze.mapCells[kind].splice(index, 1); + Maze.mapCells[cell.or].push(cell) + } + } + else + cell.remainingValue = cell.value; + } + } +} + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; +Maze.FIRSTMOVE = true; //On first move, we need to reveal + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { //Use an image as background + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + // Draw the tiles making up the maze map. + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add specific cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + var cellIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + cellIcon.setAttribute('id', 'obstacle' + obsId); + cellIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + cellIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN[kind]); + cellIcon.setAttribute('x', + Maze.SQUARE_SIZE * (cell.x + 0.5) - + cellIcon.getAttribute('width') / 2); + cellIcon.setAttribute('y', + Maze.SQUARE_SIZE * (cell.y + 0.9) - + cellIcon.getAttribute('height')); + svg.appendChild(cellIcon); + if(kind == "cloud"){ + cell.id = obsId; + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winGoalSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.failureSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + //Replace the clouds + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the image + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloud"]); + } + + //Add the remaining value on the special cells + for (var kind in Maze.mapCells) { //For each kind of cell + for(var cell of Maze.mapCells[kind]){ + if(kind == "purpleFlower"){ //When resetted, purple flowers get a question mark + Maze.updateOrCreateText_(cell.y, cell.x, "?"); + } + else{ + cell.remainingValue = cell.value; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); + } + } + } + Maze.FIRSTMOVE = true; //Reset the first move +}; +/** +* Reveal any hidden information (remove clouds and set purple flower values) +*/ +Maze.reveal = function(){ + for(var cell of Maze.mapCells["purpleFlower"]){ + var min = cell.range[0]; + var max = cell.range[1]; + var val = Math.floor(Math.random() * (max - min + 1)) + min;; //Pick a random number in range + cell.value = val; + cell.remainingValue = val; + Maze.updateOrCreateText_(cell.y, cell.x, cell.value); //Set it as the value + } + for(var cell of Maze.mapCells["cloud"]){ + //Play the animation + var cellIcon = document.getElementById("obstacle"+cell.id); //Get the element + //Change the value to the animation + cellIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN["cloudAnimation"]); + //Show the correct number on the underneath cell (redflower or honey) + for(var under of Maze.mapCells["redFlower"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + for(var under of Maze.mapCells["honey"]){ + if (under.x == cell.x && under.y == cell.y){ + Maze.updateOrCreateText_(under.y, under.x, under.value); + } + } + } +} + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + if(Maze.FIRSTMOVE){ + Maze.reveal(); + Maze.FIRSTMOVE = false; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + case 'nectar': + console.log("todo nectar"); // TODO + break; + case 'honey': + console.log("todo honey"); // TODO + break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert('Vous avez heurté un obstacle !'); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert('Vous avez heurté un mur !'); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + return; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +/** + * Create SVG text element for given cell + * @param {number} row + * @param {number} col + * @param {string} text + */ +Maze.updateOrCreateText_ = function(row, col, text) { + var pegmanElement = document.getElementById('pegman'); + var svg = document.getElementById('blocklySvgZone'); + var id = 'cellText' + row + col; + var textElement = document.getElementById(id); + + if (!textElement) { + // Create text. + var hPadding = 2; + var vPadding = 2; + textElement = document.createElementNS(Blockly.SVG_NS, 'text'); + // Position text just inside the bottom right corner. + textElement.setAttribute('x', (col + 1) * Maze.SQUARE_SIZE - hPadding); + textElement.setAttribute('y', (row + 1) * Maze.SQUARE_SIZE - vPadding); + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('font-size', '16px'); + textElement.setAttribute('font-weight', 'bold'); + textElement.setAttribute('fill', 'white'); + textElement.setAttribute('stroke', 'black'); + textElement.setAttribute('stroke-width', 1); + textElement.setAttribute('id', id); + textElement.appendChild(document.createTextNode('')); + svg.insertBefore(textElement, pegmanElement); + } + + textElement.firstChild.nodeValue = text; + return textElement; +}; + +Maze.getNectar = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isFlower = false; + for (var kind in Maze.mapCells) { //For each kind of cell + for(cell of Maze.mapCells[kind]){ + if (cell.x == x && cell.y == y && kind != 'cloud' && kind != 'honey') { + isFlower = true; + break; + } + } + if(isFlower) break; + } + if (!isFlower || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas récolter du nectar ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +//Helper functions +var nectarRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var honeyRemaining = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell = null; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + cell = current; + break; + } + } + if(cell == null) + return 0; + else + return cell.remainingValue; +} + +var isOnFlower = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["redFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } + for(var current of Maze.mapCells["purpleFlower"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +var isOnHoney = function(){ + var x = Maze.pegmanX; + var y = Maze.pegmanY; + + for(var current of Maze.mapCells["honey"]){ + if(x == current.x && y == current.y) { + return true; + } + } +} + +Maze.get2Nectar = function(id) { + Maze.getNectar(id); + Maze.getNectar(id); +}; + +Maze.makeHoney = function(id) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + var cell; + + var isHoney = false; + for(cell of Maze.mapCells["honey"]){ + if (cell.x == x && cell.y == y) { + isHoney = true; + break; + } + } + if (! isHoney || cell.remainingValue <= 0) { + BlocklyTaskInterpreter.alert("Vous ne pouvez pas fabriquer du miel ici !"); + Maze.log.push(['finish', id]); + return; + } + cell.remainingValue--; + Maze.updateOrCreateText_(y, x, cell.remainingValue); +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/avatar.png new file mode 100644 index 0000000..9734d20 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/background.png b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/background.png new file mode 100644 index 0000000..43fdf7b Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/cloud.png b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/cloud.png new file mode 100644 index 0000000..f5abefa Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/cloud.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/cloud_hide.gif b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/cloud_hide.gif new file mode 100644 index 0000000..26002e9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/cloud_hide.gif differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/failure.mp3 new file mode 100644 index 0000000..d155f29 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/failure.ogg new file mode 100644 index 0000000..542cd44 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/failure_avatar.png new file mode 100644 index 0000000..358f887 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/getNectar.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/getNectar.mp3 new file mode 100644 index 0000000..7404e5e Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/getNectar.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/getNectar.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/getNectar.ogg new file mode 100644 index 0000000..1375c87 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/getNectar.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/honey.png b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/honey.png new file mode 100644 index 0000000..2696b91 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/honey.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/idle_avatar.gif b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/idle_avatar.gif new file mode 100644 index 0000000..043f3b3 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/idle_avatar.gif differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/makeHoney.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/makeHoney.mp3 new file mode 100644 index 0000000..b30818a Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/makeHoney.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/makeHoney.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/makeHoney.ogg new file mode 100644 index 0000000..518610f Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/makeHoney.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/move_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/move_avatar.png new file mode 100644 index 0000000..d9e807e Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/move_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/obstacle.mp3 new file mode 100644 index 0000000..4fea856 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/obstacle.ogg new file mode 100644 index 0000000..a400498 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/obstacle.png b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/obstacle.png new file mode 100644 index 0000000..6394d97 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/obstacle.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/purpleFlower.png b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/purpleFlower.png new file mode 100644 index 0000000..357fd08 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/purpleFlower.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/redFlower.png b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/redFlower.png new file mode 100644 index 0000000..977cb4e Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/redFlower.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/small_static_avatar.png new file mode 100644 index 0000000..1a6e3b2 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/start.mp3 new file mode 100644 index 0000000..49bb7f8 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/start.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/start.ogg new file mode 100644 index 0000000..87821ef Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/static_avatar.png new file mode 100644 index 0000000..38c93d1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/tiles.png b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/tiles.png new file mode 100644 index 0000000..e084a34 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/tree.png b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/tree.png new file mode 100644 index 0000000..1a0c2c0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/tree.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/wall.gif b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/wall.gif new file mode 100644 index 0000000..1c029c5 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/wall.gif differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/wall_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/wall_avatar.png new file mode 100644 index 0000000..cb31b31 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/wall_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/win.mp3 new file mode 100644 index 0000000..7d01e15 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/win.ogg b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/win.ogg new file mode 100644 index 0000000..0b60464 Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/win_avatar.png new file mode 100644 index 0000000..5f5d2ce Binary files /dev/null and b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze_config.json b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze_config.json new file mode 100644 index 0000000..15f4428 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_05/public/maze_config.json @@ -0,0 +1,65 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 0, 0, 0, 0], + [0, 0, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 2, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "specialCells":{ + "honey":[ + { + "x":2, + "y":2, + "value":1 + }, + { + "x":4, + "y":4, + "value":1 + } + ], + "redFlower":[], + "purpleFlower":[ + { + "x":3, + "y":2, + "range":[1,3] + }, + { + "x":5, + "y":4, + "range":[1,3] + } + ], + "cloud":[] + }, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "OBSTACLE": 3, + "STARTANDFINISH": 4 + }, + "startDirection":"WEST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "redFlower":"redFlower.png", + "purpleFlower":"purpleFlower.png", + "honey":"honey.png", + "cloud":"cloud.png", + "cloudAnimation":"cloud_hide.gif", + "obstacleScale":1.0, + "background":"background.png" + } +} diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/run b/Cours 1 Code.org/Lecon 7/Maze_bee_05/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_05/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/student/maze.tpl.py b/Cours 1 Code.org/Lecon 7/Maze_bee_05/student/maze.tpl.py new file mode 100644 index 0000000..529acf1 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_05/student/maze.tpl.py @@ -0,0 +1,340 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import random +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class IsNotAFlowerException(Exception): + pass + +class IsNotHoneyException(Exception): + pass + +class EmptyFlowerException(Exception): + pass + +class EmptyHoneyException(Exception): + pass + +MAP = data["map"]["layout"][0] + +toRemove = [] +toAdd = [] + +MAP_CELLS = data["map"]["specialCells"] +for kind in MAP_CELLS: # Add the random value to the purple flowers + for item in MAP_CELLS[kind]: + if "optional" in item: # May remove item + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Remove + toRemove.append((item,kind)) + elif "or" in item: # May switch kind + val = random.randrange(0, 2) # Random number + if val == 0: # Keep + item["remainingValue"] = item["value"] + else: # Switch + toRemove.append((item,kind)) + toAdd.append((item, item["or"])) + if(kind == "purpleFlower"): + min = item["range"][0] + max = item["range"][1] + val = random.randrange(min, max+1) + item["value"] = val + if(kind != "cloud"): + item["remainingValue"] = item["value"] + +#Remove and add items after the loop +for (item,kind) in toRemove: + MAP_CELLS[kind].remove(item) +for (item,kind) in toAdd: + MAP_CELLS[kind].append(item) + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def moveBackward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathBackward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] - MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] - MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + sumTotal = 0 + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud": # Except clouds + for item in MAP_CELLS[cellType]: + sumTotal += item["remainingValue"] # Sum remaining values + + if sumTotal == 0: + return True + else: + return False + + +def notDone(): + return not isDone() + +def getNectar(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isFlower = False + for cellType in MAP_CELLS : # All special cells + if cellType != "cloud" and cellType != "honey": # Only flowers + for cell in MAP_CELLS[cellType]: + if cell['x'] == x and cell['y'] == y: + isFlower = True + break + + if not isFlower: + raise IsNotAFlowerException() + + if cell['remainingValue'] <= 0: + raise EmptyFlowerException() + + cell['remainingValue'] -= 1 + +def nectarRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def honeyRemaining(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + for cell in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + cell = current + break + if(cell == None): + return 0 + else: + return cell['remainingValue'] + +def isOnFlower(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for current in MAP_CELLS["redFlower"]: + if(x == current['x'] and y == current['y']): + return True + for current in MAP_CELLS["purpleFlower"]: + if(x == current['x'] and y == current['y']): + return True + +def isOnHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + + for cell in MAP_CELLS["honey"]: + if(x == current['x'] and y == current['y']): + return True + + +def get2Nectar(): + getNectar() + getNectar() + +def makeHoney(): + x = PLAYER_POSITION['x'] + y = PLAYER_POSITION['y'] + cell = None + + isHoney = False + for cell in MAP_CELLS["honey"]: #Only honey cells + if (cell['x'] == x and cell['y'] == y): + isHoney = True + break + + if not isHoney: + raise IsNotHoneyException() + + if cell['remainingValue'] <= 0: + raise EmptyHoneyException() + + cell['remainingValue'] -= 1 + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Pour terminer l'exercice, il faut que vous ayez accumulé toutes les ressources.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") +except IsNotAFlowerException: + print("Votre personnage essaie de récolter du nectar sur un endroit qui n'est pas une fleur.") +except EmptyFlowerException: + print("Votre personnage essaie de récolter du nectar sur une fleur qui n'a plus de nectar.") +except IsNotHoneyException: + print("Votre personnage essaie de fabriquer du miel sur un endroit qui n'est pas une ruche.") +except EmptyHoneyException: + print("Votre personnage essaie de fabriquer du miel dans une ruche qui est pleine.") +except Exception: + print("Votre code n'a pas pu être testé correctement. Il y a un problème avec vos blocs !") diff --git a/Cours 1 Code.org/Lecon 7/Maze_bee_05/task.yaml b/Cours 1 Code.org/Lecon 7/Maze_bee_05/task.yaml new file mode 100644 index 0000000..1bdd2c0 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/Maze_bee_05/task.yaml @@ -0,0 +1,180 @@ +accessible: true +author: Florian Thuin +context: | + Écris à l'intérieur de la fonction «Vérifier la fleur et fabriquer du miel». Ta fonction doit vérifier si la fleur pourpre a 1, 2 ou 3 nectars et recueillir l'ensemble du nectar. Ensuite, avance et fabrique du miel. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 5 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + moveForward + + + turnLeft + + + turnRight + + + + + + + + + + + ??? + + + + + + nectarRemaining + > + 0 + + + + options: + zoom: + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + minScale: 0.3 + startScale: 1.0 + wheel: false + grid: + length: 3 + snap: true + spacing: 20 + colour: '#ccc' + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + trashcan: true + css: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + + + 2 + + + + + moveForward + + + turnRight + + + moveForward + + + turnLeft + + + + + + turnRight + + + moveForward + + + turnLeft + + + + + + + + + + + + + + + + + + + Vérifier fleur frabriquer miel + Décrire cette fonction… + + + header: |4+ + +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + description: '' + type: 0 + visible: true + name: Boucles répéter X fois + id: '1' + '1': + id: '4' + description: '' + type: 0 + visible: true + name: Condition + '2': + type: 0 + description: '' + name: Si/Sinon + id: '5' + visible: true + '3': + description: Fait partie du parcours normal + type: 2 + name: Normal + visible: false + id: '' + '4': + type: 2 + description: Fait partie du parcours challenge + name: Challenge + visible: false + id: '' + '5': + visible: true + description: Fait partie de la leçon 7 + name: Lecon 7 + type: 2 + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 7/course.yaml b/Cours 1 Code.org/Lecon 7/course.yaml new file mode 100644 index 0000000..8c22681 --- /dev/null +++ b/Cours 1 Code.org/Lecon 7/course.yaml @@ -0,0 +1,18 @@ +accessible: true +name: Cours 7 - Collect and construct +description: '' +admins: +- '' +tutors: [] +groups_student_choice: false +use_classrooms: true +allow_unregister: true +allow_preview: false +registration: true +registration_password: null +registration_ac: null +registration_ac_list: +- '' +is_lti: false +lti_keys: {} +lti_send_back_grade: false diff --git a/Cours 1/Lecon1/09_maze/public/blocks.js b/Cours 1 Code.org/Lecon 8/Maze_01/public/blocks.js similarity index 99% rename from Cours 1/Lecon1/09_maze/public/blocks.js rename to Cours 1 Code.org/Lecon 8/Maze_01/public/blocks.js index 24a7f5f..f71cdc6 100644 --- a/Cours 1/Lecon1/09_maze/public/blocks.js +++ b/Cours 1 Code.org/Lecon 8/Maze_01/public/blocks.js @@ -212,7 +212,7 @@ Blockly.Blocks['maze_forever'] = { this.setColour(Maze.Blocks.LOOPS_HUE); this.appendDummyInput() .appendField("répéter jusqu'à") - .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 30, 36)); + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); this.appendStatementInput('DO') .appendField("faire"); this.setPreviousStatement(true); diff --git a/Cours 1/Lecon1/12_maze/public/interpreter.js b/Cours 1 Code.org/Lecon 8/Maze_01/public/interpreter.js similarity index 100% rename from Cours 1/Lecon1/12_maze/public/interpreter.js rename to Cours 1 Code.org/Lecon 8/Maze_01/public/interpreter.js diff --git a/Cours 1/Lecon1/01_maze/public/maze.js b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze.js similarity index 91% rename from Cours 1/Lecon1/01_maze/public/maze.js rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze.js index 6de35d8..248e96e 100644 --- a/Cours 1/Lecon1/01_maze/public/maze.js +++ b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze.js @@ -26,23 +26,37 @@ var task_directory_path = window.location.pathname + "/"; window.Maze = {}; -Maze.MAX_BLOCKS = Infinity; +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); // Crash type constants. Maze.CRASH_STOP = 1; Maze.CRASH_SPIN = 2; Maze.CRASH_FALL = 3; +var visuals_directory_path = task_directory_path+"maze/" + Maze.SKIN = { - sprite: task_directory_path + 'maze/avatar.png', - tiles: task_directory_path + 'maze/tiles.png', - marker: task_directory_path + 'maze/goalIdle.gif', - goalAnimation: task_directory_path + 'maze/goal.gif', - obstacleIdle: task_directory_path + 'maze/obstacleIdle.gif', - obstacleAnimation: task_directory_path + 'maze/obstacle.gif', - obstacleScale: 1.4, - background: task_directory_path + 'maze/background.png', - graph: false, + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, look: '#000', obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], @@ -53,36 +67,17 @@ Maze.SKIN = { /** * Milliseconds between each animation frame. */ -window.stepSpeed = 250; +window.stepSpeed = json.map.animationSpeed; /** * The types of squares in the maze, which is represented * as a 2D array of SquareType values. * @enum {number} */ -Maze.SquareType = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4, - STARTANDFINISH: 5 -}; +Maze.SquareType = json.map.squareType; -// The maze square constants defined above are inlined here -// for ease of reading and writing the static mazes. -Maze.map = - // Level 1. - [ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 2, 1, 1, 3, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0] - ]; +// The maze square constants +Maze.map = json.map.layout[0]; /** * Measure maze dimensions and set sizes. @@ -92,9 +87,9 @@ Maze.map = */ Maze.ROWS = Maze.map.length; Maze.COLS = Maze.map[0].length; -Maze.SQUARE_SIZE = 50; -Maze.PEGMAN_HEIGHT = 52; -Maze.PEGMAN_WIDTH = 49; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; @@ -131,7 +126,7 @@ Maze.result = Maze.ResultType.UNSET; /** * Starting direction. */ -Maze.startDirection = Maze.DirectionType.EAST; +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; /** * PIDs of animation tasks currently executing. @@ -164,6 +159,24 @@ Maze.tile_SHAPES = { 'null4': [1, 3] }; +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + /** * Create and layout all the nodes for the path, scenery, Pegman, and goal. */ @@ -349,9 +362,9 @@ Maze.init = function() { // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.winSound, 'win'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.crashSound, 'fail'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.obstacleSound, 'obstacle'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); // Not really needed, there are no user-defined functions or variables. Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); @@ -540,7 +553,7 @@ Maze.schedule = function(startPos, endPos) { var finishIcon = document.getElementById('finish'); if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); - Blockly.getMainWorkspace().playAudio('win', 0.3); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); } }, window.stepSpeed * 4)); } @@ -579,7 +592,7 @@ Maze.scheduleFail = function(forward) { if (squareType === Maze.SquareType.OBSTACLE) { BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); // Play the sound - Blockly.getMainWorkspace().playAudio('obstacle'); + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); // Play the animation var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); @@ -602,7 +615,7 @@ Maze.scheduleFail = function(forward) { }, window.stepSpeed * 2)); Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('failure'); + Blockly.getMainWorkspace().getAudioManager().play('failure'); }, window.stepSpeed)); } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); @@ -613,7 +626,7 @@ Maze.scheduleFail = function(forward) { Maze.displayPegman(Maze.pegmanX + deltaX, Maze.pegmanY + deltaY, direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); Maze.pidList.push(setTimeout(function() { Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, @@ -623,7 +636,7 @@ Maze.scheduleFail = function(forward) { Maze.displayPegman(Maze.pegmanX + deltaX, Maze.pegmanY + deltaY, direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); }, window.stepSpeed * 2)); Maze.pidList.push(setTimeout(function() { Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); @@ -641,7 +654,7 @@ Maze.scheduleFail = function(forward) { acceleration = 0.01; } Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('fail', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); }, window.stepSpeed * 2)); var setPosition = function(n) { return function() { @@ -670,7 +683,7 @@ Maze.scheduleFinish = function(sound) { var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); if (sound) { - Blockly.getMainWorkspace().playAudio('win', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); } window.stepSpeed = 250; // Slow down victory animation a bit. Maze.pidList.push(setTimeout(function() { diff --git a/Cours 1/Lecon1/12_maze/public/maze/avatar.png b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/avatar.png similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/avatar.png rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/avatar.png diff --git a/Cours 1/Lecon1/12_maze/public/maze/background.png b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/background.png similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/background.png rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/background.png diff --git a/Cours 1/Lecon1/12_maze/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/failure.mp3 similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/failure.mp3 rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/failure.mp3 diff --git a/Cours 1/Lecon1/12_maze/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/failure.ogg similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/failure.ogg rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/failure.ogg diff --git a/Cours 1/Lecon1/12_maze/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/failure_avatar.png similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/failure_avatar.png rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/failure_avatar.png diff --git a/Cours 1/Lecon1/12_maze/public/maze/goal.gif b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/goal.gif similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/goal.gif rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/goal.gif diff --git a/Cours 1/Lecon1/12_maze/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/goalIdle.gif similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/goalIdle.gif rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/goalIdle.gif diff --git a/Cours 1/Lecon1/12_maze/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/maze_forever.gif similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/maze_forever.gif rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/maze_forever.gif diff --git a/Cours 1/Lecon1/12_maze/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/obstacle.gif similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/obstacle.gif rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/obstacle.gif diff --git a/Cours 1/Lecon1/12_maze/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/obstacle.mp3 similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/obstacle.mp3 rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/obstacle.mp3 diff --git a/Cours 1/Lecon1/12_maze/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/obstacle.ogg similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/obstacle.ogg rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/obstacle.ogg diff --git a/Cours 1/Lecon1/12_maze/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/obstacleIdle.gif similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/obstacleIdle.gif rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/obstacleIdle.gif diff --git a/Cours 1/Lecon1/12_maze/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/small_static_avatar.png similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/small_static_avatar.png rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/small_static_avatar.png diff --git a/Cours 1/Lecon1/12_maze/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/start.mp3 similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/start.mp3 rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/start.mp3 diff --git a/Cours 1/Lecon1/12_maze/public/maze/start.ogg b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/start.ogg similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/start.ogg rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/start.ogg diff --git a/Cours 1/Lecon1/12_maze/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/static_avatar.png similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/static_avatar.png rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/static_avatar.png diff --git a/Cours 1/Lecon1/12_maze/public/maze/tiles.png b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/tiles.png similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/tiles.png rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/tiles.png diff --git a/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/wall.mp3 new file mode 100755 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/wall.ogg new file mode 100755 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/wall.ogg differ diff --git a/Cours 1/Lecon1/12_maze/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/win.mp3 similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/win.mp3 rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/win.mp3 diff --git a/Cours 1/Lecon1/12_maze/public/maze/win.ogg b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/win.ogg similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/win.ogg rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/win.ogg diff --git a/Cours 1/Lecon1/12_maze/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze/win_avatar.png similarity index 100% rename from Cours 1/Lecon1/12_maze/public/maze/win_avatar.png rename to Cours 1 Code.org/Lecon 8/Maze_01/public/maze/win_avatar.png diff --git a/Cours 1 Code.org/Lecon 8/Maze_01/public/maze_config.json b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze_config.json new file mode 100644 index 0000000..c0c26c7 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_01/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 2, 1, 1, 1, 1, 3, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 8/Maze_01/run b/Cours 1 Code.org/Lecon 8/Maze_01/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_01/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 8/Maze_01/student/maze.tpl.py b/Cours 1 Code.org/Lecon 8/Maze_01/student/maze.tpl.py new file mode 100644 index 0000000..de0722d --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_01/student/maze.tpl.py @@ -0,0 +1,185 @@ +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + + +class BadPathException(Exception): + pass + +MAP = data["map"]["layout"][0] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + global PLAYER_POSITION, FINISH_POSITION + if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: + return True + else: + return False + + +def notDone(): + return not isDone() + + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Il y a une erreur dans votre code.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1 Code.org/Lecon 8/Maze_01/task.yaml b/Cours 1 Code.org/Lecon 8/Maze_01/task.yaml new file mode 100644 index 0000000..fc55741 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_01/task.yaml @@ -0,0 +1,87 @@ +accessible: true +author: Florian Thuin +context: Utilise le bloc « Répéter jusqu'à » pour amener le zombie au tournesol. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + output: '2' + memory: '100' + time: '30' +name: Exercice 1 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + sounds: true + trashcan: true + maxBlocks: '2' + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + toolbox: |- + + + + + turnLeft + + + turnRight + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '4': + type: 0 + description: '' + name: Boucle "répéter jusqu'à" + visible: true + id: '1' + '0': + type: 2 + visible: true + name: Lecon 8 + description: '' + id: '' + '1': + description: '' + type: 2 + name: Facile + visible: false + id: '' + '2': + description: '' + name: Normal + type: 2 + visible: false + id: '' + '3': + name: Challenge + description: '' + type: 2 + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/blocks.js b/Cours 1 Code.org/Lecon 8/Maze_02/public/blocks.js new file mode 100644 index 0000000..f71cdc6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_02/public/blocks.js @@ -0,0 +1,237 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/interpreter.js b/Cours 1 Code.org/Lecon 8/Maze_02/public/interpreter.js new file mode 100644 index 0000000..f627d15 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_02/public/interpreter.js @@ -0,0 +1,55 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1/Lecon1/06_maze/public/maze.js b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze.js similarity index 91% rename from Cours 1/Lecon1/06_maze/public/maze.js rename to Cours 1 Code.org/Lecon 8/Maze_02/public/maze.js index ea6a34e..248e96e 100644 --- a/Cours 1/Lecon1/06_maze/public/maze.js +++ b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze.js @@ -24,26 +24,39 @@ "use strict"; var task_directory_path = window.location.pathname + "/"; - window.Maze = {}; -Maze.MAX_BLOCKS = Infinity; +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); // Crash type constants. Maze.CRASH_STOP = 1; Maze.CRASH_SPIN = 2; Maze.CRASH_FALL = 3; +var visuals_directory_path = task_directory_path+"maze/" + Maze.SKIN = { - sprite: task_directory_path + 'maze/avatar.png', - tiles: task_directory_path + 'maze/tiles.png', - marker: task_directory_path + 'maze/goalIdle.gif', - goalAnimation: task_directory_path + 'maze/goal.gif', - obstacleIdle: task_directory_path + 'maze/obstacleIdle.gif', - obstacleAnimation: task_directory_path + 'maze/obstacle.gif', - obstacleScale: 1.4, - background: task_directory_path + 'maze/background.png', - graph: false, + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, look: '#000', obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], @@ -54,36 +67,17 @@ Maze.SKIN = { /** * Milliseconds between each animation frame. */ -window.stepSpeed = 250; +window.stepSpeed = json.map.animationSpeed; /** * The types of squares in the maze, which is represented * as a 2D array of SquareType values. * @enum {number} */ -Maze.SquareType = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4, - STARTANDFINISH: 5 -}; +Maze.SquareType = json.map.squareType; -// The maze square constants defined above are inlined here -// for ease of reading and writing the static mazes. -Maze.map = - // Level 6. - [ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 3, 0, 2, 0, 0], - [0, 0, 0, 1, 0, 1, 0, 0], - [0, 0, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 1, 4, 1, 0, 0], - [0, 0, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0] - ]; +// The maze square constants +Maze.map = json.map.layout[0]; /** * Measure maze dimensions and set sizes. @@ -93,9 +87,9 @@ Maze.map = */ Maze.ROWS = Maze.map.length; Maze.COLS = Maze.map[0].length; -Maze.SQUARE_SIZE = 50; -Maze.PEGMAN_HEIGHT = 52; -Maze.PEGMAN_WIDTH = 49; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; @@ -132,7 +126,7 @@ Maze.result = Maze.ResultType.UNSET; /** * Starting direction. */ -Maze.startDirection = Maze.DirectionType.SOUTH; +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; /** * PIDs of animation tasks currently executing. @@ -165,6 +159,24 @@ Maze.tile_SHAPES = { 'null4': [1, 3] }; +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + /** * Create and layout all the nodes for the path, scenery, Pegman, and goal. */ @@ -350,9 +362,9 @@ Maze.init = function() { // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.winSound, 'win'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.crashSound, 'fail'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.obstacleSound, 'obstacle'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); // Not really needed, there are no user-defined functions or variables. Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); @@ -541,7 +553,7 @@ Maze.schedule = function(startPos, endPos) { var finishIcon = document.getElementById('finish'); if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); - Blockly.getMainWorkspace().playAudio('win', 0.3); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); } }, window.stepSpeed * 4)); } @@ -573,14 +585,14 @@ Maze.scheduleFail = function(forward) { deltaY = -deltaY; } - var targetX = Maze.pegmanX + deltaX * 2; - var targetY = Maze.pegmanY + deltaY * 2; + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; var squareType = Maze.map[targetY][targetX]; if (squareType === Maze.SquareType.OBSTACLE) { BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); // Play the sound - Blockly.getMainWorkspace().playAudio('obstacle'); + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); // Play the animation var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); @@ -603,7 +615,7 @@ Maze.scheduleFail = function(forward) { }, window.stepSpeed * 2)); Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('failure'); + Blockly.getMainWorkspace().getAudioManager().play('failure'); }, window.stepSpeed)); } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); @@ -614,7 +626,7 @@ Maze.scheduleFail = function(forward) { Maze.displayPegman(Maze.pegmanX + deltaX, Maze.pegmanY + deltaY, direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); Maze.pidList.push(setTimeout(function() { Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, @@ -624,7 +636,7 @@ Maze.scheduleFail = function(forward) { Maze.displayPegman(Maze.pegmanX + deltaX, Maze.pegmanY + deltaY, direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); }, window.stepSpeed * 2)); Maze.pidList.push(setTimeout(function() { Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); @@ -642,7 +654,7 @@ Maze.scheduleFail = function(forward) { acceleration = 0.01; } Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('fail', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); }, window.stepSpeed * 2)); var setPosition = function(n) { return function() { @@ -671,7 +683,7 @@ Maze.scheduleFinish = function(sound) { var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); if (sound) { - Blockly.getMainWorkspace().playAudio('win', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); } window.stepSpeed = 250; // Slow down victory animation a bit. Maze.pidList.push(setTimeout(function() { @@ -827,11 +839,6 @@ Maze.move = function(direction, id) { break; } Maze.log.push([command, id]); - - // TODO maybe add this - // if (Maze.shouldCheckSuccessOnMove()) { - // Maze.checkSuccess(); - // } }; /** diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/avatar.png b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/avatar.png new file mode 100755 index 0000000..31e1f92 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/background.png b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/background.png new file mode 100755 index 0000000..c2a80aa Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/failure.mp3 new file mode 100755 index 0000000..d3d73b9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/failure.ogg new file mode 100755 index 0000000..d7883c1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/failure_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/goal.gif b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/goal.gif new file mode 100755 index 0000000..6a4ea6b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/goal.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/goalIdle.gif new file mode 100755 index 0000000..02dab59 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/goalIdle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/maze_forever.gif new file mode 100755 index 0000000..02dab59 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/maze_forever.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/obstacle.gif new file mode 100755 index 0000000..1fa6dae Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/obstacle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/obstacle.mp3 new file mode 100755 index 0000000..b3ddb3a Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/obstacle.ogg new file mode 100755 index 0000000..e320903 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/obstacleIdle.gif new file mode 100755 index 0000000..aa27ffc Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/obstacleIdle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/small_static_avatar.png new file mode 100755 index 0000000..439b36b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/start.mp3 new file mode 100755 index 0000000..bdd6ea6 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/start.ogg b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/start.ogg new file mode 100755 index 0000000..009fe7d Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/static_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/tiles.png b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/tiles.png new file mode 100755 index 0000000..b809691 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/wall.mp3 new file mode 100755 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/wall.ogg new file mode 100755 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/win.mp3 new file mode 100755 index 0000000..e768c1b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/win.ogg b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/win.ogg new file mode 100755 index 0000000..64adef0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/win_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/public/maze_config.json b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze_config.json new file mode 100644 index 0000000..b98aaab --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_02/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 3, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 2, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 4, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/run b/Cours 1 Code.org/Lecon 8/Maze_02/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_02/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/student/maze.tpl.py b/Cours 1 Code.org/Lecon 8/Maze_02/student/maze.tpl.py new file mode 100644 index 0000000..de0722d --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_02/student/maze.tpl.py @@ -0,0 +1,185 @@ +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + + +class BadPathException(Exception): + pass + +MAP = data["map"]["layout"][0] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + global PLAYER_POSITION, FINISH_POSITION + if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: + return True + else: + return False + + +def notDone(): + return not isDone() + + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Il y a une erreur dans votre code.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1 Code.org/Lecon 8/Maze_02/task.yaml b/Cours 1 Code.org/Lecon 8/Maze_02/task.yaml new file mode 100644 index 0000000..7e4f8ba --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_02/task.yaml @@ -0,0 +1,118 @@ +accessible: true +author: Florian Thuin +context: |- + Utilise le bloc « Si » pour me laisser décider quand tourner. + Indice: ici, tu n’as besoin que d'un bloc supplémentaire, mais regarde bien comment nous avons fait afin de pouvoir le faire toi-même la prochaine fois. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 2 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + + + turnLeft + + + turnRight + + + + + + + + + + isPathRight + + + + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + + + + + isPathLeft + + + + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + visible: true + name: 'Boucle ' + id: '1' + description: '' + '1': + id: '2' + description: '' + type: 0 + visible: true + name: Condition + '2': + description: '' + name: Lecon 8 + type: 2 + visible: true + id: '' + '3': + name: Facile + description: '' + type: 2 + visible: false + id: '' + '4': + type: 2 + description: '' + name: Normal + visible: false + id: '' + '5': + description: '' + name: Challenge + type: 2 + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/blocks.js b/Cours 1 Code.org/Lecon 8/Maze_03/public/blocks.js new file mode 100644 index 0000000..f71cdc6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_03/public/blocks.js @@ -0,0 +1,237 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/interpreter.js b/Cours 1 Code.org/Lecon 8/Maze_03/public/interpreter.js new file mode 100644 index 0000000..f627d15 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_03/public/interpreter.js @@ -0,0 +1,55 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1/Lecon1/04_maze/public/maze.js b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze.js similarity index 91% rename from Cours 1/Lecon1/04_maze/public/maze.js rename to Cours 1 Code.org/Lecon 8/Maze_03/public/maze.js index 198e48c..248e96e 100644 --- a/Cours 1/Lecon1/04_maze/public/maze.js +++ b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze.js @@ -24,26 +24,39 @@ "use strict"; var task_directory_path = window.location.pathname + "/"; - window.Maze = {}; -Maze.MAX_BLOCKS = Infinity; +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); // Crash type constants. Maze.CRASH_STOP = 1; Maze.CRASH_SPIN = 2; Maze.CRASH_FALL = 3; +var visuals_directory_path = task_directory_path+"maze/" + Maze.SKIN = { - sprite: task_directory_path + 'maze/avatar.png', - tiles: task_directory_path + 'maze/tiles.png', - marker: task_directory_path + 'maze/goalIdle.gif', - goalAnimation: task_directory_path + 'maze/goal.gif', - obstacleIdle: task_directory_path + 'maze/obstacleIdle.gif', - obstacleAnimation: task_directory_path + 'maze/obstacle.gif', - obstacleScale: 1.4, - background: task_directory_path + 'maze/background.png', - graph: false, + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, look: '#000', obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], @@ -54,36 +67,17 @@ Maze.SKIN = { /** * Milliseconds between each animation frame. */ -window.stepSpeed = 250; +window.stepSpeed = json.map.animationSpeed; /** * The types of squares in the maze, which is represented * as a 2D array of SquareType values. * @enum {number} */ -Maze.SquareType = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4, - STARTANDFINISH: 5 -}; +Maze.SquareType = json.map.squareType; -// The maze square constants defined above are inlined here -// for ease of reading and writing the static mazes. -Maze.map = - // Level 4. - [ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 1, 4, 0, 0, 0], - [0, 0, 0, 1, 0, 0, 0, 0], - [0, 2, 1, 1, 1, 1, 3, 0], - [0, 0, 0, 4, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0] - ]; +// The maze square constants +Maze.map = json.map.layout[0]; /** * Measure maze dimensions and set sizes. @@ -93,9 +87,9 @@ Maze.map = */ Maze.ROWS = Maze.map.length; Maze.COLS = Maze.map[0].length; -Maze.SQUARE_SIZE = 50; -Maze.PEGMAN_HEIGHT = 52; -Maze.PEGMAN_WIDTH = 49; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; @@ -132,7 +126,7 @@ Maze.result = Maze.ResultType.UNSET; /** * Starting direction. */ -Maze.startDirection = Maze.DirectionType.EAST; +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; /** * PIDs of animation tasks currently executing. @@ -165,6 +159,24 @@ Maze.tile_SHAPES = { 'null4': [1, 3] }; +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + /** * Create and layout all the nodes for the path, scenery, Pegman, and goal. */ @@ -350,9 +362,9 @@ Maze.init = function() { // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.winSound, 'win'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.crashSound, 'fail'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.obstacleSound, 'obstacle'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); // Not really needed, there are no user-defined functions or variables. Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); @@ -541,7 +553,7 @@ Maze.schedule = function(startPos, endPos) { var finishIcon = document.getElementById('finish'); if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); - Blockly.getMainWorkspace().playAudio('win', 0.3); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); } }, window.stepSpeed * 4)); } @@ -573,14 +585,14 @@ Maze.scheduleFail = function(forward) { deltaY = -deltaY; } - var targetX = Maze.pegmanX + deltaX * 2; - var targetY = Maze.pegmanY + deltaY * 2; + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; var squareType = Maze.map[targetY][targetX]; if (squareType === Maze.SquareType.OBSTACLE) { BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); // Play the sound - Blockly.getMainWorkspace().playAudio('obstacle'); + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); // Play the animation var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); @@ -603,7 +615,7 @@ Maze.scheduleFail = function(forward) { }, window.stepSpeed * 2)); Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('failure'); + Blockly.getMainWorkspace().getAudioManager().play('failure'); }, window.stepSpeed)); } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); @@ -614,7 +626,7 @@ Maze.scheduleFail = function(forward) { Maze.displayPegman(Maze.pegmanX + deltaX, Maze.pegmanY + deltaY, direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); Maze.pidList.push(setTimeout(function() { Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, @@ -624,7 +636,7 @@ Maze.scheduleFail = function(forward) { Maze.displayPegman(Maze.pegmanX + deltaX, Maze.pegmanY + deltaY, direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); }, window.stepSpeed * 2)); Maze.pidList.push(setTimeout(function() { Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); @@ -642,7 +654,7 @@ Maze.scheduleFail = function(forward) { acceleration = 0.01; } Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('fail', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); }, window.stepSpeed * 2)); var setPosition = function(n) { return function() { @@ -671,7 +683,7 @@ Maze.scheduleFinish = function(sound) { var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); if (sound) { - Blockly.getMainWorkspace().playAudio('win', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); } window.stepSpeed = 250; // Slow down victory animation a bit. Maze.pidList.push(setTimeout(function() { @@ -827,11 +839,6 @@ Maze.move = function(direction, id) { break; } Maze.log.push([command, id]); - - // TODO maybe add this - // if (Maze.shouldCheckSuccessOnMove()) { - // Maze.checkSuccess(); - // } }; /** diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/avatar.png b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/avatar.png new file mode 100755 index 0000000..31e1f92 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/background.png b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/background.png new file mode 100755 index 0000000..c2a80aa Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/failure.mp3 new file mode 100755 index 0000000..d3d73b9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/failure.ogg new file mode 100755 index 0000000..d7883c1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/failure_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/goal.gif b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/goal.gif new file mode 100755 index 0000000..6a4ea6b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/goal.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/goalIdle.gif new file mode 100755 index 0000000..02dab59 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/goalIdle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/maze_forever.gif new file mode 100755 index 0000000..02dab59 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/maze_forever.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/obstacle.gif new file mode 100755 index 0000000..1fa6dae Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/obstacle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/obstacle.mp3 new file mode 100755 index 0000000..b3ddb3a Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/obstacle.ogg new file mode 100755 index 0000000..e320903 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/obstacleIdle.gif new file mode 100755 index 0000000..aa27ffc Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/obstacleIdle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/small_static_avatar.png new file mode 100755 index 0000000..439b36b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/start.mp3 new file mode 100755 index 0000000..bdd6ea6 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/start.ogg b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/start.ogg new file mode 100755 index 0000000..009fe7d Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/static_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/tiles.png b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/tiles.png new file mode 100755 index 0000000..b809691 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/wall.mp3 new file mode 100755 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/wall.ogg new file mode 100755 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/win.mp3 new file mode 100755 index 0000000..e768c1b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/win.ogg b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/win.ogg new file mode 100755 index 0000000..64adef0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/win_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/public/maze_config.json b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze_config.json new file mode 100644 index 0000000..3e0692c --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_03/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 4, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 1, 0, 0, 0], + [0, 2, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 1, 4, 0], + [0, 0, 0, 0, 1, 0, 0, 0], + [0, 3, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/run b/Cours 1 Code.org/Lecon 8/Maze_03/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_03/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/student/maze.tpl.py b/Cours 1 Code.org/Lecon 8/Maze_03/student/maze.tpl.py new file mode 100644 index 0000000..de0722d --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_03/student/maze.tpl.py @@ -0,0 +1,185 @@ +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + + +class BadPathException(Exception): + pass + +MAP = data["map"]["layout"][0] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + global PLAYER_POSITION, FINISH_POSITION + if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: + return True + else: + return False + + +def notDone(): + return not isDone() + + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Il y a une erreur dans votre code.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1 Code.org/Lecon 8/Maze_03/task.yaml b/Cours 1 Code.org/Lecon 8/Maze_03/task.yaml new file mode 100644 index 0000000..4912c23 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_03/task.yaml @@ -0,0 +1,106 @@ +accessible: true +author: Florian Thuin +context: Même chose que le puzzle précédent, mais tu dois te rappeler comment tu as + utilisé le bloc «Si» et le bloc « Répéter » ensemble. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 3 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + + + turnLeft + + + turnRight + + + + + + + + + + isPathForward + + + + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + visible: true + name: 'Boucle ' + id: '1' + description: '' + '1': + id: '2' + description: '' + type: 0 + visible: true + name: Condition + '2': + description: '' + name: Challenge + type: 2 + visible: false + id: '' + '3': + name: Facile + description: '' + type: 2 + visible: false + id: '' + '4': + type: 2 + description: '' + name: Lecon 8 + visible: true + id: '' + '5': + description: '' + name: Normal + type: 2 + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/blocks.js b/Cours 1 Code.org/Lecon 8/Maze_04/public/blocks.js new file mode 100644 index 0000000..f71cdc6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_04/public/blocks.js @@ -0,0 +1,237 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/interpreter.js b/Cours 1 Code.org/Lecon 8/Maze_04/public/interpreter.js new file mode 100644 index 0000000..f627d15 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_04/public/interpreter.js @@ -0,0 +1,55 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1/Lecon1/05_maze/public/maze.js b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze.js similarity index 91% rename from Cours 1/Lecon1/05_maze/public/maze.js rename to Cours 1 Code.org/Lecon 8/Maze_04/public/maze.js index 9c4cef8..248e96e 100644 --- a/Cours 1/Lecon1/05_maze/public/maze.js +++ b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze.js @@ -24,26 +24,39 @@ "use strict"; var task_directory_path = window.location.pathname + "/"; - window.Maze = {}; -Maze.MAX_BLOCKS = Infinity; +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); // Crash type constants. Maze.CRASH_STOP = 1; Maze.CRASH_SPIN = 2; Maze.CRASH_FALL = 3; +var visuals_directory_path = task_directory_path+"maze/" + Maze.SKIN = { - sprite: task_directory_path + 'maze/avatar.png', - tiles: task_directory_path + 'maze/tiles.png', - marker: task_directory_path + 'maze/goalIdle.gif', - goalAnimation: task_directory_path + 'maze/goal.gif', - obstacleIdle: task_directory_path + 'maze/obstacleIdle.gif', - obstacleAnimation: task_directory_path + 'maze/obstacle.gif', - obstacleScale: 1.4, - background: task_directory_path + 'maze/background.png', - graph: false, + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, look: '#000', obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], @@ -54,36 +67,17 @@ Maze.SKIN = { /** * Milliseconds between each animation frame. */ -window.stepSpeed = 250; +window.stepSpeed = json.map.animationSpeed; /** * The types of squares in the maze, which is represented * as a 2D array of SquareType values. * @enum {number} */ -Maze.SquareType = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4, - STARTANDFINISH: 5 -}; +Maze.SquareType = json.map.squareType; -// The maze square constants defined above are inlined here -// for ease of reading and writing the static mazes. -Maze.map = - // Level 5. - [ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 2, 1, 1, 1, 1, 0, 0], - [0, 0, 0, 0, 0, 1, 0, 0], - [0, 0, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 4, 0, 1, 0, 0], - [0, 0, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 0, 0, 3, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0] - ]; +// The maze square constants +Maze.map = json.map.layout[0]; /** * Measure maze dimensions and set sizes. @@ -93,9 +87,9 @@ Maze.map = */ Maze.ROWS = Maze.map.length; Maze.COLS = Maze.map[0].length; -Maze.SQUARE_SIZE = 50; -Maze.PEGMAN_HEIGHT = 52; -Maze.PEGMAN_WIDTH = 49; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; @@ -132,7 +126,7 @@ Maze.result = Maze.ResultType.UNSET; /** * Starting direction. */ -Maze.startDirection = Maze.DirectionType.EAST; +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; /** * PIDs of animation tasks currently executing. @@ -165,6 +159,24 @@ Maze.tile_SHAPES = { 'null4': [1, 3] }; +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + /** * Create and layout all the nodes for the path, scenery, Pegman, and goal. */ @@ -350,9 +362,9 @@ Maze.init = function() { // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.winSound, 'win'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.crashSound, 'fail'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.obstacleSound, 'obstacle'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); // Not really needed, there are no user-defined functions or variables. Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); @@ -541,7 +553,7 @@ Maze.schedule = function(startPos, endPos) { var finishIcon = document.getElementById('finish'); if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); - Blockly.getMainWorkspace().playAudio('win', 0.3); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); } }, window.stepSpeed * 4)); } @@ -573,14 +585,14 @@ Maze.scheduleFail = function(forward) { deltaY = -deltaY; } - var targetX = Maze.pegmanX + deltaX * 2; - var targetY = Maze.pegmanY + deltaY * 2; + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; var squareType = Maze.map[targetY][targetX]; if (squareType === Maze.SquareType.OBSTACLE) { BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); // Play the sound - Blockly.getMainWorkspace().playAudio('obstacle'); + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); // Play the animation var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); @@ -603,7 +615,7 @@ Maze.scheduleFail = function(forward) { }, window.stepSpeed * 2)); Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('failure'); + Blockly.getMainWorkspace().getAudioManager().play('failure'); }, window.stepSpeed)); } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); @@ -614,7 +626,7 @@ Maze.scheduleFail = function(forward) { Maze.displayPegman(Maze.pegmanX + deltaX, Maze.pegmanY + deltaY, direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); Maze.pidList.push(setTimeout(function() { Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, @@ -624,7 +636,7 @@ Maze.scheduleFail = function(forward) { Maze.displayPegman(Maze.pegmanX + deltaX, Maze.pegmanY + deltaY, direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); }, window.stepSpeed * 2)); Maze.pidList.push(setTimeout(function() { Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); @@ -642,7 +654,7 @@ Maze.scheduleFail = function(forward) { acceleration = 0.01; } Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('fail', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); }, window.stepSpeed * 2)); var setPosition = function(n) { return function() { @@ -671,7 +683,7 @@ Maze.scheduleFinish = function(sound) { var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); if (sound) { - Blockly.getMainWorkspace().playAudio('win', 0.5); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); } window.stepSpeed = 250; // Slow down victory animation a bit. Maze.pidList.push(setTimeout(function() { @@ -827,11 +839,6 @@ Maze.move = function(direction, id) { break; } Maze.log.push([command, id]); - - // TODO maybe add this - // if (Maze.shouldCheckSuccessOnMove()) { - // Maze.checkSuccess(); - // } }; /** diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/avatar.png b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/avatar.png new file mode 100755 index 0000000..31e1f92 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/background.png b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/background.png new file mode 100755 index 0000000..c2a80aa Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/failure.mp3 new file mode 100755 index 0000000..d3d73b9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/failure.ogg new file mode 100755 index 0000000..d7883c1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/failure_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/goal.gif b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/goal.gif new file mode 100755 index 0000000..6a4ea6b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/goal.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/goalIdle.gif new file mode 100755 index 0000000..02dab59 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/goalIdle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/maze_forever.gif new file mode 100755 index 0000000..02dab59 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/maze_forever.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/obstacle.gif new file mode 100755 index 0000000..1fa6dae Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/obstacle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/obstacle.mp3 new file mode 100755 index 0000000..b3ddb3a Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/obstacle.ogg new file mode 100755 index 0000000..e320903 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/obstacleIdle.gif new file mode 100755 index 0000000..aa27ffc Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/obstacleIdle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/small_static_avatar.png new file mode 100755 index 0000000..439b36b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/start.mp3 new file mode 100755 index 0000000..bdd6ea6 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/start.ogg b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/start.ogg new file mode 100755 index 0000000..009fe7d Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/static_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/tiles.png b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/tiles.png new file mode 100755 index 0000000..b809691 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/wall.mp3 new file mode 100755 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/wall.ogg new file mode 100755 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/win.mp3 new file mode 100755 index 0000000..e768c1b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/win.ogg b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/win.ogg new file mode 100755 index 0000000..64adef0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/win_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/public/maze_config.json b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze_config.json new file mode 100644 index 0000000..a418d93 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_04/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 4, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 1, 1, 1, 3, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 4], + [0, 1, 1, 2, 0, 1, 0, 4], + [0, 1, 0, 0, 0, 1, 0, 1], + [0, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"WEST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/run b/Cours 1 Code.org/Lecon 8/Maze_04/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_04/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/student/maze.tpl.py b/Cours 1 Code.org/Lecon 8/Maze_04/student/maze.tpl.py new file mode 100644 index 0000000..de0722d --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_04/student/maze.tpl.py @@ -0,0 +1,185 @@ +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + + +class BadPathException(Exception): + pass + +MAP = data["map"]["layout"][0] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + global PLAYER_POSITION, FINISH_POSITION + if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: + return True + else: + return False + + +def notDone(): + return not isDone() + + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Il y a une erreur dans votre code.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1 Code.org/Lecon 8/Maze_04/task.yaml b/Cours 1 Code.org/Lecon 8/Maze_04/task.yaml new file mode 100644 index 0000000..3a89e7f --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_04/task.yaml @@ -0,0 +1,100 @@ +accessible: true +author: Florian Thuin +context: Utilise le bloc "si chemin" encore une fois pour t'entraîner - ce n'est pas + très différent, mais attention aux plantes carnivores! +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + output: '2' + memory: '100' + time: '30' +name: Exercice 4 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + toolbox: |- + + + + + + + turnLeft + + + turnRight + + + + + + + + + + isPathForward + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + visible: true + name: 'Boucle ' + description: '' + id: '1' + '1': + id: '2' + description: '' + type: 0 + visible: true + name: Condition + '2': + description: '' + name: Lecon 8 + visible: true + type: 2 + id: '' + '3': + description: '' + name: Facile + type: 2 + visible: false + id: '' + '4': + type: 2 + description: '' + name: Normal + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/blocks.js b/Cours 1 Code.org/Lecon 8/Maze_05/public/blocks.js new file mode 100644 index 0000000..f71cdc6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_05/public/blocks.js @@ -0,0 +1,237 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/interpreter.js b/Cours 1 Code.org/Lecon 8/Maze_05/public/interpreter.js new file mode 100644 index 0000000..f627d15 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_05/public/interpreter.js @@ -0,0 +1,55 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze.js b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze.js new file mode 100644 index 0000000..248e96e --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze.js @@ -0,0 +1,912 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +var visuals_directory_path = task_directory_path+"maze/" + +Maze.SKIN = { + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + if (Maze.SKIN.graph) { + // Draw the grid lines. + // The grid lines are offset so that the lines pass through the centre of + // each square. A half-pixel offset is also added to as standard SVG + // practice to avoid blurriness. + var offset = Maze.SQUARE_SIZE / 2 + 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Draw the tiles making up the maze map. + + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - + finishIcon.getAttribute('width') / 2); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - + finishIcon.getAttribute('height')); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/avatar.png b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/avatar.png new file mode 100755 index 0000000..31e1f92 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/background.png b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/background.png new file mode 100755 index 0000000..c2a80aa Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/failure.mp3 new file mode 100755 index 0000000..d3d73b9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/failure.ogg new file mode 100755 index 0000000..d7883c1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/failure_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/goal.gif b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/goal.gif new file mode 100755 index 0000000..6a4ea6b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/goal.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/goalIdle.gif new file mode 100755 index 0000000..02dab59 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/goalIdle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/maze_forever.gif new file mode 100755 index 0000000..02dab59 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/maze_forever.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/obstacle.gif new file mode 100755 index 0000000..1fa6dae Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/obstacle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/obstacle.mp3 new file mode 100755 index 0000000..b3ddb3a Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/obstacle.ogg new file mode 100755 index 0000000..e320903 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/obstacleIdle.gif new file mode 100755 index 0000000..aa27ffc Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/obstacleIdle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/small_static_avatar.png new file mode 100755 index 0000000..439b36b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/start.mp3 new file mode 100755 index 0000000..bdd6ea6 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/start.ogg b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/start.ogg new file mode 100755 index 0000000..009fe7d Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/static_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/tiles.png b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/tiles.png new file mode 100755 index 0000000..b809691 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/wall.mp3 new file mode 100755 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/wall.ogg new file mode 100755 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/win.mp3 new file mode 100755 index 0000000..e768c1b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/win.ogg b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/win.ogg new file mode 100755 index 0000000..64adef0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/win_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/public/maze_config.json b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze_config.json new file mode 100644 index 0000000..70c4a48 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_05/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 4, 1, 1, 1, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], + [3, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 0, 1, 0, 0, 0, 1], + [1, 1, 1, 4, 1, 0, 0, 1], + [0, 1, 0, 1, 0, 0, 2, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/run b/Cours 1 Code.org/Lecon 8/Maze_05/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_05/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/student/maze.tpl.py b/Cours 1 Code.org/Lecon 8/Maze_05/student/maze.tpl.py new file mode 100644 index 0000000..de0722d --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_05/student/maze.tpl.py @@ -0,0 +1,185 @@ +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + + +class BadPathException(Exception): + pass + +MAP = data["map"]["layout"][0] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + global PLAYER_POSITION, FINISH_POSITION + if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: + return True + else: + return False + + +def notDone(): + return not isDone() + + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Il y a une erreur dans votre code.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1 Code.org/Lecon 8/Maze_05/task.yaml b/Cours 1 Code.org/Lecon 8/Maze_05/task.yaml new file mode 100644 index 0000000..8eecfd5 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_05/task.yaml @@ -0,0 +1,113 @@ +accessible: true +author: Florian Thuin +context: Le blocs « Si/Sinon » vérifie une condition et puis fait une chose OU une + autre. Pour amener le personnage au tournesol, essaie d'utiliser ce nouveau bloc. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 5 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + + + turnLeft + + + turnRight + + + + + + + + + + isPathForward + + + + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + + + isPathForward + + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + visible: true + name: 'Boucle ' + id: '1' + description: '' + '1': + id: '2' + description: '' + type: 0 + visible: true + name: Condition + '2': + description: '' + name: Challenge + type: 2 + visible: false + id: '' + '3': + name: Facile + description: '' + type: 2 + visible: false + id: '' + '4': + type: 2 + description: '' + name: Lecon 8 + visible: true + id: '' + '5': + description: '' + name: Normal + type: 2 + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/blocks.js b/Cours 1 Code.org/Lecon 8/Maze_06/public/blocks.js new file mode 100644 index 0000000..f71cdc6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_06/public/blocks.js @@ -0,0 +1,237 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/interpreter.js b/Cours 1 Code.org/Lecon 8/Maze_06/public/interpreter.js new file mode 100644 index 0000000..f627d15 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_06/public/interpreter.js @@ -0,0 +1,55 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze.js b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze.js new file mode 100644 index 0000000..248e96e --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze.js @@ -0,0 +1,912 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +var visuals_directory_path = task_directory_path+"maze/" + +Maze.SKIN = { + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + if (Maze.SKIN.graph) { + // Draw the grid lines. + // The grid lines are offset so that the lines pass through the centre of + // each square. A half-pixel offset is also added to as standard SVG + // practice to avoid blurriness. + var offset = Maze.SQUARE_SIZE / 2 + 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Draw the tiles making up the maze map. + + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - + finishIcon.getAttribute('width') / 2); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - + finishIcon.getAttribute('height')); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/avatar.png b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/avatar.png new file mode 100755 index 0000000..31e1f92 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/background.png b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/background.png new file mode 100755 index 0000000..c2a80aa Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/failure.mp3 new file mode 100755 index 0000000..d3d73b9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/failure.ogg new file mode 100755 index 0000000..d7883c1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/failure_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/goal.gif b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/goal.gif new file mode 100755 index 0000000..6a4ea6b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/goal.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/goalIdle.gif new file mode 100755 index 0000000..02dab59 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/goalIdle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/maze_forever.gif new file mode 100755 index 0000000..02dab59 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/maze_forever.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/obstacle.gif new file mode 100755 index 0000000..1fa6dae Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/obstacle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/obstacle.mp3 new file mode 100755 index 0000000..b3ddb3a Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/obstacle.ogg new file mode 100755 index 0000000..e320903 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/obstacleIdle.gif new file mode 100755 index 0000000..aa27ffc Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/obstacleIdle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/small_static_avatar.png new file mode 100755 index 0000000..439b36b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/start.mp3 new file mode 100755 index 0000000..bdd6ea6 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/start.ogg b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/start.ogg new file mode 100755 index 0000000..009fe7d Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/static_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/tiles.png b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/tiles.png new file mode 100755 index 0000000..b809691 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/wall.mp3 new file mode 100755 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/wall.ogg new file mode 100755 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/win.mp3 new file mode 100755 index 0000000..e768c1b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/win.ogg b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/win.ogg new file mode 100755 index 0000000..64adef0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/win_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/public/maze_config.json b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze_config.json new file mode 100644 index 0000000..9ee3da0 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_06/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[0, 0, 4, 0, 4, 0, 4, 0], + [0, 0, 1, 0, 1, 0, 1, 0], + [0, 2, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 0, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 1, 0], + [0, 0, 1, 1, 0, 1, 1, 0], + [0, 1, 3, 1, 1, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/run b/Cours 1 Code.org/Lecon 8/Maze_06/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_06/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/student/maze.tpl.py b/Cours 1 Code.org/Lecon 8/Maze_06/student/maze.tpl.py new file mode 100644 index 0000000..de0722d --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_06/student/maze.tpl.py @@ -0,0 +1,185 @@ +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + + +class BadPathException(Exception): + pass + +MAP = data["map"]["layout"][0] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + global PLAYER_POSITION, FINISH_POSITION + if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: + return True + else: + return False + + +def notDone(): + return not isDone() + + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Il y a une erreur dans votre code.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1 Code.org/Lecon 8/Maze_06/task.yaml b/Cours 1 Code.org/Lecon 8/Maze_06/task.yaml new file mode 100644 index 0000000..3e7fd4c --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_06/task.yaml @@ -0,0 +1,99 @@ +accessible: true +author: Florian Thuin +context: Voici un autre exercice avec le bloc « Si/Sinon », sans aide cette fois ! +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + output: '2' + memory: '100' + time: '30' +name: Exercice 6 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + toolbox: |- + + + + + + + turnLeft + + + turnRight + + + + + + + + + + isPathForward + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + visible: true + name: 'Boucle ' + description: '' + id: '1' + '1': + id: '2' + description: '' + type: 0 + visible: true + name: Condition + '2': + description: '' + name: Lecon 8 + visible: true + type: 2 + id: '' + '3': + description: '' + name: Normal + type: 2 + visible: false + id: '' + '4': + type: 2 + description: '' + name: Facile + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/blocks.js b/Cours 1 Code.org/Lecon 8/Maze_07/public/blocks.js new file mode 100644 index 0000000..f71cdc6 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_07/public/blocks.js @@ -0,0 +1,237 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. + +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": "avancer", + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": "Avance le joueur d'un espace" + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["tourner à gauche", 'turnLeft'], + ["tourner à droite", 'turnRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip("Tourne le joueur à gauche ou à droite de 90 degrés."); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['maze_if'] = { + /** + * Block for 'if' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors effectue ces actions. "); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + var code = 'if (' + argument + ') {\n' + branch + '}\n'; + return code; +}; + +Blockly.Python['maze_if'] = function(block) { + // Generate JavaScript for 'if' conditional if there is a path. + var argument = block.getFieldValue('DIR') + '()'; + var branch = Blockly.Python.statementToCode(block, 'DO'); + var code = 'if ' + argument + ':\n' + branch + '\n'; + return code; +}; + +Blockly.Blocks['maze_ifElse'] = { + /** + * Block for 'if/else' conditional if there is a path. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + ["si chemin devant", 'isPathForward'], + ["si chemin vers la gauche", 'isPathLeft'], + ["si chemin vers la droite", 'isPathRight'] + ]; + // Append arrows to direction messages. + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + DIRECTIONS[2][0] += Maze.Blocks.RIGHT_TURN; + this.setColour(Maze.Blocks.LOGIC_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.appendStatementInput('DO') + .appendField("faire"); + this.appendStatementInput('ELSE') + .appendField("sinon"); + this.setTooltip("Si il y a un chemin dans la direction specifiée, \nalors fais le premier bloc d'actions. \nSinon fais le second bloc d'actions."); + this.setPreviousStatement(true); + this.setNextStatement(true); + } +}; + +Blockly.JavaScript['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '(\'block_id_' + block.id + '\')'; + var branch0 = Blockly.JavaScript.statementToCode(block, 'DO'); + var branch1 = Blockly.JavaScript.statementToCode(block, 'ELSE'); + var code = 'if (' + argument + ') {\n' + branch0 + + '} else {\n' + branch1 + '}\n'; + return code; +}; + +Blockly.Python['maze_ifElse'] = function(block) { + // Generate JavaScript for 'if/else' conditional if there is a path. + var argument = block.getFieldValue('DIR') + + '()'; + var branch0 = Blockly.Python.statementToCode(block, 'DO'); + var branch1 = Blockly.Python.statementToCode(block, 'ELSE'); + var code = 'if ' + argument + ':\n' + branch0 + + '\nelse:\n' + branch1 + '\n'; + return code; +}; + +Blockly.Blocks['maze_forever'] = { + /** + * Block for repeat loop. + * @this Blockly.Block + */ + init: function() { + this.setColour(Maze.Blocks.LOOPS_HUE); + this.appendDummyInput() + .appendField("répéter jusqu'à") + .appendField(new Blockly.FieldImage(Maze.SKIN.marker, 12, 16)); + this.appendStatementInput('DO') + .appendField("faire"); + this.setPreviousStatement(true); + this.setTooltip("Répète les blocs qui sont à l'intérieur jusqu'à \natteindre le but. "); + } +}; + +Blockly.JavaScript['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.JavaScript.statementToCode(block, 'DO'); + if (Blockly.JavaScript.INFINITE_LOOP_TRAP) { + branch = Blockly.JavaScript.INFINITE_LOOP_TRAP.replace(/%1/g, + '\'block_id_' + block.id + '\'') + branch; + } + return 'while (notDone()) {\n' + branch + '}\n'; +}; + +Blockly.Python['maze_forever'] = function(block) { + // Generate JavaScript for repeat loop. + var branch = Blockly.Python.statementToCode(block, 'DO'); + return 'while notDone():\n' + branch + '\n'; +}; diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/interpreter.js b/Cours 1 Code.org/Lecon 8/Maze_07/public/interpreter.js new file mode 100644 index 0000000..f627d15 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_07/public/interpreter.js @@ -0,0 +1,55 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze.js b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze.js new file mode 100644 index 0000000..248e96e --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze.js @@ -0,0 +1,912 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +var visuals_directory_path = task_directory_path+"maze/" + +Maze.SKIN = { + sprite: visuals_directory_path + json.visuals.sprite, + tiles: visuals_directory_path + json.visuals.tiles, + marker: visuals_directory_path + json.visuals.marker, + goalAnimation: visuals_directory_path + json.visuals.goalAnimation, + obstacleIdle: visuals_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: visuals_directory_path + json.visuals.obstacleAnimation, + obstacleScale: json.visuals.obstacleScale, + background: visuals_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + +// Map each possible shape to a sprite. +// Input: Binary string representing Centre/North/West/South/East squares. +// Output: [x, y] coordinates of each tile's sprite in tiles.png. +Maze.tile_SHAPES = { + '10010': [4, 0], // Dead ends + '10001': [3, 3], + '11000': [0, 1], + '10100': [0, 2], + '11010': [4, 1], // Vertical + '10101': [3, 2], // Horizontal + '10110': [0, 0], // Elbows + '10011': [2, 0], + '11001': [4, 2], + '11100': [2, 3], + '11110': [1, 1], // Junctions + '10111': [1, 0], + '11011': [2, 1], + '11101': [1, 2], + '11111': [2, 2], // Cross + 'null0': [4, 3], // Empty + 'null1': [3, 0], + 'null2': [3, 1], + 'null3': [0, 3], + 'null4': [1, 3] +}; + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.MAZE_HEIGHT); + tile.setAttribute('width', Maze.MAZE_WIDTH); + tile.setAttribute('x', 0); + tile.setAttribute('y', 0); + svg.appendChild(tile); + } + + if (Maze.SKIN.graph) { + // Draw the grid lines. + // The grid lines are offset so that the lines pass through the centre of + // each square. A half-pixel offset is also added to as standard SVG + // practice to avoid blurriness. + var offset = Maze.SQUARE_SIZE / 2 + 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Draw the tiles making up the maze map. + + // Return a value of '0' if the specified square is wall or out of bounds, + // '1' otherwise (empty, start, finish). + var normalize = function(x, y) { + if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { + return '0'; + } + return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; + }; + + // Compute and draw the tile for each square. + var tileId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + // Compute the tile index. + tile = normalize(x, y) + + normalize(x, y - 1) + // North. + normalize(x + 1, y) + // West. + normalize(x, y + 1) + // South. + normalize(x - 1, y); // East. + + // Draw the tile. + if (!Maze.tile_SHAPES[tile]) { + // Empty square. Use null0 for large areas, with null1-4 for borders. + // Add some randomness to avoid large empty spaces. + if (tile == '00000' && Math.random() > 0.3) { + tile = 'null0'; + } else { + tile = 'null' + Math.floor(1 + Math.random() * 4); + } + } + var left = Maze.tile_SHAPES[tile][0]; + var top = Maze.tile_SHAPES[tile][1]; + // Tile's clipPath element. + var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + tileClip.setAttribute('id', 'tileClipPath' + tileId); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('width', Maze.SQUARE_SIZE); + clipRect.setAttribute('height', Maze.SQUARE_SIZE); + + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); + clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); + + tileClip.appendChild(clipRect); + svg.appendChild(tileClip); + // Tile sprite. + tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.tiles); + // Position the tile sprite relative to the clipRect. + tile.setAttribute('height', Maze.SQUARE_SIZE * 4); + tile.setAttribute('width', Maze.SQUARE_SIZE * 5); + tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); + tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); + tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); + svg.appendChild(tile); + tileId++; + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + } + ++obsId; + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - + finishIcon.getAttribute('width') / 2); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - + finishIcon.getAttribute('height')); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed * 3)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Is the player at the finish marker? + * @return {boolean} True if not done, false if done. + */ +Maze.notDone = function() { + return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/avatar.png b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/avatar.png new file mode 100755 index 0000000..31e1f92 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/background.png b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/background.png new file mode 100755 index 0000000..c2a80aa Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/background.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/failure.mp3 b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/failure.mp3 new file mode 100755 index 0000000..d3d73b9 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/failure.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/failure.ogg b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/failure.ogg new file mode 100755 index 0000000..d7883c1 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/failure.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/failure_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/failure_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/failure_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/goal.gif b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/goal.gif new file mode 100755 index 0000000..6a4ea6b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/goal.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/goalIdle.gif b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/goalIdle.gif new file mode 100755 index 0000000..02dab59 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/goalIdle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/maze_forever.gif b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/maze_forever.gif new file mode 100755 index 0000000..02dab59 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/maze_forever.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/obstacle.gif b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/obstacle.gif new file mode 100755 index 0000000..1fa6dae Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/obstacle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/obstacle.mp3 b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/obstacle.mp3 new file mode 100755 index 0000000..b3ddb3a Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/obstacle.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/obstacle.ogg b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/obstacle.ogg new file mode 100755 index 0000000..e320903 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/obstacle.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/obstacleIdle.gif b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/obstacleIdle.gif new file mode 100755 index 0000000..aa27ffc Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/obstacleIdle.gif differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/small_static_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/small_static_avatar.png new file mode 100755 index 0000000..439b36b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/small_static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/start.mp3 b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/start.mp3 new file mode 100755 index 0000000..bdd6ea6 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/start.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/start.ogg b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/start.ogg new file mode 100755 index 0000000..009fe7d Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/start.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/static_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/static_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/static_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/tiles.png b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/tiles.png new file mode 100755 index 0000000..b809691 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/tiles.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/wall.mp3 b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/wall.mp3 new file mode 100755 index 0000000..7814930 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/wall.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/wall.ogg b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/wall.ogg new file mode 100755 index 0000000..0f324bc Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/wall.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/win.mp3 b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/win.mp3 new file mode 100755 index 0000000..e768c1b Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/win.mp3 differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/win.ogg b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/win.ogg new file mode 100755 index 0000000..64adef0 Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/win.ogg differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/win_avatar.png b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/win_avatar.png new file mode 100755 index 0000000..0004eba Binary files /dev/null and b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze/win_avatar.png differ diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/public/maze_config.json b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze_config.json new file mode 100644 index 0000000..32380dd --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_07/public/maze_config.json @@ -0,0 +1,42 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 0, 0, 0, 0, 0, 1], + [1, 0, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 0, 0, 0, 0, 0], + [1, 0, 1, 0, 1, 1, 1, 1], + [1, 0, 1, 0, 3, 0, 0, 1], + [1, 0, 1, 0, 0, 0, 0, 1], + [2, 0, 1, 1, 1, 1, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"NORTH", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"avatar.png", + "tiles":"tiles.png", + "marker":"goalIdle.gif", + "goalAnimation":"goal.gif", + "obstacleIdle":"obstacleIdle.gif", + "obstacleAnimation":"obstacle.gif", + "obstacleScale":1.2, + "background":"background.png", + "graph":false, + "obstacleSound":[], + "winSound":[], + "crashSound":[] + } +} \ No newline at end of file diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/run b/Cours 1 Code.org/Lecon 8/Maze_07/run new file mode 100644 index 0000000..629e46d --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_07/run @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback(make_output) diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/student/maze.tpl.py b/Cours 1 Code.org/Lecon 8/Maze_07/student/maze.tpl.py new file mode 100644 index 0000000..de0722d --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_07/student/maze.tpl.py @@ -0,0 +1,185 @@ +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + + +class BadPathException(Exception): + pass + +MAP = data["map"]["layout"][0] + +ROWS = len(MAP) +COLS = len(MAP[0]) + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + +RESULT = RESULT_TYPE[UNSET] + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + + +def student_code(): +@ @code@@ + + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION + if isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + + +def turnRight(): + global PLAYER_ORIENTATION + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + + +def isDone(): + global PLAYER_POSITION, FINISH_POSITION + if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: + return True + else: + return False + + +def notDone(): + return not isDone() + + +try: + student_code() + if isDone(): + print("True", end='', flush=True) + else: + print("Il y a une erreur dans votre code.", end='', flush=True) +except BadPathException: + print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1 Code.org/Lecon 8/Maze_07/task.yaml b/Cours 1 Code.org/Lecon 8/Maze_07/task.yaml new file mode 100644 index 0000000..b655b64 --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/Maze_07/task.yaml @@ -0,0 +1,120 @@ +accessible: true +author: Florian Thuin +context: |- + Peux tu rajouter juste 3 blocs pour aider le zombie à résoudre un labyrinthe plus complexe ? + + Si tu le fais bien, il peut parcourir n'importe quel chemin sinueux peu importe la longueur! +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: Exercice 7 +network_grading: false +order: 0 +problems: + code: + toolbox: |- + + + + + + + turnLeft + + + turnRight + + + + + + + + + + isPathForward + + + + options: + scrollbars: true + visual: + position: left + oneBasedIndex: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + maxBlocks: Infinity + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + workspace: |- + + + + + + isPathForward + + + isPathRight + + + + + + + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: + '0': + type: 0 + visible: true + name: 'Boucle ' + id: '1' + description: '' + '1': + id: '2' + description: '' + type: 0 + visible: true + name: Condition + '2': + description: '' + name: Challenge + type: 2 + visible: false + id: '' + '3': + name: Facile + description: '' + type: 2 + visible: false + id: '' + '4': + type: 2 + description: '' + name: Lecon 8 + visible: true + id: '' + '5': + description: '' + name: Normal + type: 2 + visible: false + id: '' +weight: 1.0 diff --git a/Cours 1 Code.org/Lecon 8/course.yaml b/Cours 1 Code.org/Lecon 8/course.yaml new file mode 100644 index 0000000..beadf4b --- /dev/null +++ b/Cours 1 Code.org/Lecon 8/course.yaml @@ -0,0 +1,18 @@ +accessible: true +name: Cours 8 - Maze +description: '' +admins: +- '' +tutors: [] +groups_student_choice: false +use_classrooms: true +allow_unregister: true +allow_preview: false +registration: true +registration_password: null +registration_ac: null +registration_ac_list: +- '' +is_lti: false +lti_keys: {} +lti_send_back_grade: false diff --git a/Cours 1/Lecon1/02_maze/public/maze.js b/Cours 1/Lecon1/02_maze/public/maze.js deleted file mode 100644 index 0744dfc..0000000 --- a/Cours 1/Lecon1/02_maze/public/maze.js +++ /dev/null @@ -1,901 +0,0 @@ -/** - * Blockly Games: Maze - * - * Copyright 2012 Google Inc. - * https://github.com/google/blockly-games - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview JavaScript for Blockly's Maze application. - * @author fraser@google.com (Neil Fraser) - */ -"use strict"; - -var task_directory_path = window.location.pathname + "/"; - -window.Maze = {}; - -Maze.MAX_BLOCKS = Infinity; - -// Crash type constants. -Maze.CRASH_STOP = 1; -Maze.CRASH_SPIN = 2; -Maze.CRASH_FALL = 3; - -Maze.SKIN = { - sprite: task_directory_path + 'maze/avatar.png', - tiles: task_directory_path + 'maze/tiles.png', - marker: task_directory_path + 'maze/goalIdle.gif', - goalAnimation: task_directory_path + 'maze/goal.gif', - obstacleIdle: task_directory_path + 'maze/obstacleIdle.gif', - obstacleAnimation: task_directory_path + 'maze/obstacle.gif', - obstacleScale: 1.4, - background: task_directory_path + 'maze/background.png', - graph: false, - look: '#000', - obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], - winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], - crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], - crashType: Maze.CRASH_STOP -}; - -/** - * Milliseconds between each animation frame. - */ -window.stepSpeed = 250; - -/** - * The types of squares in the maze, which is represented - * as a 2D array of SquareType values. - * @enum {number} - */ -Maze.SquareType = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4, - STARTANDFINISH: 5 -}; - -// The maze square constants defined above are inlined here -// for ease of reading and writing the static mazes. -Maze.map = - // Level 2. - [ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 1, 3, 0, 0], - [0, 0, 0, 2, 1, 4, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0] - ]; - -/** - * Measure maze dimensions and set sizes. - * ROWS: Number of tiles down. - * COLS: Number of tiles across. - * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). - */ -Maze.ROWS = Maze.map.length; -Maze.COLS = Maze.map[0].length; -Maze.SQUARE_SIZE = 50; -Maze.PEGMAN_HEIGHT = 52; -Maze.PEGMAN_WIDTH = 49; - -Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; -Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; -Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; - -/** - * Constants for cardinal directions. Subsequent code assumes these are - * in the range 0..3 and that opposites have an absolute difference of 2. - * @enum {number} - */ -Maze.DirectionType = { - NORTH: 0, - EAST: 1, - SOUTH: 2, - WEST: 3 -}; - -/** - * Outcomes of running the user program. - */ -Maze.ResultType = { - UNSET: 0, - SUCCESS: 1, - FAILURE: -1, - TIMEOUT: 2, - ERROR: -2 -}; - -/** - * Result of last execution. - */ -Maze.result = Maze.ResultType.UNSET; - -/** - * Starting direction. - */ -Maze.startDirection = Maze.DirectionType.EAST; - -/** - * PIDs of animation tasks currently executing. - */ -Maze.pidList = []; - -// Map each possible shape to a sprite. -// Input: Binary string representing Centre/North/West/South/East squares. -// Output: [x, y] coordinates of each tile's sprite in tiles.png. -Maze.tile_SHAPES = { - '10010': [4, 0], // Dead ends - '10001': [3, 3], - '11000': [0, 1], - '10100': [0, 2], - '11010': [4, 1], // Vertical - '10101': [3, 2], // Horizontal - '10110': [0, 0], // Elbows - '10011': [2, 0], - '11001': [4, 2], - '11100': [2, 3], - '11110': [1, 1], // Junctions - '10111': [1, 0], - '11011': [2, 1], - '11101': [1, 2], - '11111': [2, 2], // Cross - 'null0': [4, 3], // Empty - 'null1': [3, 0], - 'null2': [3, 1], - 'null3': [0, 3], - 'null4': [1, 3] -}; - -/** - * Create and layout all the nodes for the path, scenery, Pegman, and goal. - */ -Maze.drawMap = function() { - var svg = document.getElementById('blocklySvgZone'); - var x, y, tile; - var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; - svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); - svg.setAttribute('style', ''); - - // Draw the outer square. - var square = document.createElementNS(Blockly.SVG_NS, 'rect'); - square.setAttribute('width', Maze.MAZE_WIDTH); - square.setAttribute('height', Maze.MAZE_HEIGHT); - square.setAttribute('fill', '#F1EEE7'); - square.setAttribute('stroke-width', 1); - square.setAttribute('stroke', '#CCB'); - svg.appendChild(square); - - if (Maze.SKIN.background) { - var tile = document.createElementNS(Blockly.SVG_NS, 'image'); - tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.background); - tile.setAttribute('height', Maze.MAZE_HEIGHT); - tile.setAttribute('width', Maze.MAZE_WIDTH); - tile.setAttribute('x', 0); - tile.setAttribute('y', 0); - svg.appendChild(tile); - } - - if (Maze.SKIN.graph) { - // Draw the grid lines. - // The grid lines are offset so that the lines pass through the centre of - // each square. A half-pixel offset is also added to as standard SVG - // practice to avoid blurriness. - var offset = Maze.SQUARE_SIZE / 2 + 0.5; - for (var k = 0; k < Maze.ROWS; k++) { - var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); - h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('x2', Maze.MAZE_WIDTH); - h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('stroke', Maze.SKIN.graph); - h_line.setAttribute('stroke-width', 1); - svg.appendChild(h_line); - } - for (var k = 0; k < Maze.COLS; k++) { - var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); - v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('y2', Maze.MAZE_HEIGHT); - v_line.setAttribute('stroke', Maze.SKIN.graph); - v_line.setAttribute('stroke-width', 1); - svg.appendChild(v_line); - } - } - - // Draw the tiles making up the maze map. - - // Return a value of '0' if the specified square is wall or out of bounds, - // '1' otherwise (empty, start, finish). - var normalize = function(x, y) { - if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { - return '0'; - } - return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; - }; - - // Compute and draw the tile for each square. - var tileId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - // Compute the tile index. - tile = normalize(x, y) + - normalize(x, y - 1) + // North. - normalize(x + 1, y) + // West. - normalize(x, y + 1) + // South. - normalize(x - 1, y); // East. - - // Draw the tile. - if (!Maze.tile_SHAPES[tile]) { - // Empty square. Use null0 for large areas, with null1-4 for borders. - // Add some randomness to avoid large empty spaces. - if (tile == '00000' && Math.random() > 0.3) { - tile = 'null0'; - } else { - tile = 'null' + Math.floor(1 + Math.random() * 4); - } - } - var left = Maze.tile_SHAPES[tile][0]; - var top = Maze.tile_SHAPES[tile][1]; - // Tile's clipPath element. - var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); - tileClip.setAttribute('id', 'tileClipPath' + tileId); - var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); - clipRect.setAttribute('width', Maze.SQUARE_SIZE); - clipRect.setAttribute('height', Maze.SQUARE_SIZE); - - clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); - clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); - - tileClip.appendChild(clipRect); - svg.appendChild(tileClip); - // Tile sprite. - tile = document.createElementNS(Blockly.SVG_NS, 'image'); - tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.tiles); - // Position the tile sprite relative to the clipRect. - tile.setAttribute('height', Maze.SQUARE_SIZE * 4); - tile.setAttribute('width', Maze.SQUARE_SIZE * 5); - tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); - tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); - tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); - svg.appendChild(tile); - tileId++; - } - } - - // Add finish marker. - var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); - finishMarker.setAttribute('id', 'finish'); - finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.marker); - finishMarker.setAttribute('height', 43); - finishMarker.setAttribute('width', 50); - svg.appendChild(finishMarker); - - // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman - var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); - pegmanClip.setAttribute('id', 'pegmanClipPath'); - var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); - clipRect.setAttribute('id', 'clipRect'); - clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); - clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); - pegmanClip.appendChild(clipRect); - svg.appendChild(pegmanClip); - - // Add obstacles. - var obsId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { - var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); - obsIcon.setAttribute('id', 'obstacle' + obsId); - obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); - obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); - obsIcon.setAttribute('x', - Maze.SQUARE_SIZE * (x + 0.5) - - obsIcon.getAttribute('width') / 2); - obsIcon.setAttribute('y', - Maze.SQUARE_SIZE * (y + 0.9) - - obsIcon.getAttribute('height')); - svg.appendChild(obsIcon); - } - ++obsId; - } - } - - // Add Pegman. - var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); - pegmanIcon.setAttribute('id', 'pegman'); - pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.sprite); - pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); - pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 - pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); - svg.appendChild(pegmanIcon); -}; - -/** - * Initialize Blockly and the maze. Called on page load. - */ -Maze.init = function() { - - if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { - console.warn("Maze.init() called but Blockly or workspace was not loaded."); - window.setTimeout(Maze.init, 20); - return; - } - - // - // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); - // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); - - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.winSound, 'win'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.crashSound, 'fail'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.obstacleSound, 'obstacle'); - // Not really needed, there are no user-defined functions or variables. - Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + - 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); - - Maze.drawMap(); - - // Locate the start and finish squares. - for (var y = 0; y < Maze.ROWS; y++) { - for (var x = 0; x < Maze.COLS; x++) { - if (Maze.map[y][x] == Maze.SquareType.START) { - Maze.start_ = { - x: x, - y: y - }; - } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { - Maze.finish_ = { - x: x, - y: y - }; - } - } - } - - Maze.reset(true); - - // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); - - // Switch to zero-based indexing so that later JS levels match the blocks. - Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); - Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); -}; - -/** - * Reset the maze to the start position and kill any pending animation tasks. - * @param {boolean} first True if an opening animation is to be played. - */ -Maze.reset = function(first) { - var x, y; - - // Kill all tasks. - for (x = 0; x < Maze.pidList.length; x++) { - window.clearTimeout(Maze.pidList[x]); - } - Maze.pidList = []; - - // Move Pegman into position. - Maze.pegmanX = Maze.start_.x; - Maze.pegmanY = Maze.start_.y; - - if (first) { - Maze.pegmanD = Maze.startDirection + 1; - Maze.scheduleFinish(false); - Maze.pidList.push(setTimeout(function() { - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); - Maze.pegmanD++; - }, window.stepSpeed * 5)); - } else { - Maze.pegmanD = Maze.startDirection; - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); - } - - // Move the finish icon into position. - var finishIcon = document.getElementById('finish'); - finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - - finishIcon.getAttribute('width') / 2); - finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - - finishIcon.getAttribute('height')); - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); - - // Reset pegman's visibility. - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('opacity', 1); - pegmanIcon.setAttribute('visibility', 'visible'); - - // Reset the obstacle image. - var obsId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - var obsIcon = document.getElementById('obstacle' + obsId); - if (obsIcon) { - obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.obstacleIdle); - } - ++obsId; - } - } - -}; - - -/** - * Iterate through the recorded path and animate pegman's actions. - */ -Maze.animate = function() { - var action = Maze.log.shift(); - if (!action) { - // for (var x = 0; x < Maze.pidList.length; x++) { - // window.clearTimeout(Maze.pidList[x]); - // } - return; - } - - switch (action[0]) { - case 'north': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); - Maze.pegmanY--; - break; - case 'east': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); - Maze.pegmanX++; - break; - case 'south': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); - Maze.pegmanY++; - break; - case 'west': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); - Maze.pegmanX--; - break; - case 'look_north': - Maze.scheduleLook(Maze.DirectionType.NORTH); - break; - case 'look_east': - Maze.scheduleLook(Maze.DirectionType.EAST); - break; - case 'look_south': - Maze.scheduleLook(Maze.DirectionType.SOUTH); - break; - case 'look_west': - Maze.scheduleLook(Maze.DirectionType.WEST); - break; - case 'fail_forward': - Maze.scheduleFail(true); - break; - case 'fail_backward': - Maze.scheduleFail(false); - break; - case 'left': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); - break; - case 'right': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); - break; - case 'finish': - Maze.scheduleFinish(true); - break; - // TODO maybe add this - // case 'plant': - // Maze.animatePlant(); - // break; - } -}; - -/** - * Schedule the animations for a move or turn. - * @param {!Array.} startPos X, Y and direction starting points. - * @param {!Array.} endPos X, Y and direction ending points. - */ -Maze.schedule = function(startPos, endPos) { - var deltas = [(endPos[0] - startPos[0]) / 4, - (endPos[1] - startPos[1]) / 4, - (endPos[2] - startPos[2]) / 4 - ]; - Maze.displayPegman(startPos[0] + deltas[0], - startPos[1] + deltas[1], - Maze.constrainDirection16(startPos[2] + deltas[2])); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(startPos[0] + deltas[0] * 2, - startPos[1] + deltas[1] * 2, - Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(startPos[0] + deltas[0] * 3, - startPos[1] + deltas[1] * 3, - Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(endPos[0], endPos[1], - Maze.constrainDirection16(endPos[2])); - }, window.stepSpeed * 3)); - - if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { - Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('win', 0.5); - var finishIcon = document.getElementById('finish'); - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); - }, window.stepSpeed * 4)); - } - -}; - -/** - * Schedule the animations and sounds for a failed move. - * @param {boolean} forward True if forward, false if backward. - */ -Maze.scheduleFail = function(forward) { - var deltaX = 0; - var deltaY = 0; - switch (Maze.pegmanD) { - case Maze.DirectionType.NORTH: - deltaY = -1; - break; - case Maze.DirectionType.EAST: - deltaX = 1; - break; - case Maze.DirectionType.SOUTH: - deltaY = 1; - break; - case Maze.DirectionType.WEST: - deltaX = -1; - break; - } - if (!forward) { - deltaX = -deltaX; - deltaY = -deltaY; - } - - var targetX = Maze.pegmanX + deltaX * 2; - var targetY = Maze.pegmanY + deltaY * 2; - var squareType = Maze.map[targetY][targetX]; - - if (squareType === Maze.SquareType.OBSTACLE) { - BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); - // Play the sound - Blockly.getMainWorkspace().playAudio('obstacle'); - - // Play the animation - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - var obsId = targetX + Maze.COLS * targetY; - var obsIcon = document.getElementById('obstacle' + obsId); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.obstacleAnimation); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX + deltaX / 2, - Maze.pegmanY + deltaY / 2, - direction16); - }, window.stepSpeed)); - - - var pegmanIcon = document.getElementById('pegman'); - - Maze.pidList.push(setTimeout(function() { - pegmanIcon.setAttribute('visibility', 'hidden'); - }, window.stepSpeed * 2)); - - Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('failure'); - }, window.stepSpeed)); - } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { - BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); - // Bounce bounce. - deltaX /= 4; - deltaY /= 4; - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - Maze.displayPegman(Maze.pegmanX + deltaX, - Maze.pegmanY + deltaY, - direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, - Maze.pegmanY, - direction16); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX + deltaX, - Maze.pegmanY + deltaY, - direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); - }, window.stepSpeed * 3)); - } else { - // Add a small random delta away from the grid. - var deltaZ = (Math.random() - 0.5) * 10; - var deltaD = (Math.random() - 0.5) / 2; - deltaX += (Math.random() - 0.5) / 4; - deltaY += (Math.random() - 0.5) / 4; - deltaX /= 8; - deltaY /= 8; - var acceleration = 0; - if (Maze.SKIN.crashType == Maze.CRASH_FALL) { - acceleration = 0.01; - } - Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('fail', 0.5); - }, window.stepSpeed * 2)); - var setPosition = function(n) { - return function() { - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + - deltaD * n); - Maze.displayPegman(Maze.pegmanX + deltaX * n, - Maze.pegmanY + deltaY * n, - direction16, - deltaZ * n); - deltaY += acceleration; - }; - }; - // 100 frames should get Pegman offscreen. - for (var i = 1; i < 100; i++) { - Maze.pidList.push(setTimeout(setPosition(i), - window.stepSpeed * i / 2)); - } - } -}; - -/** - * Schedule the animations and sound for a victory dance. - * @param {boolean} sound Play the victory sound. - */ -Maze.scheduleFinish = function(sound) { - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); - if (sound) { - Blockly.getMainWorkspace().playAudio('win', 0.5); - } - window.stepSpeed = 250; // Slow down victory animation a bit. - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); - }, window.stepSpeed * 3)); - -}; - -/** - * Display Pegman at the specified location, facing the specified direction. - * @param {number} x Horizontal grid (or fraction thereof). - * @param {number} y Vertical grid (or fraction thereof). - * @param {number} d Direction (0 - 15) or dance (16 - 17). - * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. - */ -Maze.displayPegman = function(x, y, d, opt_angle) { - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('x', - x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); - pegmanIcon.setAttribute('y', - Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); - if (opt_angle) { - pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + - (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + - (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); - } else { - pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); - } - - var clipRect = document.getElementById('clipRect'); - clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); - clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); -}; - -/** - * Display the look icon at Pegman's current location, - * in the specified direction. - * @param {!Maze.DirectionType} d Direction (0 - 3). - */ -Maze.scheduleLook = function(d) { - var x = Maze.pegmanX; - var y = Maze.pegmanY; - switch (d) { - case Maze.DirectionType.NORTH: - x += 0.5; - break; - case Maze.DirectionType.EAST: - x += 1; - y += 0.5; - break; - case Maze.DirectionType.SOUTH: - x += 0.5; - y += 1; - break; - case Maze.DirectionType.WEST: - y += 0.5; - break; - } - x *= Maze.SQUARE_SIZE; - y *= Maze.SQUARE_SIZE; - d = d * 90 - 45; - - var lookIcon = document.getElementById('look'); - lookIcon.setAttribute('transform', - 'translate(' + x + ', ' + y + ') ' + - 'rotate(' + d + ' 0 0) scale(.4)'); - var paths = lookIcon.getElementsByTagName('path'); - lookIcon.style.display = 'inline'; - for (var x = 0, path; path = paths[x]; x++) { - Maze.scheduleLookStep(path, window.stepSpeed * x); - } -}; - -/** - * Schedule one of the 'look' icon's waves to appear, then disappear. - * @param {!Element} path Element to make appear. - * @param {number} delay Milliseconds to wait before making wave appear. - */ -Maze.scheduleLookStep = function(path, delay) { - Maze.pidList.push(setTimeout(function() { - path.style.display = 'inline'; - setTimeout(function() { - path.style.display = 'none'; - }, window.stepSpeed * 2); - }, delay)); -}; - -/** - * Keep the direction within 0-3, wrapping at both ends. - * @param {number} d Potentially out-of-bounds direction value. - * @return {number} Legal direction value. - */ -Maze.constrainDirection4 = function(d) { - d = Math.round(d) % 4; - if (d < 0) { - d += 4; - } - return d; -}; - -/** - * Keep the direction within 0-15, wrapping at both ends. - * @param {number} d Potentially out-of-bounds direction value. - * @return {number} Legal direction value. - */ -Maze.constrainDirection16 = function(d) { - d = Math.round(d) % 16; - if (d < 0) { - d += 16; - } - return d; -}; - -// Core functions. - -/** - * Attempt to move pegman forward or backward. - * @param {number} direction Direction to move (0 = forward, 2 = backward). - * @param {string} id ID of block that triggered this action. - * @throws {true} If the end of the maze is reached. - * @throws {false} If Pegman collides with a wall. - */ -Maze.move = function(direction, id) { - var isNotAPath = !Maze.isPath(direction, null); - if (isNotAPath) { - Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); - Maze.result = Maze.ResultType.ERROR; - } - // If moving backward, flip the effective direction. - var effectiveDirection = Maze.pegmanD + direction; - var command; - switch (Maze.constrainDirection4(effectiveDirection)) { - case Maze.DirectionType.NORTH: - if (isNotAPath) Maze.pegmanY++; - command = 'north'; - break; - case Maze.DirectionType.EAST: - if (isNotAPath) Maze.pegmanX--; - command = 'east'; - break; - case Maze.DirectionType.SOUTH: - if (isNotAPath) Maze.pegmanY--; - command = 'south'; - break; - case Maze.DirectionType.WEST: - if (isNotAPath) Maze.pegmanX++; - command = 'west'; - break; - } - Maze.log.push([command, id]); -}; - -/** - * Turn pegman left or right. - * @param {number} direction Direction to turn (0 = left, 1 = right). - * @param {string} id ID of block that triggered this action. - */ -Maze.turn = function(direction, id) { - if (direction) { - // Right turn (clockwise). - // Maze.pegmanD++; - Maze.log.push(['right', id]); - } else { - // Left turn (counterclockwise). - // Maze.pegmanD--; - Maze.log.push(['left', id]); - } - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); -}; - -/** - * Is there a path next to pegman? - * @param {number} direction Direction to look - * (0 = forward, 1 = right, 2 = backward, 3 = left). - * @param {?string} id ID of block that triggered this action. - * Null if called as a helper function in Maze.move(). - * @return {boolean} True if there is a path. - */ -Maze.isPath = function(direction, id) { - var effectiveDirection = Maze.pegmanD + direction; - var square; - var command; - switch (Maze.constrainDirection4(effectiveDirection)) { - case Maze.DirectionType.NORTH: - square = Maze.map[Maze.pegmanY - 1] && - Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; - command = 'look_north'; - break; - case Maze.DirectionType.EAST: - square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; - command = 'look_east'; - break; - case Maze.DirectionType.SOUTH: - square = Maze.map[Maze.pegmanY + 1] && - Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; - command = 'look_south'; - break; - case Maze.DirectionType.WEST: - square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; - command = 'look_west'; - break; - } - if (id) { - Maze.log.push([command, id]); - } - return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; -}; - -/** - * Is the player at the finish marker? - * @return {boolean} True if not done, false if done. - */ -Maze.notDone = function() { - return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; -}; - - -if (document.getElementById('blocklySvgZone') != null) { - window.addEventListener('load', Maze.init); -} else { - console.warn('Cannot find blocklySvgZone element.'); -} diff --git a/Cours 1/Lecon1/02_maze/student/maze.tpl.py b/Cours 1/Lecon1/02_maze/student/maze.tpl.py deleted file mode 100644 index 80ef1ae..0000000 --- a/Cours 1/Lecon1/02_maze/student/maze.tpl.py +++ /dev/null @@ -1,192 +0,0 @@ -''' -This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. -Try to forget the basic Python syntax for a while. -''' - - -class BadPathException(Exception): - pass - -MAP = [[0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 1, 3, 0, 0], - [0, 0, 0, 2, 1, 4, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0]] - -ROWS = len(MAP) -COLS = len(MAP[0]) - -UNSET = "UNSET" -SUCCESS = "SUCCESS" -FAILURE = "FAILURE" -TIMEOUT = "TIMEOUT" -ERROR = "ERROR" - -RESULT_TYPE = { - UNSET: 0, - SUCCESS: 1, - FAILURE: -1, - TIMEOUT: 2, - ERROR: -2 -} - -RESULT = RESULT_TYPE[UNSET] - -WALL = "WALL" -OPEN = "OPEN" -START = "START" -FINISH = "FINISH" -OBSTACLE = "OBSTACLE" - -SQUARE_TYPE = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4 -} - -PLAYER_POSITION = { - 'x': None, - 'y': None -} - -FINISH_POSITION = { - 'x': None, - 'y': None -} - -for y in range(ROWS): - for x in range(COLS): - if MAP[y][x] == SQUARE_TYPE[START]: - PLAYER_POSITION['x'] = x - PLAYER_POSITION['y'] = y - if MAP[y][x] == SQUARE_TYPE[FINISH]: - FINISH_POSITION['x'] = x - FINISH_POSITION['y'] = y - -EAST = "east" -SOUTH = "south" -WEST = "west" -NORTH = "north" - -DIRECTION_TYPE = { - NORTH: 0, - EAST: 1, - SOUTH: 2, - WEST: 3 -} - -MOVE_POSITION = { - DIRECTION_TYPE[EAST]: { - 'x': 1, - 'y': 0 - }, - DIRECTION_TYPE[SOUTH]: { - 'x': 0, - 'y': 1 - }, - DIRECTION_TYPE[WEST]: { - 'x': -1, - 'y': 0 - }, - DIRECTION_TYPE[NORTH]: { - 'x': 0, - 'y': -1 - } -} - -PLAYER_ORIENTATION = DIRECTION_TYPE[EAST] - - -def student_code(): -@ @code@@ - -def constrain_direction4(direction): - d = direction % 4 - if d < 0: - d += 4 - return d - - -def isPath(direction): - global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE - effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) - test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] - test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] - if test_x < 0 or test_x >= COLS: - return False - elif test_y < 0 or test_y >= ROWS: - return False - else: - return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] - - -def isPathForward(): - return isPath(0) - - -def isPathRight(): - return isPath(1) - - -def isPathBackward(): - return isPath(2) - - -def isPathLeft(): - return isPath(3) - - -def moveForward(): - global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION - if isPathForward(): - PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + \ - MOVE_POSITION[PLAYER_ORIENTATION]['x'] - PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + \ - MOVE_POSITION[PLAYER_ORIENTATION]['y'] - else: - raise BadPathException() - - -def turnLeft(): - global PLAYER_ORIENTATION - PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], - DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], - DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], - DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] - }[PLAYER_ORIENTATION] - - -def turnRight(): - global PLAYER_ORIENTATION - PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], - DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], - DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], - DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] - }[PLAYER_ORIENTATION] - - -def isDone(): - global PLAYER_POSITION, FINISH_POSITION - if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: - return True - else: - return False - - -def notDone(): - return not isDone() - - -try: - student_code() - if isDone(): - print("True", end='', flush=True) - else: - print("Il y a une erreur dans votre code.", end='', flush=True) -except BadPathException: - print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1/Lecon1/06_maze/student/maze.tpl.py b/Cours 1/Lecon1/06_maze/student/maze.tpl.py deleted file mode 100644 index b62e28d..0000000 --- a/Cours 1/Lecon1/06_maze/student/maze.tpl.py +++ /dev/null @@ -1,191 +0,0 @@ -''' -This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. -Try to forget the basic Python syntax for a while. -''' - - -class BadPathException(Exception): - pass - -MAP = [[0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 3, 0, 2, 0, 0], - [0, 0, 0, 1, 0, 1, 0, 0], - [0, 0, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 1, 4, 1, 0, 0], - [0, 0, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0]] - -ROWS = len(MAP) -COLS = len(MAP[0]) - -UNSET = "UNSET" -SUCCESS = "SUCCESS" -FAILURE = "FAILURE" -TIMEOUT = "TIMEOUT" -ERROR = "ERROR" - -RESULT_TYPE = { - UNSET: 0, - SUCCESS: 1, - FAILURE: -1, - TIMEOUT: 2, - ERROR: -2 -} - -RESULT = RESULT_TYPE[UNSET] - -WALL = "WALL" -OPEN = "OPEN" -START = "START" -FINISH = "FINISH" -OBSTACLE = "OBSTACLE" - -SQUARE_TYPE = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4 -} - -PLAYER_POSITION = { - 'x': None, - 'y': None -} - -FINISH_POSITION = { - 'x': None, - 'y': None -} - -for y in range(ROWS): - for x in range(COLS): - if MAP[y][x] == SQUARE_TYPE[START]: - PLAYER_POSITION['x'] = x - PLAYER_POSITION['y'] = y - if MAP[y][x] == SQUARE_TYPE[FINISH]: - FINISH_POSITION['x'] = x - FINISH_POSITION['y'] = y - -EAST = "east" -SOUTH = "south" -WEST = "west" -NORTH = "north" - -DIRECTION_TYPE = { - NORTH: 0, - EAST: 1, - SOUTH: 2, - WEST: 3 -} - -MOVE_POSITION = { - DIRECTION_TYPE[EAST]: { - 'x': 1, - 'y': 0 - }, - DIRECTION_TYPE[SOUTH]: { - 'x': 0, - 'y': 1 - }, - DIRECTION_TYPE[WEST]: { - 'x': -1, - 'y': 0 - }, - DIRECTION_TYPE[NORTH]: { - 'x': 0, - 'y': -1 - } -} - -PLAYER_ORIENTATION = DIRECTION_TYPE[SOUTH] - - -def student_code(): -@ @code@@ - - -def constrain_direction4(direction): - d = direction % 4 - if d < 0: - d += 4 - return d - - -def isPath(direction): - global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE - effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) - test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] - test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] - if test_x < 0 or test_x >= COLS: - return False - elif test_y < 0 or test_y >= ROWS: - return False - else: - return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] - - -def isPathForward(): - return isPath(0) - - -def isPathRight(): - return isPath(1) - - -def isPathBackward(): - return isPath(2) - - -def isPathLeft(): - return isPath(3) - - -def moveForward(): - global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION - if isPathForward(): - PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] - PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] - else: - raise BadPathException() - - -def turnLeft(): - global PLAYER_ORIENTATION - PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], - DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], - DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], - DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] - }[PLAYER_ORIENTATION] - - -def turnRight(): - global PLAYER_ORIENTATION - PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], - DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], - DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], - DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] - }[PLAYER_ORIENTATION] - - -def isDone(): - global PLAYER_POSITION, FINISH_POSITION - if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: - return True - else: - return False - - -def notDone(): - return not isDone() - - -try: - student_code() - if isDone(): - print("True", end='', flush=True) - else: - print("Il y a une erreur dans votre code.", end='', flush=True) -except BadPathException: - print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1/Lecon1/06_maze/task.yaml b/Cours 1/Lecon1/06_maze/task.yaml deleted file mode 100644 index 86b345a..0000000 --- a/Cours 1/Lecon1/06_maze/task.yaml +++ /dev/null @@ -1,111 +0,0 @@ -accessible: true -author: Florian Thuin -context: '' -environment: default -evaluate: best -groups: false -input_random: '0' -limits: - memory: '100' - output: '2' - time: '30' -name: Exercice 6 -network_grading: false -order: 5 -problems: - code: - blocks_files: - - blocks.js - options: - trashcan: true - scrollbars: true - oneBasedIndex: true - maxBlocks: '8' - grid: - spacing: 20 - length: 3 - snap: true - colour: '#ccc' - zoom: - maxScale: 3.0 - minScale: 0.3 - startScale: 1.0 - controls: true - scaleSpeed: 1.2 - wheel: false - toolboxPosition: start - visual: - position: left - css: true - media: /static/common/js/blockly/media/ - sounds: true - files: - - maze.js - - interpreter.js - name: Boucles imbriquées - type: blockly - toolbox: |- - - header: |- - .. image:: 01_maze/maze/small_static_avatar.png - :height: 40px - - **Peux-tu résoudre ce puzzle en utilisant le moins de blocs possible?** - - *Indice* : Tu peux mettre une boucle dans une boucle ! Cet exercice - est résolvable en 5 blocs ! - workspace: -stored_submissions: 0 -submission_limit: - amount: -1 - period: -1 -tags: - '0': - description: utilise une boucle "répéter X fois" - name: Boucle X fois - type: 2 - visible: false - id: '' - '1': - type: 2 - name: Cours1 - description: Exercice faisant partie du cours 1 - visible: false - id: '' - '2': - description: Fait partie du parcours pour élèves en difficulté - visible: true - name: Facile - type: 2 - id: '' - '3': - description: Fait partie du parcours normal - name: Normal - visible: true - type: 2 - id: '' - '4': - description: Fait partie du parcours challenge - visible: true - type: 2 - name: Challenge - id: '' - '5': - name: Séquence - description: Demande de créer une séquence d'instruction - type: 2 - visible: false - id: '' - '6': - name: Boucles imbriquées - type: 2 - description: Demande de créer une boucle dans une boucle - visible: false - id: '' -weight: 1.0 diff --git a/Cours 1/Lecon1/07_maze/public/maze.js b/Cours 1/Lecon1/07_maze/public/maze.js deleted file mode 100644 index 060df05..0000000 --- a/Cours 1/Lecon1/07_maze/public/maze.js +++ /dev/null @@ -1,905 +0,0 @@ -/** - * Blockly Games: Maze - * - * Copyright 2012 Google Inc. - * https://github.com/google/blockly-games - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview JavaScript for Blockly's Maze application. - * @author fraser@google.com (Neil Fraser) - */ -"use strict"; - -var task_directory_path = window.location.pathname + "/"; - -window.Maze = {}; - -Maze.MAX_BLOCKS = Infinity; - -// Crash type constants. -Maze.CRASH_STOP = 1; -Maze.CRASH_SPIN = 2; -Maze.CRASH_FALL = 3; - -Maze.SKIN = { - sprite: task_directory_path + 'maze/avatar.png', - tiles: task_directory_path + 'maze/tiles.png', - marker: task_directory_path + 'maze/goalIdle.gif', - goalAnimation: task_directory_path + 'maze/goal.gif', - obstacleIdle: task_directory_path + 'maze/obstacleIdle.gif', - obstacleAnimation: task_directory_path + 'maze/obstacle.gif', - obstacleScale: 1.4, - background: task_directory_path + 'maze/background.png', - graph: false, - look: '#000', - obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], - winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], - crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], - crashType: Maze.CRASH_STOP -}; - -/** - * Milliseconds between each animation frame. - */ -window.stepSpeed = 250; - -/** - * The types of squares in the maze, which is represented - * as a 2D array of SquareType values. - * @enum {number} - */ -Maze.SquareType = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4, - STARTANDFINISH: 5 -}; - -// The maze square constants defined above are inlined here -// for ease of reading and writing the static mazes. -Maze.map = - // Level 7. - [ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 1, 1, 0, 0], - [0, 1, 0, 4, 0, 1, 0, 0], - [0, 1, 0, 1, 1, 1, 0, 0], - [0, 3, 0, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 0, 1, 2, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0] - ]; - -/** - * Measure maze dimensions and set sizes. - * ROWS: Number of tiles down. - * COLS: Number of tiles across. - * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). - */ -Maze.ROWS = Maze.map.length; -Maze.COLS = Maze.map[0].length; -Maze.SQUARE_SIZE = 50; -Maze.PEGMAN_HEIGHT = 52; -Maze.PEGMAN_WIDTH = 49; - -Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; -Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; -Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; - -/** - * Constants for cardinal directions. Subsequent code assumes these are - * in the range 0..3 and that opposites have an absolute difference of 2. - * @enum {number} - */ -Maze.DirectionType = { - NORTH: 0, - EAST: 1, - SOUTH: 2, - WEST: 3 -}; - -/** - * Outcomes of running the user program. - */ -Maze.ResultType = { - UNSET: 0, - SUCCESS: 1, - FAILURE: -1, - TIMEOUT: 2, - ERROR: -2 -}; - -/** - * Result of last execution. - */ -Maze.result = Maze.ResultType.UNSET; - -/** - * Starting direction. - */ -Maze.startDirection = Maze.DirectionType.WEST; - -/** - * PIDs of animation tasks currently executing. - */ -Maze.pidList = []; - -// Map each possible shape to a sprite. -// Input: Binary string representing Centre/North/West/South/East squares. -// Output: [x, y] coordinates of each tile's sprite in tiles.png. -Maze.tile_SHAPES = { - '10010': [4, 0], // Dead ends - '10001': [3, 3], - '11000': [0, 1], - '10100': [0, 2], - '11010': [4, 1], // Vertical - '10101': [3, 2], // Horizontal - '10110': [0, 0], // Elbows - '10011': [2, 0], - '11001': [4, 2], - '11100': [2, 3], - '11110': [1, 1], // Junctions - '10111': [1, 0], - '11011': [2, 1], - '11101': [1, 2], - '11111': [2, 2], // Cross - 'null0': [4, 3], // Empty - 'null1': [3, 0], - 'null2': [3, 1], - 'null3': [0, 3], - 'null4': [1, 3] -}; - -/** - * Create and layout all the nodes for the path, scenery, Pegman, and goal. - */ -Maze.drawMap = function() { - var svg = document.getElementById('blocklySvgZone'); - var x, y, tile; - var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; - svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); - svg.setAttribute('style', ''); - - // Draw the outer square. - var square = document.createElementNS(Blockly.SVG_NS, 'rect'); - square.setAttribute('width', Maze.MAZE_WIDTH); - square.setAttribute('height', Maze.MAZE_HEIGHT); - square.setAttribute('fill', '#F1EEE7'); - square.setAttribute('stroke-width', 1); - square.setAttribute('stroke', '#CCB'); - svg.appendChild(square); - - if (Maze.SKIN.background) { - var tile = document.createElementNS(Blockly.SVG_NS, 'image'); - tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.background); - tile.setAttribute('height', Maze.MAZE_HEIGHT); - tile.setAttribute('width', Maze.MAZE_WIDTH); - tile.setAttribute('x', 0); - tile.setAttribute('y', 0); - svg.appendChild(tile); - } - - if (Maze.SKIN.graph) { - // Draw the grid lines. - // The grid lines are offset so that the lines pass through the centre of - // each square. A half-pixel offset is also added to as standard SVG - // practice to avoid blurriness. - var offset = Maze.SQUARE_SIZE / 2 + 0.5; - for (var k = 0; k < Maze.ROWS; k++) { - var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); - h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('x2', Maze.MAZE_WIDTH); - h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('stroke', Maze.SKIN.graph); - h_line.setAttribute('stroke-width', 1); - svg.appendChild(h_line); - } - for (var k = 0; k < Maze.COLS; k++) { - var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); - v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('y2', Maze.MAZE_HEIGHT); - v_line.setAttribute('stroke', Maze.SKIN.graph); - v_line.setAttribute('stroke-width', 1); - svg.appendChild(v_line); - } - } - - // Draw the tiles making up the maze map. - - // Return a value of '0' if the specified square is wall or out of bounds, - // '1' otherwise (empty, start, finish). - var normalize = function(x, y) { - if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { - return '0'; - } - return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; - }; - - // Compute and draw the tile for each square. - var tileId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - // Compute the tile index. - tile = normalize(x, y) + - normalize(x, y - 1) + // North. - normalize(x + 1, y) + // West. - normalize(x, y + 1) + // South. - normalize(x - 1, y); // East. - - // Draw the tile. - if (!Maze.tile_SHAPES[tile]) { - // Empty square. Use null0 for large areas, with null1-4 for borders. - // Add some randomness to avoid large empty spaces. - if (tile == '00000' && Math.random() > 0.3) { - tile = 'null0'; - } else { - tile = 'null' + Math.floor(1 + Math.random() * 4); - } - } - var left = Maze.tile_SHAPES[tile][0]; - var top = Maze.tile_SHAPES[tile][1]; - // Tile's clipPath element. - var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); - tileClip.setAttribute('id', 'tileClipPath' + tileId); - var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); - clipRect.setAttribute('width', Maze.SQUARE_SIZE); - clipRect.setAttribute('height', Maze.SQUARE_SIZE); - - clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); - clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); - - tileClip.appendChild(clipRect); - svg.appendChild(tileClip); - // Tile sprite. - tile = document.createElementNS(Blockly.SVG_NS, 'image'); - tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.tiles); - // Position the tile sprite relative to the clipRect. - tile.setAttribute('height', Maze.SQUARE_SIZE * 4); - tile.setAttribute('width', Maze.SQUARE_SIZE * 5); - tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); - tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); - tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); - svg.appendChild(tile); - tileId++; - } - } - - // Add finish marker. - var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); - finishMarker.setAttribute('id', 'finish'); - finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.marker); - finishMarker.setAttribute('height', 43); - finishMarker.setAttribute('width', 50); - svg.appendChild(finishMarker); - - // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman - var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); - pegmanClip.setAttribute('id', 'pegmanClipPath'); - var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); - clipRect.setAttribute('id', 'clipRect'); - clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); - clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); - pegmanClip.appendChild(clipRect); - svg.appendChild(pegmanClip); - - // Add obstacles. - var obsId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { - var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); - obsIcon.setAttribute('id', 'obstacle' + obsId); - obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); - obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); - obsIcon.setAttribute('x', - Maze.SQUARE_SIZE * (x + 0.5) - - obsIcon.getAttribute('width') / 2); - obsIcon.setAttribute('y', - Maze.SQUARE_SIZE * (y + 0.9) - - obsIcon.getAttribute('height')); - svg.appendChild(obsIcon); - } - ++obsId; - } - } - - // Add Pegman. - var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); - pegmanIcon.setAttribute('id', 'pegman'); - pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.sprite); - pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); - pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 - pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); - svg.appendChild(pegmanIcon); -}; - -/** - * Initialize Blockly and the maze. Called on page load. - */ -Maze.init = function() { - - if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { - console.warn("Maze.init() called but Blockly or workspace was not loaded."); - window.setTimeout(Maze.init, 20); - return; - } - - // - // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); - // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); - - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.winSound, 'win'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.crashSound, 'fail'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.obstacleSound, 'obstacle'); - // Not really needed, there are no user-defined functions or variables. - Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + - 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); - - Maze.drawMap(); - - // Locate the start and finish squares. - for (var y = 0; y < Maze.ROWS; y++) { - for (var x = 0; x < Maze.COLS; x++) { - if (Maze.map[y][x] == Maze.SquareType.START) { - Maze.start_ = { - x: x, - y: y - }; - } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { - Maze.finish_ = { - x: x, - y: y - }; - } - } - } - - Maze.reset(true); - - // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); - - // Switch to zero-based indexing so that later JS levels match the blocks. - Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); - Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); -}; - -/** - * Reset the maze to the start position and kill any pending animation tasks. - * @param {boolean} first True if an opening animation is to be played. - */ -Maze.reset = function(first) { - var x, y; - - // Kill all tasks. - for (x = 0; x < Maze.pidList.length; x++) { - window.clearTimeout(Maze.pidList[x]); - } - Maze.pidList = []; - - // Move Pegman into position. - Maze.pegmanX = Maze.start_.x; - Maze.pegmanY = Maze.start_.y; - - if (first) { - Maze.pegmanD = Maze.startDirection + 1; - Maze.scheduleFinish(false); - Maze.pidList.push(setTimeout(function() { - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); - Maze.pegmanD++; - }, window.stepSpeed * 5)); - } else { - Maze.pegmanD = Maze.startDirection; - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); - } - - // Move the finish icon into position. - var finishIcon = document.getElementById('finish'); - finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - - finishIcon.getAttribute('width') / 2); - finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - - finishIcon.getAttribute('height')); - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); - - // Reset pegman's visibility. - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('opacity', 1); - pegmanIcon.setAttribute('visibility', 'visible'); - - // Reset the obstacle image. - var obsId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - var obsIcon = document.getElementById('obstacle' + obsId); - if (obsIcon) { - obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.obstacleIdle); - } - ++obsId; - } - } - -}; - - -/** - * Iterate through the recorded path and animate pegman's actions. - */ -Maze.animate = function() { - var action = Maze.log.shift(); - if (!action) { - // for (var x = 0; x < Maze.pidList.length; x++) { - // window.clearTimeout(Maze.pidList[x]); - // } - return; - } - - switch (action[0]) { - case 'north': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); - Maze.pegmanY--; - break; - case 'east': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); - Maze.pegmanX++; - break; - case 'south': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); - Maze.pegmanY++; - break; - case 'west': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); - Maze.pegmanX--; - break; - case 'look_north': - Maze.scheduleLook(Maze.DirectionType.NORTH); - break; - case 'look_east': - Maze.scheduleLook(Maze.DirectionType.EAST); - break; - case 'look_south': - Maze.scheduleLook(Maze.DirectionType.SOUTH); - break; - case 'look_west': - Maze.scheduleLook(Maze.DirectionType.WEST); - break; - case 'fail_forward': - Maze.scheduleFail(true); - break; - case 'fail_backward': - Maze.scheduleFail(false); - break; - case 'left': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); - break; - case 'right': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); - break; - case 'finish': - Maze.scheduleFinish(true); - break; - // TODO maybe add this - // case 'plant': - // Maze.animatePlant(); - // break; - } -}; - -/** - * Schedule the animations for a move or turn. - * @param {!Array.} startPos X, Y and direction starting points. - * @param {!Array.} endPos X, Y and direction ending points. - */ -Maze.schedule = function(startPos, endPos) { - var deltas = [(endPos[0] - startPos[0]) / 4, - (endPos[1] - startPos[1]) / 4, - (endPos[2] - startPos[2]) / 4 - ]; - Maze.displayPegman(startPos[0] + deltas[0], - startPos[1] + deltas[1], - Maze.constrainDirection16(startPos[2] + deltas[2])); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(startPos[0] + deltas[0] * 2, - startPos[1] + deltas[1] * 2, - Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(startPos[0] + deltas[0] * 3, - startPos[1] + deltas[1] * 3, - Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(endPos[0], endPos[1], - Maze.constrainDirection16(endPos[2])); - }, window.stepSpeed * 3)); - - if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { - Maze.pidList.push(setTimeout(function() { - var finishIcon = document.getElementById('finish'); - if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); - Blockly.getMainWorkspace().playAudio('win', 0.3); - } - }, window.stepSpeed * 4)); - } -}; - -/** - * Schedule the animations and sounds for a failed move. - * @param {boolean} forward True if forward, false if backward. - */ -Maze.scheduleFail = function(forward) { - var deltaX = 0; - var deltaY = 0; - switch (Maze.pegmanD) { - case Maze.DirectionType.NORTH: - deltaY = -1; - break; - case Maze.DirectionType.EAST: - deltaX = 1; - break; - case Maze.DirectionType.SOUTH: - deltaY = 1; - break; - case Maze.DirectionType.WEST: - deltaX = -1; - break; - } - if (!forward) { - deltaX = -deltaX; - deltaY = -deltaY; - } - - var targetX = Maze.pegmanX + deltaX * 2; - var targetY = Maze.pegmanY + deltaY * 2; - var squareType = Maze.map[targetY][targetX]; - - if (squareType === Maze.SquareType.OBSTACLE) { - BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); - // Play the sound - Blockly.getMainWorkspace().playAudio('obstacle'); - - // Play the animation - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - var obsId = targetX + Maze.COLS * targetY; - var obsIcon = document.getElementById('obstacle' + obsId); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.obstacleAnimation); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX + deltaX / 2, - Maze.pegmanY + deltaY / 2, - direction16); - }, window.stepSpeed)); - - - var pegmanIcon = document.getElementById('pegman'); - - Maze.pidList.push(setTimeout(function() { - pegmanIcon.setAttribute('visibility', 'hidden'); - }, window.stepSpeed * 2)); - - Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('failure'); - }, window.stepSpeed)); - } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { - BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); - // Bounce bounce. - deltaX /= 4; - deltaY /= 4; - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - Maze.displayPegman(Maze.pegmanX + deltaX, - Maze.pegmanY + deltaY, - direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, - Maze.pegmanY, - direction16); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX + deltaX, - Maze.pegmanY + deltaY, - direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); - }, window.stepSpeed * 3)); - } else { - // Add a small random delta away from the grid. - var deltaZ = (Math.random() - 0.5) * 10; - var deltaD = (Math.random() - 0.5) / 2; - deltaX += (Math.random() - 0.5) / 4; - deltaY += (Math.random() - 0.5) / 4; - deltaX /= 8; - deltaY /= 8; - var acceleration = 0; - if (Maze.SKIN.crashType == Maze.CRASH_FALL) { - acceleration = 0.01; - } - Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('fail', 0.5); - }, window.stepSpeed * 2)); - var setPosition = function(n) { - return function() { - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + - deltaD * n); - Maze.displayPegman(Maze.pegmanX + deltaX * n, - Maze.pegmanY + deltaY * n, - direction16, - deltaZ * n); - deltaY += acceleration; - }; - }; - // 100 frames should get Pegman offscreen. - for (var i = 1; i < 100; i++) { - Maze.pidList.push(setTimeout(setPosition(i), - window.stepSpeed * i / 2)); - } - } -}; - -/** - * Schedule the animations and sound for a victory dance. - * @param {boolean} sound Play the victory sound. - */ -Maze.scheduleFinish = function(sound) { - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); - if (sound) { - Blockly.getMainWorkspace().playAudio('win', 0.5); - } - window.stepSpeed = 250; // Slow down victory animation a bit. - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); - }, window.stepSpeed * 3)); -}; - -/** - * Display Pegman at the specified location, facing the specified direction. - * @param {number} x Horizontal grid (or fraction thereof). - * @param {number} y Vertical grid (or fraction thereof). - * @param {number} d Direction (0 - 15) or dance (16 - 17). - * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. - */ -Maze.displayPegman = function(x, y, d, opt_angle) { - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('x', - x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); - pegmanIcon.setAttribute('y', - Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); - if (opt_angle) { - pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + - (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + - (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); - } else { - pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); - } - - var clipRect = document.getElementById('clipRect'); - clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); - clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); -}; - -/** - * Display the look icon at Pegman's current location, - * in the specified direction. - * @param {!Maze.DirectionType} d Direction (0 - 3). - */ -Maze.scheduleLook = function(d) { - var x = Maze.pegmanX; - var y = Maze.pegmanY; - switch (d) { - case Maze.DirectionType.NORTH: - x += 0.5; - break; - case Maze.DirectionType.EAST: - x += 1; - y += 0.5; - break; - case Maze.DirectionType.SOUTH: - x += 0.5; - y += 1; - break; - case Maze.DirectionType.WEST: - y += 0.5; - break; - } - x *= Maze.SQUARE_SIZE; - y *= Maze.SQUARE_SIZE; - d = d * 90 - 45; - - var lookIcon = document.getElementById('look'); - lookIcon.setAttribute('transform', - 'translate(' + x + ', ' + y + ') ' + - 'rotate(' + d + ' 0 0) scale(.4)'); - var paths = lookIcon.getElementsByTagName('path'); - lookIcon.style.display = 'inline'; - for (var x = 0, path; path = paths[x]; x++) { - Maze.scheduleLookStep(path, window.stepSpeed * x); - } -}; - -/** - * Schedule one of the 'look' icon's waves to appear, then disappear. - * @param {!Element} path Element to make appear. - * @param {number} delay Milliseconds to wait before making wave appear. - */ -Maze.scheduleLookStep = function(path, delay) { - Maze.pidList.push(setTimeout(function() { - path.style.display = 'inline'; - setTimeout(function() { - path.style.display = 'none'; - }, window.stepSpeed * 2); - }, delay)); -}; - -/** - * Keep the direction within 0-3, wrapping at both ends. - * @param {number} d Potentially out-of-bounds direction value. - * @return {number} Legal direction value. - */ -Maze.constrainDirection4 = function(d) { - d = Math.round(d) % 4; - if (d < 0) { - d += 4; - } - return d; -}; - -/** - * Keep the direction within 0-15, wrapping at both ends. - * @param {number} d Potentially out-of-bounds direction value. - * @return {number} Legal direction value. - */ -Maze.constrainDirection16 = function(d) { - d = Math.round(d) % 16; - if (d < 0) { - d += 16; - } - return d; -}; - -// Core functions. - -/** - * Attempt to move pegman forward or backward. - * @param {number} direction Direction to move (0 = forward, 2 = backward). - * @param {string} id ID of block that triggered this action. - * @throws {true} If the end of the maze is reached. - * @throws {false} If Pegman collides with a wall. - */ -Maze.move = function(direction, id) { - var isNotAPath = !Maze.isPath(direction, null); - if (isNotAPath) { - Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); - Maze.result = Maze.ResultType.ERROR; - } - // If moving backward, flip the effective direction. - var effectiveDirection = Maze.pegmanD + direction; - var command; - switch (Maze.constrainDirection4(effectiveDirection)) { - case Maze.DirectionType.NORTH: - if (isNotAPath) Maze.pegmanY++; - command = 'north'; - break; - case Maze.DirectionType.EAST: - if (isNotAPath) Maze.pegmanX--; - command = 'east'; - break; - case Maze.DirectionType.SOUTH: - if (isNotAPath) Maze.pegmanY--; - command = 'south'; - break; - case Maze.DirectionType.WEST: - if (isNotAPath) Maze.pegmanX++; - command = 'west'; - break; - } - Maze.log.push([command, id]); - - // TODO maybe add this - // if (Maze.shouldCheckSuccessOnMove()) { - // Maze.checkSuccess(); - // } -}; - -/** - * Turn pegman left or right. - * @param {number} direction Direction to turn (0 = left, 1 = right). - * @param {string} id ID of block that triggered this action. - */ -Maze.turn = function(direction, id) { - if (direction) { - // Right turn (clockwise). - // Maze.pegmanD++; - Maze.log.push(['right', id]); - } else { - // Left turn (counterclockwise). - // Maze.pegmanD--; - Maze.log.push(['left', id]); - } - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); -}; - -/** - * Is there a path next to pegman? - * @param {number} direction Direction to look - * (0 = forward, 1 = right, 2 = backward, 3 = left). - * @param {?string} id ID of block that triggered this action. - * Null if called as a helper function in Maze.move(). - * @return {boolean} True if there is a path. - */ -Maze.isPath = function(direction, id) { - var effectiveDirection = Maze.pegmanD + direction; - var square; - var command; - switch (Maze.constrainDirection4(effectiveDirection)) { - case Maze.DirectionType.NORTH: - square = Maze.map[Maze.pegmanY - 1] && - Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; - command = 'look_north'; - break; - case Maze.DirectionType.EAST: - square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; - command = 'look_east'; - break; - case Maze.DirectionType.SOUTH: - square = Maze.map[Maze.pegmanY + 1] && - Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; - command = 'look_south'; - break; - case Maze.DirectionType.WEST: - square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; - command = 'look_west'; - break; - } - if (id) { - Maze.log.push([command, id]); - } - return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; -}; - -/** - * Is the player at the finish marker? - * @return {boolean} True if not done, false if done. - */ -Maze.notDone = function() { - return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; -}; - -if (document.getElementById('blocklySvgZone') != null) { - window.addEventListener('load', Maze.init); -} else { - console.warn('Cannot find blocklySvgZone element.'); -} diff --git a/Cours 1/Lecon1/08_maze/public/maze.js b/Cours 1/Lecon1/08_maze/public/maze.js deleted file mode 100644 index 1b5648e..0000000 --- a/Cours 1/Lecon1/08_maze/public/maze.js +++ /dev/null @@ -1,905 +0,0 @@ -/** - * Blockly Games: Maze - * - * Copyright 2012 Google Inc. - * https://github.com/google/blockly-games - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview JavaScript for Blockly's Maze application. - * @author fraser@google.com (Neil Fraser) - */ -"use strict"; - -var task_directory_path = window.location.pathname + "/"; - -window.Maze = {}; - -Maze.MAX_BLOCKS = Infinity; - -// Crash type constants. -Maze.CRASH_STOP = 1; -Maze.CRASH_SPIN = 2; -Maze.CRASH_FALL = 3; - -Maze.SKIN = { - sprite: task_directory_path + 'maze/avatar.png', - tiles: task_directory_path + 'maze/tiles.png', - marker: task_directory_path + 'maze/goalIdle.gif', - goalAnimation: task_directory_path + 'maze/goal.gif', - obstacleIdle: task_directory_path + 'maze/obstacleIdle.gif', - obstacleAnimation: task_directory_path + 'maze/obstacle.gif', - obstacleScale: 1.4, - background: task_directory_path + 'maze/background.png', - graph: false, - look: '#000', - obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], - winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], - crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], - crashType: Maze.CRASH_STOP -}; - -/** - * Milliseconds between each animation frame. - */ -window.stepSpeed = 250; - -/** - * The types of squares in the maze, which is represented - * as a 2D array of SquareType values. - * @enum {number} - */ -Maze.SquareType = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4, - STARTANDFINISH: 5 -}; - -// The maze square constants defined above are inlined here -// for ease of reading and writing the static mazes. -Maze.map = - // Level 8. - [ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 2, 0, 1, 1, 1, 0], - [0, 0, 1, 0, 1, 0, 1, 0], - [0, 0, 1, 0, 1, 4, 1, 0], - [0, 0, 1, 4, 1, 0, 1, 0], - [0, 0, 1, 0, 1, 0, 1, 0], - [0, 0, 1, 1, 1, 0, 3, 0], - [0, 0, 0, 0, 0, 0, 0, 0] - ]; - -/** - * Measure maze dimensions and set sizes. - * ROWS: Number of tiles down. - * COLS: Number of tiles across. - * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). - */ -Maze.ROWS = Maze.map.length; -Maze.COLS = Maze.map[0].length; -Maze.SQUARE_SIZE = 50; -Maze.PEGMAN_HEIGHT = 52; -Maze.PEGMAN_WIDTH = 49; - -Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; -Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; -Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; - -/** - * Constants for cardinal directions. Subsequent code assumes these are - * in the range 0..3 and that opposites have an absolute difference of 2. - * @enum {number} - */ -Maze.DirectionType = { - NORTH: 0, - EAST: 1, - SOUTH: 2, - WEST: 3 -}; - -/** - * Outcomes of running the user program. - */ -Maze.ResultType = { - UNSET: 0, - SUCCESS: 1, - FAILURE: -1, - TIMEOUT: 2, - ERROR: -2 -}; - -/** - * Result of last execution. - */ -Maze.result = Maze.ResultType.UNSET; - -/** - * Starting direction. - */ -Maze.startDirection = Maze.DirectionType.SOUTH; - -/** - * PIDs of animation tasks currently executing. - */ -Maze.pidList = []; - -// Map each possible shape to a sprite. -// Input: Binary string representing Centre/North/West/South/East squares. -// Output: [x, y] coordinates of each tile's sprite in tiles.png. -Maze.tile_SHAPES = { - '10010': [4, 0], // Dead ends - '10001': [3, 3], - '11000': [0, 1], - '10100': [0, 2], - '11010': [4, 1], // Vertical - '10101': [3, 2], // Horizontal - '10110': [0, 0], // Elbows - '10011': [2, 0], - '11001': [4, 2], - '11100': [2, 3], - '11110': [1, 1], // Junctions - '10111': [1, 0], - '11011': [2, 1], - '11101': [1, 2], - '11111': [2, 2], // Cross - 'null0': [4, 3], // Empty - 'null1': [3, 0], - 'null2': [3, 1], - 'null3': [0, 3], - 'null4': [1, 3] -}; - -/** - * Create and layout all the nodes for the path, scenery, Pegman, and goal. - */ -Maze.drawMap = function() { - var svg = document.getElementById('blocklySvgZone'); - var x, y, tile; - var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; - svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); - svg.setAttribute('style', ''); - - // Draw the outer square. - var square = document.createElementNS(Blockly.SVG_NS, 'rect'); - square.setAttribute('width', Maze.MAZE_WIDTH); - square.setAttribute('height', Maze.MAZE_HEIGHT); - square.setAttribute('fill', '#F1EEE7'); - square.setAttribute('stroke-width', 1); - square.setAttribute('stroke', '#CCB'); - svg.appendChild(square); - - if (Maze.SKIN.background) { - var tile = document.createElementNS(Blockly.SVG_NS, 'image'); - tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.background); - tile.setAttribute('height', Maze.MAZE_HEIGHT); - tile.setAttribute('width', Maze.MAZE_WIDTH); - tile.setAttribute('x', 0); - tile.setAttribute('y', 0); - svg.appendChild(tile); - } - - if (Maze.SKIN.graph) { - // Draw the grid lines. - // The grid lines are offset so that the lines pass through the centre of - // each square. A half-pixel offset is also added to as standard SVG - // practice to avoid blurriness. - var offset = Maze.SQUARE_SIZE / 2 + 0.5; - for (var k = 0; k < Maze.ROWS; k++) { - var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); - h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('x2', Maze.MAZE_WIDTH); - h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('stroke', Maze.SKIN.graph); - h_line.setAttribute('stroke-width', 1); - svg.appendChild(h_line); - } - for (var k = 0; k < Maze.COLS; k++) { - var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); - v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('y2', Maze.MAZE_HEIGHT); - v_line.setAttribute('stroke', Maze.SKIN.graph); - v_line.setAttribute('stroke-width', 1); - svg.appendChild(v_line); - } - } - - // Draw the tiles making up the maze map. - - // Return a value of '0' if the specified square is wall or out of bounds, - // '1' otherwise (empty, start, finish). - var normalize = function(x, y) { - if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { - return '0'; - } - return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; - }; - - // Compute and draw the tile for each square. - var tileId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - // Compute the tile index. - tile = normalize(x, y) + - normalize(x, y - 1) + // North. - normalize(x + 1, y) + // West. - normalize(x, y + 1) + // South. - normalize(x - 1, y); // East. - - // Draw the tile. - if (!Maze.tile_SHAPES[tile]) { - // Empty square. Use null0 for large areas, with null1-4 for borders. - // Add some randomness to avoid large empty spaces. - if (tile == '00000' && Math.random() > 0.3) { - tile = 'null0'; - } else { - tile = 'null' + Math.floor(1 + Math.random() * 4); - } - } - var left = Maze.tile_SHAPES[tile][0]; - var top = Maze.tile_SHAPES[tile][1]; - // Tile's clipPath element. - var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); - tileClip.setAttribute('id', 'tileClipPath' + tileId); - var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); - clipRect.setAttribute('width', Maze.SQUARE_SIZE); - clipRect.setAttribute('height', Maze.SQUARE_SIZE); - - clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); - clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); - - tileClip.appendChild(clipRect); - svg.appendChild(tileClip); - // Tile sprite. - tile = document.createElementNS(Blockly.SVG_NS, 'image'); - tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.tiles); - // Position the tile sprite relative to the clipRect. - tile.setAttribute('height', Maze.SQUARE_SIZE * 4); - tile.setAttribute('width', Maze.SQUARE_SIZE * 5); - tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); - tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); - tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); - svg.appendChild(tile); - tileId++; - } - } - - // Add finish marker. - var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); - finishMarker.setAttribute('id', 'finish'); - finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.marker); - finishMarker.setAttribute('height', 43); - finishMarker.setAttribute('width', 50); - svg.appendChild(finishMarker); - - // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman - var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); - pegmanClip.setAttribute('id', 'pegmanClipPath'); - var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); - clipRect.setAttribute('id', 'clipRect'); - clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); - clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); - pegmanClip.appendChild(clipRect); - svg.appendChild(pegmanClip); - - // Add obstacles. - var obsId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { - var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); - obsIcon.setAttribute('id', 'obstacle' + obsId); - obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); - obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); - obsIcon.setAttribute('x', - Maze.SQUARE_SIZE * (x + 0.5) - - obsIcon.getAttribute('width') / 2); - obsIcon.setAttribute('y', - Maze.SQUARE_SIZE * (y + 0.9) - - obsIcon.getAttribute('height')); - svg.appendChild(obsIcon); - } - ++obsId; - } - } - - // Add Pegman. - var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); - pegmanIcon.setAttribute('id', 'pegman'); - pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.sprite); - pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); - pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 - pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); - svg.appendChild(pegmanIcon); -}; - -/** - * Initialize Blockly and the maze. Called on page load. - */ -Maze.init = function() { - - if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { - console.warn("Maze.init() called but Blockly or workspace was not loaded."); - window.setTimeout(Maze.init, 20); - return; - } - - // - // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); - // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); - - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.winSound, 'win'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.crashSound, 'fail'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.obstacleSound, 'obstacle'); - // Not really needed, there are no user-defined functions or variables. - Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + - 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); - - Maze.drawMap(); - - // Locate the start and finish squares. - for (var y = 0; y < Maze.ROWS; y++) { - for (var x = 0; x < Maze.COLS; x++) { - if (Maze.map[y][x] == Maze.SquareType.START) { - Maze.start_ = { - x: x, - y: y - }; - } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { - Maze.finish_ = { - x: x, - y: y - }; - } - } - } - - Maze.reset(true); - - // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); - - // Switch to zero-based indexing so that later JS levels match the blocks. - Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); - Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); -}; - -/** - * Reset the maze to the start position and kill any pending animation tasks. - * @param {boolean} first True if an opening animation is to be played. - */ -Maze.reset = function(first) { - var x, y; - - // Kill all tasks. - for (x = 0; x < Maze.pidList.length; x++) { - window.clearTimeout(Maze.pidList[x]); - } - Maze.pidList = []; - - // Move Pegman into position. - Maze.pegmanX = Maze.start_.x; - Maze.pegmanY = Maze.start_.y; - - if (first) { - Maze.pegmanD = Maze.startDirection + 1; - Maze.scheduleFinish(false); - Maze.pidList.push(setTimeout(function() { - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); - Maze.pegmanD++; - }, window.stepSpeed * 5)); - } else { - Maze.pegmanD = Maze.startDirection; - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); - } - - // Move the finish icon into position. - var finishIcon = document.getElementById('finish'); - finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - - finishIcon.getAttribute('width') / 2); - finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - - finishIcon.getAttribute('height')); - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); - - // Reset pegman's visibility. - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('opacity', 1); - pegmanIcon.setAttribute('visibility', 'visible'); - - // Reset the obstacle image. - var obsId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - var obsIcon = document.getElementById('obstacle' + obsId); - if (obsIcon) { - obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.obstacleIdle); - } - ++obsId; - } - } - -}; - - -/** - * Iterate through the recorded path and animate pegman's actions. - */ -Maze.animate = function() { - var action = Maze.log.shift(); - if (!action) { - // for (var x = 0; x < Maze.pidList.length; x++) { - // window.clearTimeout(Maze.pidList[x]); - // } - return; - } - - switch (action[0]) { - case 'north': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); - Maze.pegmanY--; - break; - case 'east': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); - Maze.pegmanX++; - break; - case 'south': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); - Maze.pegmanY++; - break; - case 'west': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); - Maze.pegmanX--; - break; - case 'look_north': - Maze.scheduleLook(Maze.DirectionType.NORTH); - break; - case 'look_east': - Maze.scheduleLook(Maze.DirectionType.EAST); - break; - case 'look_south': - Maze.scheduleLook(Maze.DirectionType.SOUTH); - break; - case 'look_west': - Maze.scheduleLook(Maze.DirectionType.WEST); - break; - case 'fail_forward': - Maze.scheduleFail(true); - break; - case 'fail_backward': - Maze.scheduleFail(false); - break; - case 'left': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); - break; - case 'right': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); - break; - case 'finish': - Maze.scheduleFinish(true); - break; - // TODO maybe add this - // case 'plant': - // Maze.animatePlant(); - // break; - } -}; - -/** - * Schedule the animations for a move or turn. - * @param {!Array.} startPos X, Y and direction starting points. - * @param {!Array.} endPos X, Y and direction ending points. - */ -Maze.schedule = function(startPos, endPos) { - var deltas = [(endPos[0] - startPos[0]) / 4, - (endPos[1] - startPos[1]) / 4, - (endPos[2] - startPos[2]) / 4 - ]; - Maze.displayPegman(startPos[0] + deltas[0], - startPos[1] + deltas[1], - Maze.constrainDirection16(startPos[2] + deltas[2])); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(startPos[0] + deltas[0] * 2, - startPos[1] + deltas[1] * 2, - Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(startPos[0] + deltas[0] * 3, - startPos[1] + deltas[1] * 3, - Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(endPos[0], endPos[1], - Maze.constrainDirection16(endPos[2])); - }, window.stepSpeed * 3)); - - if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { - Maze.pidList.push(setTimeout(function() { - var finishIcon = document.getElementById('finish'); - if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); - Blockly.getMainWorkspace().playAudio('win', 0.3); - } - }, window.stepSpeed * 4)); - } -}; - -/** - * Schedule the animations and sounds for a failed move. - * @param {boolean} forward True if forward, false if backward. - */ -Maze.scheduleFail = function(forward) { - var deltaX = 0; - var deltaY = 0; - switch (Maze.pegmanD) { - case Maze.DirectionType.NORTH: - deltaY = -1; - break; - case Maze.DirectionType.EAST: - deltaX = 1; - break; - case Maze.DirectionType.SOUTH: - deltaY = 1; - break; - case Maze.DirectionType.WEST: - deltaX = -1; - break; - } - if (!forward) { - deltaX = -deltaX; - deltaY = -deltaY; - } - - var targetX = Maze.pegmanX + deltaX * 2; - var targetY = Maze.pegmanY + deltaY * 2; - var squareType = Maze.map[targetY][targetX]; - - if (squareType === Maze.SquareType.OBSTACLE) { - BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); - // Play the sound - Blockly.getMainWorkspace().playAudio('obstacle'); - - // Play the animation - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - var obsId = targetX + Maze.COLS * targetY; - var obsIcon = document.getElementById('obstacle' + obsId); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.obstacleAnimation); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX + deltaX / 2, - Maze.pegmanY + deltaY / 2, - direction16); - }, window.stepSpeed)); - - - var pegmanIcon = document.getElementById('pegman'); - - Maze.pidList.push(setTimeout(function() { - pegmanIcon.setAttribute('visibility', 'hidden'); - }, window.stepSpeed * 2)); - - Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('failure'); - }, window.stepSpeed)); - } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { - BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); - // Bounce bounce. - deltaX /= 4; - deltaY /= 4; - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - Maze.displayPegman(Maze.pegmanX + deltaX, - Maze.pegmanY + deltaY, - direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, - Maze.pegmanY, - direction16); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX + deltaX, - Maze.pegmanY + deltaY, - direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); - }, window.stepSpeed * 3)); - } else { - // Add a small random delta away from the grid. - var deltaZ = (Math.random() - 0.5) * 10; - var deltaD = (Math.random() - 0.5) / 2; - deltaX += (Math.random() - 0.5) / 4; - deltaY += (Math.random() - 0.5) / 4; - deltaX /= 8; - deltaY /= 8; - var acceleration = 0; - if (Maze.SKIN.crashType == Maze.CRASH_FALL) { - acceleration = 0.01; - } - Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('fail', 0.5); - }, window.stepSpeed * 2)); - var setPosition = function(n) { - return function() { - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + - deltaD * n); - Maze.displayPegman(Maze.pegmanX + deltaX * n, - Maze.pegmanY + deltaY * n, - direction16, - deltaZ * n); - deltaY += acceleration; - }; - }; - // 100 frames should get Pegman offscreen. - for (var i = 1; i < 100; i++) { - Maze.pidList.push(setTimeout(setPosition(i), - window.stepSpeed * i / 2)); - } - } -}; - -/** - * Schedule the animations and sound for a victory dance. - * @param {boolean} sound Play the victory sound. - */ -Maze.scheduleFinish = function(sound) { - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); - if (sound) { - Blockly.getMainWorkspace().playAudio('win', 0.5); - } - window.stepSpeed = 250; // Slow down victory animation a bit. - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); - }, window.stepSpeed * 3)); -}; - -/** - * Display Pegman at the specified location, facing the specified direction. - * @param {number} x Horizontal grid (or fraction thereof). - * @param {number} y Vertical grid (or fraction thereof). - * @param {number} d Direction (0 - 15) or dance (16 - 17). - * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. - */ -Maze.displayPegman = function(x, y, d, opt_angle) { - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('x', - x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); - pegmanIcon.setAttribute('y', - Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); - if (opt_angle) { - pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + - (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + - (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); - } else { - pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); - } - - var clipRect = document.getElementById('clipRect'); - clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); - clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); -}; - -/** - * Display the look icon at Pegman's current location, - * in the specified direction. - * @param {!Maze.DirectionType} d Direction (0 - 3). - */ -Maze.scheduleLook = function(d) { - var x = Maze.pegmanX; - var y = Maze.pegmanY; - switch (d) { - case Maze.DirectionType.NORTH: - x += 0.5; - break; - case Maze.DirectionType.EAST: - x += 1; - y += 0.5; - break; - case Maze.DirectionType.SOUTH: - x += 0.5; - y += 1; - break; - case Maze.DirectionType.WEST: - y += 0.5; - break; - } - x *= Maze.SQUARE_SIZE; - y *= Maze.SQUARE_SIZE; - d = d * 90 - 45; - - var lookIcon = document.getElementById('look'); - lookIcon.setAttribute('transform', - 'translate(' + x + ', ' + y + ') ' + - 'rotate(' + d + ' 0 0) scale(.4)'); - var paths = lookIcon.getElementsByTagName('path'); - lookIcon.style.display = 'inline'; - for (var x = 0, path; path = paths[x]; x++) { - Maze.scheduleLookStep(path, window.stepSpeed * x); - } -}; - -/** - * Schedule one of the 'look' icon's waves to appear, then disappear. - * @param {!Element} path Element to make appear. - * @param {number} delay Milliseconds to wait before making wave appear. - */ -Maze.scheduleLookStep = function(path, delay) { - Maze.pidList.push(setTimeout(function() { - path.style.display = 'inline'; - setTimeout(function() { - path.style.display = 'none'; - }, window.stepSpeed * 2); - }, delay)); -}; - -/** - * Keep the direction within 0-3, wrapping at both ends. - * @param {number} d Potentially out-of-bounds direction value. - * @return {number} Legal direction value. - */ -Maze.constrainDirection4 = function(d) { - d = Math.round(d) % 4; - if (d < 0) { - d += 4; - } - return d; -}; - -/** - * Keep the direction within 0-15, wrapping at both ends. - * @param {number} d Potentially out-of-bounds direction value. - * @return {number} Legal direction value. - */ -Maze.constrainDirection16 = function(d) { - d = Math.round(d) % 16; - if (d < 0) { - d += 16; - } - return d; -}; - -// Core functions. - -/** - * Attempt to move pegman forward or backward. - * @param {number} direction Direction to move (0 = forward, 2 = backward). - * @param {string} id ID of block that triggered this action. - * @throws {true} If the end of the maze is reached. - * @throws {false} If Pegman collides with a wall. - */ -Maze.move = function(direction, id) { - var isNotAPath = !Maze.isPath(direction, null); - if (isNotAPath) { - Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); - Maze.result = Maze.ResultType.ERROR; - } - // If moving backward, flip the effective direction. - var effectiveDirection = Maze.pegmanD + direction; - var command; - switch (Maze.constrainDirection4(effectiveDirection)) { - case Maze.DirectionType.NORTH: - if (isNotAPath) Maze.pegmanY++; - command = 'north'; - break; - case Maze.DirectionType.EAST: - if (isNotAPath) Maze.pegmanX--; - command = 'east'; - break; - case Maze.DirectionType.SOUTH: - if (isNotAPath) Maze.pegmanY--; - command = 'south'; - break; - case Maze.DirectionType.WEST: - if (isNotAPath) Maze.pegmanX++; - command = 'west'; - break; - } - Maze.log.push([command, id]); - - // TODO maybe add this - // if (Maze.shouldCheckSuccessOnMove()) { - // Maze.checkSuccess(); - // } -}; - -/** - * Turn pegman left or right. - * @param {number} direction Direction to turn (0 = left, 1 = right). - * @param {string} id ID of block that triggered this action. - */ -Maze.turn = function(direction, id) { - if (direction) { - // Right turn (clockwise). - // Maze.pegmanD++; - Maze.log.push(['right', id]); - } else { - // Left turn (counterclockwise). - // Maze.pegmanD--; - Maze.log.push(['left', id]); - } - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); -}; - -/** - * Is there a path next to pegman? - * @param {number} direction Direction to look - * (0 = forward, 1 = right, 2 = backward, 3 = left). - * @param {?string} id ID of block that triggered this action. - * Null if called as a helper function in Maze.move(). - * @return {boolean} True if there is a path. - */ -Maze.isPath = function(direction, id) { - var effectiveDirection = Maze.pegmanD + direction; - var square; - var command; - switch (Maze.constrainDirection4(effectiveDirection)) { - case Maze.DirectionType.NORTH: - square = Maze.map[Maze.pegmanY - 1] && - Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; - command = 'look_north'; - break; - case Maze.DirectionType.EAST: - square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; - command = 'look_east'; - break; - case Maze.DirectionType.SOUTH: - square = Maze.map[Maze.pegmanY + 1] && - Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; - command = 'look_south'; - break; - case Maze.DirectionType.WEST: - square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; - command = 'look_west'; - break; - } - if (id) { - Maze.log.push([command, id]); - } - return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; -}; - -/** - * Is the player at the finish marker? - * @return {boolean} True if not done, false if done. - */ -Maze.notDone = function() { - return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; -}; - -if (document.getElementById('blocklySvgZone') != null) { - window.addEventListener('load', Maze.init); -} else { - console.warn('Cannot find blocklySvgZone element.'); -} diff --git a/Cours 1/Lecon1/08_maze/student/maze.tpl.py b/Cours 1/Lecon1/08_maze/student/maze.tpl.py deleted file mode 100644 index f6b7026..0000000 --- a/Cours 1/Lecon1/08_maze/student/maze.tpl.py +++ /dev/null @@ -1,191 +0,0 @@ -''' -This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. -Try to forget the basic Python syntax for a while. -''' - - -class BadPathException(Exception): - pass - -MAP = [[0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 2, 0, 1, 1, 1, 0], - [0, 0, 1, 0, 1, 0, 1, 0], - [0, 0, 1, 0, 1, 4, 1, 0], - [0, 0, 1, 4, 1, 0, 1, 0], - [0, 0, 1, 0, 1, 0, 1, 0], - [0, 0, 1, 1, 1, 0, 3, 0], - [0, 0, 0, 0, 0, 0, 0, 0]] - -ROWS = len(MAP) -COLS = len(MAP[0]) - -UNSET = "UNSET" -SUCCESS = "SUCCESS" -FAILURE = "FAILURE" -TIMEOUT = "TIMEOUT" -ERROR = "ERROR" - -RESULT_TYPE = { - UNSET: 0, - SUCCESS: 1, - FAILURE: -1, - TIMEOUT: 2, - ERROR: -2 -} - -RESULT = RESULT_TYPE[UNSET] - -WALL = "WALL" -OPEN = "OPEN" -START = "START" -FINISH = "FINISH" -OBSTACLE = "OBSTACLE" - -SQUARE_TYPE = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4 -} - -PLAYER_POSITION = { - 'x': None, - 'y': None -} - -FINISH_POSITION = { - 'x': None, - 'y': None -} - -for y in range(ROWS): - for x in range(COLS): - if MAP[y][x] == SQUARE_TYPE[START]: - PLAYER_POSITION['x'] = x - PLAYER_POSITION['y'] = y - if MAP[y][x] == SQUARE_TYPE[FINISH]: - FINISH_POSITION['x'] = x - FINISH_POSITION['y'] = y - -EAST = "east" -SOUTH = "south" -WEST = "west" -NORTH = "north" - -DIRECTION_TYPE = { - NORTH: 0, - EAST: 1, - SOUTH: 2, - WEST: 3 -} - -MOVE_POSITION = { - DIRECTION_TYPE[EAST]: { - 'x': 1, - 'y': 0 - }, - DIRECTION_TYPE[SOUTH]: { - 'x': 0, - 'y': 1 - }, - DIRECTION_TYPE[WEST]: { - 'x': -1, - 'y': 0 - }, - DIRECTION_TYPE[NORTH]: { - 'x': 0, - 'y': -1 - } -} - -PLAYER_ORIENTATION = DIRECTION_TYPE[SOUTH] - - -def student_code(): -@ @code@@ - - -def constrain_direction4(direction): - d = direction % 4 - if d < 0: - d += 4 - return d - - -def isPath(direction): - global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE - effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) - test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] - test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] - if test_x < 0 or test_x >= COLS: - return False - elif test_y < 0 or test_y >= ROWS: - return False - else: - return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] - - -def isPathForward(): - return isPath(0) - - -def isPathRight(): - return isPath(1) - - -def isPathBackward(): - return isPath(2) - - -def isPathLeft(): - return isPath(3) - - -def moveForward(): - global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION - if isPathForward(): - PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] - PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] - else: - raise BadPathException() - - -def turnLeft(): - global PLAYER_ORIENTATION - PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], - DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], - DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], - DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] - }[PLAYER_ORIENTATION] - - -def turnRight(): - global PLAYER_ORIENTATION - PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], - DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], - DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], - DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] - }[PLAYER_ORIENTATION] - - -def isDone(): - global PLAYER_POSITION, FINISH_POSITION - if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: - return True - else: - return False - - -def notDone(): - return not isDone() - - -try: - student_code() - if isDone(): - print("True", end='', flush=True) - else: - print("Il y a une erreur dans votre code.", end='', flush=True) -except BadPathException: - print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1/Lecon1/09_maze/public/maze.js b/Cours 1/Lecon1/09_maze/public/maze.js deleted file mode 100644 index ed46e45..0000000 --- a/Cours 1/Lecon1/09_maze/public/maze.js +++ /dev/null @@ -1,905 +0,0 @@ -/** - * Blockly Games: Maze - * - * Copyright 2012 Google Inc. - * https://github.com/google/blockly-games - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview JavaScript for Blockly's Maze application. - * @author fraser@google.com (Neil Fraser) - */ -"use strict"; - -var task_directory_path = window.location.pathname + "/"; - -window.Maze = {}; - -Maze.MAX_BLOCKS = Infinity; - -// Crash type constants. -Maze.CRASH_STOP = 1; -Maze.CRASH_SPIN = 2; -Maze.CRASH_FALL = 3; - -Maze.SKIN = { - sprite: task_directory_path + 'maze/avatar.png', - tiles: task_directory_path + 'maze/tiles.png', - marker: task_directory_path + 'maze/goalIdle.gif', - goalAnimation: task_directory_path + 'maze/goal.gif', - obstacleIdle: task_directory_path + 'maze/obstacleIdle.gif', - obstacleAnimation: task_directory_path + 'maze/obstacle.gif', - obstacleScale: 1.4, - background: task_directory_path + 'maze/background.png', - graph: false, - look: '#000', - obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], - winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], - crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], - crashType: Maze.CRASH_STOP -}; - -/** - * Milliseconds between each animation frame. - */ -window.stepSpeed = 250; - -/** - * The types of squares in the maze, which is represented - * as a 2D array of SquareType values. - * @enum {number} - */ -Maze.SquareType = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4, - STARTANDFINISH: 5 -}; - -// The maze square constants defined above are inlined here -// for ease of reading and writing the static mazes. -Maze.map = - // Level 9. - [ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 2, 1, 1, 1, 1, 3, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0] - ]; - -/** - * Measure maze dimensions and set sizes. - * ROWS: Number of tiles down. - * COLS: Number of tiles across. - * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). - */ -Maze.ROWS = Maze.map.length; -Maze.COLS = Maze.map[0].length; -Maze.SQUARE_SIZE = 50; -Maze.PEGMAN_HEIGHT = 52; -Maze.PEGMAN_WIDTH = 49; - -Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; -Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; -Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; - -/** - * Constants for cardinal directions. Subsequent code assumes these are - * in the range 0..3 and that opposites have an absolute difference of 2. - * @enum {number} - */ -Maze.DirectionType = { - NORTH: 0, - EAST: 1, - SOUTH: 2, - WEST: 3 -}; - -/** - * Outcomes of running the user program. - */ -Maze.ResultType = { - UNSET: 0, - SUCCESS: 1, - FAILURE: -1, - TIMEOUT: 2, - ERROR: -2 -}; - -/** - * Result of last execution. - */ -Maze.result = Maze.ResultType.UNSET; - -/** - * Starting direction. - */ -Maze.startDirection = Maze.DirectionType.EAST; - -/** - * PIDs of animation tasks currently executing. - */ -Maze.pidList = []; - -// Map each possible shape to a sprite. -// Input: Binary string representing Centre/North/West/South/East squares. -// Output: [x, y] coordinates of each tile's sprite in tiles.png. -Maze.tile_SHAPES = { - '10010': [4, 0], // Dead ends - '10001': [3, 3], - '11000': [0, 1], - '10100': [0, 2], - '11010': [4, 1], // Vertical - '10101': [3, 2], // Horizontal - '10110': [0, 0], // Elbows - '10011': [2, 0], - '11001': [4, 2], - '11100': [2, 3], - '11110': [1, 1], // Junctions - '10111': [1, 0], - '11011': [2, 1], - '11101': [1, 2], - '11111': [2, 2], // Cross - 'null0': [4, 3], // Empty - 'null1': [3, 0], - 'null2': [3, 1], - 'null3': [0, 3], - 'null4': [1, 3] -}; - -/** - * Create and layout all the nodes for the path, scenery, Pegman, and goal. - */ -Maze.drawMap = function() { - var svg = document.getElementById('blocklySvgZone'); - var x, y, tile; - var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; - svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); - svg.setAttribute('style', ''); - - // Draw the outer square. - var square = document.createElementNS(Blockly.SVG_NS, 'rect'); - square.setAttribute('width', Maze.MAZE_WIDTH); - square.setAttribute('height', Maze.MAZE_HEIGHT); - square.setAttribute('fill', '#F1EEE7'); - square.setAttribute('stroke-width', 1); - square.setAttribute('stroke', '#CCB'); - svg.appendChild(square); - - if (Maze.SKIN.background) { - var tile = document.createElementNS(Blockly.SVG_NS, 'image'); - tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.background); - tile.setAttribute('height', Maze.MAZE_HEIGHT); - tile.setAttribute('width', Maze.MAZE_WIDTH); - tile.setAttribute('x', 0); - tile.setAttribute('y', 0); - svg.appendChild(tile); - } - - if (Maze.SKIN.graph) { - // Draw the grid lines. - // The grid lines are offset so that the lines pass through the centre of - // each square. A half-pixel offset is also added to as standard SVG - // practice to avoid blurriness. - var offset = Maze.SQUARE_SIZE / 2 + 0.5; - for (var k = 0; k < Maze.ROWS; k++) { - var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); - h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('x2', Maze.MAZE_WIDTH); - h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('stroke', Maze.SKIN.graph); - h_line.setAttribute('stroke-width', 1); - svg.appendChild(h_line); - } - for (var k = 0; k < Maze.COLS; k++) { - var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); - v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('y2', Maze.MAZE_HEIGHT); - v_line.setAttribute('stroke', Maze.SKIN.graph); - v_line.setAttribute('stroke-width', 1); - svg.appendChild(v_line); - } - } - - // Draw the tiles making up the maze map. - - // Return a value of '0' if the specified square is wall or out of bounds, - // '1' otherwise (empty, start, finish). - var normalize = function(x, y) { - if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { - return '0'; - } - return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; - }; - - // Compute and draw the tile for each square. - var tileId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - // Compute the tile index. - tile = normalize(x, y) + - normalize(x, y - 1) + // North. - normalize(x + 1, y) + // West. - normalize(x, y + 1) + // South. - normalize(x - 1, y); // East. - - // Draw the tile. - if (!Maze.tile_SHAPES[tile]) { - // Empty square. Use null0 for large areas, with null1-4 for borders. - // Add some randomness to avoid large empty spaces. - if (tile == '00000' && Math.random() > 0.3) { - tile = 'null0'; - } else { - tile = 'null' + Math.floor(1 + Math.random() * 4); - } - } - var left = Maze.tile_SHAPES[tile][0]; - var top = Maze.tile_SHAPES[tile][1]; - // Tile's clipPath element. - var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); - tileClip.setAttribute('id', 'tileClipPath' + tileId); - var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); - clipRect.setAttribute('width', Maze.SQUARE_SIZE); - clipRect.setAttribute('height', Maze.SQUARE_SIZE); - - clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); - clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); - - tileClip.appendChild(clipRect); - svg.appendChild(tileClip); - // Tile sprite. - tile = document.createElementNS(Blockly.SVG_NS, 'image'); - tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.tiles); - // Position the tile sprite relative to the clipRect. - tile.setAttribute('height', Maze.SQUARE_SIZE * 4); - tile.setAttribute('width', Maze.SQUARE_SIZE * 5); - tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); - tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); - tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); - svg.appendChild(tile); - tileId++; - } - } - - // Add finish marker. - var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); - finishMarker.setAttribute('id', 'finish'); - finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.marker); - finishMarker.setAttribute('height', 43); - finishMarker.setAttribute('width', 50); - svg.appendChild(finishMarker); - - // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman - var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); - pegmanClip.setAttribute('id', 'pegmanClipPath'); - var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); - clipRect.setAttribute('id', 'clipRect'); - clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); - clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); - pegmanClip.appendChild(clipRect); - svg.appendChild(pegmanClip); - - // Add obstacles. - var obsId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { - var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); - obsIcon.setAttribute('id', 'obstacle' + obsId); - obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); - obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); - obsIcon.setAttribute('x', - Maze.SQUARE_SIZE * (x + 0.5) - - obsIcon.getAttribute('width') / 2); - obsIcon.setAttribute('y', - Maze.SQUARE_SIZE * (y + 0.9) - - obsIcon.getAttribute('height')); - svg.appendChild(obsIcon); - } - ++obsId; - } - } - - // Add Pegman. - var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); - pegmanIcon.setAttribute('id', 'pegman'); - pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.sprite); - pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); - pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 - pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); - svg.appendChild(pegmanIcon); -}; - -/** - * Initialize Blockly and the maze. Called on page load. - */ -Maze.init = function() { - - if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { - console.warn("Maze.init() called but Blockly or workspace was not loaded."); - window.setTimeout(Maze.init, 20); - return; - } - - // - // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); - // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); - - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.winSound, 'win'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.crashSound, 'fail'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.obstacleSound, 'obstacle'); - // Not really needed, there are no user-defined functions or variables. - Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + - 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); - - Maze.drawMap(); - - // Locate the start and finish squares. - for (var y = 0; y < Maze.ROWS; y++) { - for (var x = 0; x < Maze.COLS; x++) { - if (Maze.map[y][x] == Maze.SquareType.START) { - Maze.start_ = { - x: x, - y: y - }; - } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { - Maze.finish_ = { - x: x, - y: y - }; - } - } - } - - Maze.reset(true); - - // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); - - // Switch to zero-based indexing so that later JS levels match the blocks. - Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); - Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); -}; - -/** - * Reset the maze to the start position and kill any pending animation tasks. - * @param {boolean} first True if an opening animation is to be played. - */ -Maze.reset = function(first) { - var x, y; - - // Kill all tasks. - for (x = 0; x < Maze.pidList.length; x++) { - window.clearTimeout(Maze.pidList[x]); - } - Maze.pidList = []; - - // Move Pegman into position. - Maze.pegmanX = Maze.start_.x; - Maze.pegmanY = Maze.start_.y; - - if (first) { - Maze.pegmanD = Maze.startDirection + 1; - Maze.scheduleFinish(false); - Maze.pidList.push(setTimeout(function() { - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); - Maze.pegmanD++; - }, window.stepSpeed * 5)); - } else { - Maze.pegmanD = Maze.startDirection; - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); - } - - // Move the finish icon into position. - var finishIcon = document.getElementById('finish'); - finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - - finishIcon.getAttribute('width') / 2); - finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - - finishIcon.getAttribute('height')); - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); - - // Reset pegman's visibility. - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('opacity', 1); - pegmanIcon.setAttribute('visibility', 'visible'); - - // Reset the obstacle image. - var obsId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - var obsIcon = document.getElementById('obstacle' + obsId); - if (obsIcon) { - obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.obstacleIdle); - } - ++obsId; - } - } - -}; - - -/** - * Iterate through the recorded path and animate pegman's actions. - */ -Maze.animate = function() { - var action = Maze.log.shift(); - if (!action) { - // for (var x = 0; x < Maze.pidList.length; x++) { - // window.clearTimeout(Maze.pidList[x]); - // } - return; - } - - switch (action[0]) { - case 'north': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); - Maze.pegmanY--; - break; - case 'east': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); - Maze.pegmanX++; - break; - case 'south': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); - Maze.pegmanY++; - break; - case 'west': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); - Maze.pegmanX--; - break; - case 'look_north': - Maze.scheduleLook(Maze.DirectionType.NORTH); - break; - case 'look_east': - Maze.scheduleLook(Maze.DirectionType.EAST); - break; - case 'look_south': - Maze.scheduleLook(Maze.DirectionType.SOUTH); - break; - case 'look_west': - Maze.scheduleLook(Maze.DirectionType.WEST); - break; - case 'fail_forward': - Maze.scheduleFail(true); - break; - case 'fail_backward': - Maze.scheduleFail(false); - break; - case 'left': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); - break; - case 'right': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); - break; - case 'finish': - Maze.scheduleFinish(true); - break; - // TODO maybe add this - // case 'plant': - // Maze.animatePlant(); - // break; - } -}; - -/** - * Schedule the animations for a move or turn. - * @param {!Array.} startPos X, Y and direction starting points. - * @param {!Array.} endPos X, Y and direction ending points. - */ -Maze.schedule = function(startPos, endPos) { - var deltas = [(endPos[0] - startPos[0]) / 4, - (endPos[1] - startPos[1]) / 4, - (endPos[2] - startPos[2]) / 4 - ]; - Maze.displayPegman(startPos[0] + deltas[0], - startPos[1] + deltas[1], - Maze.constrainDirection16(startPos[2] + deltas[2])); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(startPos[0] + deltas[0] * 2, - startPos[1] + deltas[1] * 2, - Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(startPos[0] + deltas[0] * 3, - startPos[1] + deltas[1] * 3, - Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(endPos[0], endPos[1], - Maze.constrainDirection16(endPos[2])); - }, window.stepSpeed * 3)); - - if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { - Maze.pidList.push(setTimeout(function() { - var finishIcon = document.getElementById('finish'); - if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); - Blockly.getMainWorkspace().playAudio('win', 0.3); - } - }, window.stepSpeed * 4)); - } -}; - -/** - * Schedule the animations and sounds for a failed move. - * @param {boolean} forward True if forward, false if backward. - */ -Maze.scheduleFail = function(forward) { - var deltaX = 0; - var deltaY = 0; - switch (Maze.pegmanD) { - case Maze.DirectionType.NORTH: - deltaY = -1; - break; - case Maze.DirectionType.EAST: - deltaX = 1; - break; - case Maze.DirectionType.SOUTH: - deltaY = 1; - break; - case Maze.DirectionType.WEST: - deltaX = -1; - break; - } - if (!forward) { - deltaX = -deltaX; - deltaY = -deltaY; - } - - var targetX = Maze.pegmanX + deltaX * 2; - var targetY = Maze.pegmanY + deltaY * 2; - var squareType = Maze.map[targetY][targetX]; - - if (squareType === Maze.SquareType.OBSTACLE) { - BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); - // Play the sound - Blockly.getMainWorkspace().playAudio('obstacle'); - - // Play the animation - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - var obsId = targetX + Maze.COLS * targetY; - var obsIcon = document.getElementById('obstacle' + obsId); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.obstacleAnimation); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX + deltaX / 2, - Maze.pegmanY + deltaY / 2, - direction16); - }, window.stepSpeed)); - - - var pegmanIcon = document.getElementById('pegman'); - - Maze.pidList.push(setTimeout(function() { - pegmanIcon.setAttribute('visibility', 'hidden'); - }, window.stepSpeed * 2)); - - Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('failure'); - }, window.stepSpeed)); - } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { - BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); - // Bounce bounce. - deltaX /= 4; - deltaY /= 4; - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - Maze.displayPegman(Maze.pegmanX + deltaX, - Maze.pegmanY + deltaY, - direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, - Maze.pegmanY, - direction16); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX + deltaX, - Maze.pegmanY + deltaY, - direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); - }, window.stepSpeed * 3)); - } else { - // Add a small random delta away from the grid. - var deltaZ = (Math.random() - 0.5) * 10; - var deltaD = (Math.random() - 0.5) / 2; - deltaX += (Math.random() - 0.5) / 4; - deltaY += (Math.random() - 0.5) / 4; - deltaX /= 8; - deltaY /= 8; - var acceleration = 0; - if (Maze.SKIN.crashType == Maze.CRASH_FALL) { - acceleration = 0.01; - } - Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('fail', 0.5); - }, window.stepSpeed * 2)); - var setPosition = function(n) { - return function() { - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + - deltaD * n); - Maze.displayPegman(Maze.pegmanX + deltaX * n, - Maze.pegmanY + deltaY * n, - direction16, - deltaZ * n); - deltaY += acceleration; - }; - }; - // 100 frames should get Pegman offscreen. - for (var i = 1; i < 100; i++) { - Maze.pidList.push(setTimeout(setPosition(i), - window.stepSpeed * i / 2)); - } - } -}; - -/** - * Schedule the animations and sound for a victory dance. - * @param {boolean} sound Play the victory sound. - */ -Maze.scheduleFinish = function(sound) { - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); - if (sound) { - Blockly.getMainWorkspace().playAudio('win', 0.5); - } - window.stepSpeed = 250; // Slow down victory animation a bit. - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); - }, window.stepSpeed * 3)); -}; - -/** - * Display Pegman at the specified location, facing the specified direction. - * @param {number} x Horizontal grid (or fraction thereof). - * @param {number} y Vertical grid (or fraction thereof). - * @param {number} d Direction (0 - 15) or dance (16 - 17). - * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. - */ -Maze.displayPegman = function(x, y, d, opt_angle) { - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('x', - x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); - pegmanIcon.setAttribute('y', - Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); - if (opt_angle) { - pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + - (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + - (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); - } else { - pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); - } - - var clipRect = document.getElementById('clipRect'); - clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); - clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); -}; - -/** - * Display the look icon at Pegman's current location, - * in the specified direction. - * @param {!Maze.DirectionType} d Direction (0 - 3). - */ -Maze.scheduleLook = function(d) { - var x = Maze.pegmanX; - var y = Maze.pegmanY; - switch (d) { - case Maze.DirectionType.NORTH: - x += 0.5; - break; - case Maze.DirectionType.EAST: - x += 1; - y += 0.5; - break; - case Maze.DirectionType.SOUTH: - x += 0.5; - y += 1; - break; - case Maze.DirectionType.WEST: - y += 0.5; - break; - } - x *= Maze.SQUARE_SIZE; - y *= Maze.SQUARE_SIZE; - d = d * 90 - 45; - - var lookIcon = document.getElementById('look'); - lookIcon.setAttribute('transform', - 'translate(' + x + ', ' + y + ') ' + - 'rotate(' + d + ' 0 0) scale(.4)'); - var paths = lookIcon.getElementsByTagName('path'); - lookIcon.style.display = 'inline'; - for (var x = 0, path; path = paths[x]; x++) { - Maze.scheduleLookStep(path, window.stepSpeed * x); - } -}; - -/** - * Schedule one of the 'look' icon's waves to appear, then disappear. - * @param {!Element} path Element to make appear. - * @param {number} delay Milliseconds to wait before making wave appear. - */ -Maze.scheduleLookStep = function(path, delay) { - Maze.pidList.push(setTimeout(function() { - path.style.display = 'inline'; - setTimeout(function() { - path.style.display = 'none'; - }, window.stepSpeed * 2); - }, delay)); -}; - -/** - * Keep the direction within 0-3, wrapping at both ends. - * @param {number} d Potentially out-of-bounds direction value. - * @return {number} Legal direction value. - */ -Maze.constrainDirection4 = function(d) { - d = Math.round(d) % 4; - if (d < 0) { - d += 4; - } - return d; -}; - -/** - * Keep the direction within 0-15, wrapping at both ends. - * @param {number} d Potentially out-of-bounds direction value. - * @return {number} Legal direction value. - */ -Maze.constrainDirection16 = function(d) { - d = Math.round(d) % 16; - if (d < 0) { - d += 16; - } - return d; -}; - -// Core functions. - -/** - * Attempt to move pegman forward or backward. - * @param {number} direction Direction to move (0 = forward, 2 = backward). - * @param {string} id ID of block that triggered this action. - * @throws {true} If the end of the maze is reached. - * @throws {false} If Pegman collides with a wall. - */ -Maze.move = function(direction, id) { - var isNotAPath = !Maze.isPath(direction, null); - if (isNotAPath) { - Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); - Maze.result = Maze.ResultType.ERROR; - } - // If moving backward, flip the effective direction. - var effectiveDirection = Maze.pegmanD + direction; - var command; - switch (Maze.constrainDirection4(effectiveDirection)) { - case Maze.DirectionType.NORTH: - if (isNotAPath) Maze.pegmanY++; - command = 'north'; - break; - case Maze.DirectionType.EAST: - if (isNotAPath) Maze.pegmanX--; - command = 'east'; - break; - case Maze.DirectionType.SOUTH: - if (isNotAPath) Maze.pegmanY--; - command = 'south'; - break; - case Maze.DirectionType.WEST: - if (isNotAPath) Maze.pegmanX++; - command = 'west'; - break; - } - Maze.log.push([command, id]); - - // TODO maybe add this - // if (Maze.shouldCheckSuccessOnMove()) { - // Maze.checkSuccess(); - // } -}; - -/** - * Turn pegman left or right. - * @param {number} direction Direction to turn (0 = left, 1 = right). - * @param {string} id ID of block that triggered this action. - */ -Maze.turn = function(direction, id) { - if (direction) { - // Right turn (clockwise). - // Maze.pegmanD++; - Maze.log.push(['right', id]); - } else { - // Left turn (counterclockwise). - // Maze.pegmanD--; - Maze.log.push(['left', id]); - } - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); -}; - -/** - * Is there a path next to pegman? - * @param {number} direction Direction to look - * (0 = forward, 1 = right, 2 = backward, 3 = left). - * @param {?string} id ID of block that triggered this action. - * Null if called as a helper function in Maze.move(). - * @return {boolean} True if there is a path. - */ -Maze.isPath = function(direction, id) { - var effectiveDirection = Maze.pegmanD + direction; - var square; - var command; - switch (Maze.constrainDirection4(effectiveDirection)) { - case Maze.DirectionType.NORTH: - square = Maze.map[Maze.pegmanY - 1] && - Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; - command = 'look_north'; - break; - case Maze.DirectionType.EAST: - square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; - command = 'look_east'; - break; - case Maze.DirectionType.SOUTH: - square = Maze.map[Maze.pegmanY + 1] && - Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; - command = 'look_south'; - break; - case Maze.DirectionType.WEST: - square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; - command = 'look_west'; - break; - } - if (id) { - Maze.log.push([command, id]); - } - return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; -}; - -/** - * Is the player at the finish marker? - * @return {boolean} True if not done, false if done. - */ -Maze.notDone = function() { - return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; -}; - -if (document.getElementById('blocklySvgZone') != null) { - window.addEventListener('load', Maze.init); -} else { - console.warn('Cannot find blocklySvgZone element.'); -} diff --git a/Cours 1/Lecon1/09_maze/student/maze.tpl.py b/Cours 1/Lecon1/09_maze/student/maze.tpl.py deleted file mode 100644 index c2d9cc9..0000000 --- a/Cours 1/Lecon1/09_maze/student/maze.tpl.py +++ /dev/null @@ -1,191 +0,0 @@ -''' -This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. -Try to forget the basic Python syntax for a while. -''' - - -class BadPathException(Exception): - pass - -MAP = [[0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 2, 1, 1, 1, 1, 3, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0]] - -ROWS = len(MAP) -COLS = len(MAP[0]) - -UNSET = "UNSET" -SUCCESS = "SUCCESS" -FAILURE = "FAILURE" -TIMEOUT = "TIMEOUT" -ERROR = "ERROR" - -RESULT_TYPE = { - UNSET: 0, - SUCCESS: 1, - FAILURE: -1, - TIMEOUT: 2, - ERROR: -2 -} - -RESULT = RESULT_TYPE[UNSET] - -WALL = "WALL" -OPEN = "OPEN" -START = "START" -FINISH = "FINISH" -OBSTACLE = "OBSTACLE" - -SQUARE_TYPE = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4 -} - -PLAYER_POSITION = { - 'x': None, - 'y': None -} - -FINISH_POSITION = { - 'x': None, - 'y': None -} - -for y in range(ROWS): - for x in range(COLS): - if MAP[y][x] == SQUARE_TYPE[START]: - PLAYER_POSITION['x'] = x - PLAYER_POSITION['y'] = y - if MAP[y][x] == SQUARE_TYPE[FINISH]: - FINISH_POSITION['x'] = x - FINISH_POSITION['y'] = y - -EAST = "east" -SOUTH = "south" -WEST = "west" -NORTH = "north" - -DIRECTION_TYPE = { - NORTH: 0, - EAST: 1, - SOUTH: 2, - WEST: 3 -} - -MOVE_POSITION = { - DIRECTION_TYPE[EAST]: { - 'x': 1, - 'y': 0 - }, - DIRECTION_TYPE[SOUTH]: { - 'x': 0, - 'y': 1 - }, - DIRECTION_TYPE[WEST]: { - 'x': -1, - 'y': 0 - }, - DIRECTION_TYPE[NORTH]: { - 'x': 0, - 'y': -1 - } -} - -PLAYER_ORIENTATION = DIRECTION_TYPE[EAST] - - -def student_code(): -@ @code@@ - - -def constrain_direction4(direction): - d = direction % 4 - if d < 0: - d += 4 - return d - - -def isPath(direction): - global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE - effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) - test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] - test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] - if test_x < 0 or test_x >= COLS: - return False - elif test_y < 0 or test_y >= ROWS: - return False - else: - return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] - - -def isPathForward(): - return isPath(0) - - -def isPathRight(): - return isPath(1) - - -def isPathBackward(): - return isPath(2) - - -def isPathLeft(): - return isPath(3) - - -def moveForward(): - global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION - if isPathForward(): - PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] - PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] - else: - raise BadPathException() - - -def turnLeft(): - global PLAYER_ORIENTATION - PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], - DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], - DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], - DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] - }[PLAYER_ORIENTATION] - - -def turnRight(): - global PLAYER_ORIENTATION - PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], - DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], - DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], - DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] - }[PLAYER_ORIENTATION] - - -def isDone(): - global PLAYER_POSITION, FINISH_POSITION - if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: - return True - else: - return False - - -def notDone(): - return not isDone() - - -try: - student_code() - if isDone(): - print("True", end='', flush=True) - else: - print("Il y a une erreur dans votre code.", end='', flush=True) -except BadPathException: - print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1/Lecon1/10_maze/public/maze.js b/Cours 1/Lecon1/10_maze/public/maze.js deleted file mode 100644 index af433f1..0000000 --- a/Cours 1/Lecon1/10_maze/public/maze.js +++ /dev/null @@ -1,905 +0,0 @@ -/** - * Blockly Games: Maze - * - * Copyright 2012 Google Inc. - * https://github.com/google/blockly-games - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview JavaScript for Blockly's Maze application. - * @author fraser@google.com (Neil Fraser) - */ -"use strict"; - -var task_directory_path = window.location.pathname + "/"; - -window.Maze = {}; - -Maze.MAX_BLOCKS = Infinity; - -// Crash type constants. -Maze.CRASH_STOP = 1; -Maze.CRASH_SPIN = 2; -Maze.CRASH_FALL = 3; - -Maze.SKIN = { - sprite: task_directory_path + 'maze/avatar.png', - tiles: task_directory_path + 'maze/tiles.png', - marker: task_directory_path + 'maze/goalIdle.gif', - goalAnimation: task_directory_path + 'maze/goal.gif', - obstacleIdle: task_directory_path + 'maze/obstacleIdle.gif', - obstacleAnimation: task_directory_path + 'maze/obstacle.gif', - obstacleScale: 1.4, - background: task_directory_path + 'maze/background.png', - graph: false, - look: '#000', - obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], - winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], - crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], - crashType: Maze.CRASH_STOP -}; - -/** - * Milliseconds between each animation frame. - */ -window.stepSpeed = 250; - -/** - * The types of squares in the maze, which is represented - * as a 2D array of SquareType values. - * @enum {number} - */ -Maze.SquareType = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4, - STARTANDFINISH: 5 -}; - -// The maze square constants defined above are inlined here -// for ease of reading and writing the static mazes. -Maze.map = - // Level 10. - [ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 2, 0, 3, 0, 0], - [0, 0, 0, 1, 0, 1, 0, 0], - [0, 0, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0] - ]; - -/** - * Measure maze dimensions and set sizes. - * ROWS: Number of tiles down. - * COLS: Number of tiles across. - * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). - */ -Maze.ROWS = Maze.map.length; -Maze.COLS = Maze.map[0].length; -Maze.SQUARE_SIZE = 50; -Maze.PEGMAN_HEIGHT = 52; -Maze.PEGMAN_WIDTH = 49; - -Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; -Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; -Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; - -/** - * Constants for cardinal directions. Subsequent code assumes these are - * in the range 0..3 and that opposites have an absolute difference of 2. - * @enum {number} - */ -Maze.DirectionType = { - NORTH: 0, - EAST: 1, - SOUTH: 2, - WEST: 3 -}; - -/** - * Outcomes of running the user program. - */ -Maze.ResultType = { - UNSET: 0, - SUCCESS: 1, - FAILURE: -1, - TIMEOUT: 2, - ERROR: -2 -}; - -/** - * Result of last execution. - */ -Maze.result = Maze.ResultType.UNSET; - -/** - * Starting direction. - */ -Maze.startDirection = Maze.DirectionType.WEST; - -/** - * PIDs of animation tasks currently executing. - */ -Maze.pidList = []; - -// Map each possible shape to a sprite. -// Input: Binary string representing Centre/North/West/South/East squares. -// Output: [x, y] coordinates of each tile's sprite in tiles.png. -Maze.tile_SHAPES = { - '10010': [4, 0], // Dead ends - '10001': [3, 3], - '11000': [0, 1], - '10100': [0, 2], - '11010': [4, 1], // Vertical - '10101': [3, 2], // Horizontal - '10110': [0, 0], // Elbows - '10011': [2, 0], - '11001': [4, 2], - '11100': [2, 3], - '11110': [1, 1], // Junctions - '10111': [1, 0], - '11011': [2, 1], - '11101': [1, 2], - '11111': [2, 2], // Cross - 'null0': [4, 3], // Empty - 'null1': [3, 0], - 'null2': [3, 1], - 'null3': [0, 3], - 'null4': [1, 3] -}; - -/** - * Create and layout all the nodes for the path, scenery, Pegman, and goal. - */ -Maze.drawMap = function() { - var svg = document.getElementById('blocklySvgZone'); - var x, y, tile; - var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; - svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); - svg.setAttribute('style', ''); - - // Draw the outer square. - var square = document.createElementNS(Blockly.SVG_NS, 'rect'); - square.setAttribute('width', Maze.MAZE_WIDTH); - square.setAttribute('height', Maze.MAZE_HEIGHT); - square.setAttribute('fill', '#F1EEE7'); - square.setAttribute('stroke-width', 1); - square.setAttribute('stroke', '#CCB'); - svg.appendChild(square); - - if (Maze.SKIN.background) { - var tile = document.createElementNS(Blockly.SVG_NS, 'image'); - tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.background); - tile.setAttribute('height', Maze.MAZE_HEIGHT); - tile.setAttribute('width', Maze.MAZE_WIDTH); - tile.setAttribute('x', 0); - tile.setAttribute('y', 0); - svg.appendChild(tile); - } - - if (Maze.SKIN.graph) { - // Draw the grid lines. - // The grid lines are offset so that the lines pass through the centre of - // each square. A half-pixel offset is also added to as standard SVG - // practice to avoid blurriness. - var offset = Maze.SQUARE_SIZE / 2 + 0.5; - for (var k = 0; k < Maze.ROWS; k++) { - var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); - h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('x2', Maze.MAZE_WIDTH); - h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('stroke', Maze.SKIN.graph); - h_line.setAttribute('stroke-width', 1); - svg.appendChild(h_line); - } - for (var k = 0; k < Maze.COLS; k++) { - var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); - v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('y2', Maze.MAZE_HEIGHT); - v_line.setAttribute('stroke', Maze.SKIN.graph); - v_line.setAttribute('stroke-width', 1); - svg.appendChild(v_line); - } - } - - // Draw the tiles making up the maze map. - - // Return a value of '0' if the specified square is wall or out of bounds, - // '1' otherwise (empty, start, finish). - var normalize = function(x, y) { - if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { - return '0'; - } - return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; - }; - - // Compute and draw the tile for each square. - var tileId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - // Compute the tile index. - tile = normalize(x, y) + - normalize(x, y - 1) + // North. - normalize(x + 1, y) + // West. - normalize(x, y + 1) + // South. - normalize(x - 1, y); // East. - - // Draw the tile. - if (!Maze.tile_SHAPES[tile]) { - // Empty square. Use null0 for large areas, with null1-4 for borders. - // Add some randomness to avoid large empty spaces. - if (tile == '00000' && Math.random() > 0.3) { - tile = 'null0'; - } else { - tile = 'null' + Math.floor(1 + Math.random() * 4); - } - } - var left = Maze.tile_SHAPES[tile][0]; - var top = Maze.tile_SHAPES[tile][1]; - // Tile's clipPath element. - var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); - tileClip.setAttribute('id', 'tileClipPath' + tileId); - var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); - clipRect.setAttribute('width', Maze.SQUARE_SIZE); - clipRect.setAttribute('height', Maze.SQUARE_SIZE); - - clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); - clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); - - tileClip.appendChild(clipRect); - svg.appendChild(tileClip); - // Tile sprite. - tile = document.createElementNS(Blockly.SVG_NS, 'image'); - tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.tiles); - // Position the tile sprite relative to the clipRect. - tile.setAttribute('height', Maze.SQUARE_SIZE * 4); - tile.setAttribute('width', Maze.SQUARE_SIZE * 5); - tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); - tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); - tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); - svg.appendChild(tile); - tileId++; - } - } - - // Add finish marker. - var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); - finishMarker.setAttribute('id', 'finish'); - finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.marker); - finishMarker.setAttribute('height', 43); - finishMarker.setAttribute('width', 50); - svg.appendChild(finishMarker); - - // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman - var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); - pegmanClip.setAttribute('id', 'pegmanClipPath'); - var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); - clipRect.setAttribute('id', 'clipRect'); - clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); - clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); - pegmanClip.appendChild(clipRect); - svg.appendChild(pegmanClip); - - // Add obstacles. - var obsId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { - var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); - obsIcon.setAttribute('id', 'obstacle' + obsId); - obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); - obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); - obsIcon.setAttribute('x', - Maze.SQUARE_SIZE * (x + 0.5) - - obsIcon.getAttribute('width') / 2); - obsIcon.setAttribute('y', - Maze.SQUARE_SIZE * (y + 0.9) - - obsIcon.getAttribute('height')); - svg.appendChild(obsIcon); - } - ++obsId; - } - } - - // Add Pegman. - var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); - pegmanIcon.setAttribute('id', 'pegman'); - pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.sprite); - pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); - pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 - pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); - svg.appendChild(pegmanIcon); -}; - -/** - * Initialize Blockly and the maze. Called on page load. - */ -Maze.init = function() { - - if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { - console.warn("Maze.init() called but Blockly or workspace was not loaded."); - window.setTimeout(Maze.init, 20); - return; - } - - // - // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); - // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); - - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.winSound, 'win'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.crashSound, 'fail'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.obstacleSound, 'obstacle'); - // Not really needed, there are no user-defined functions or variables. - Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + - 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); - - Maze.drawMap(); - - // Locate the start and finish squares. - for (var y = 0; y < Maze.ROWS; y++) { - for (var x = 0; x < Maze.COLS; x++) { - if (Maze.map[y][x] == Maze.SquareType.START) { - Maze.start_ = { - x: x, - y: y - }; - } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { - Maze.finish_ = { - x: x, - y: y - }; - } - } - } - - Maze.reset(true); - - // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); - - // Switch to zero-based indexing so that later JS levels match the blocks. - Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); - Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); -}; - -/** - * Reset the maze to the start position and kill any pending animation tasks. - * @param {boolean} first True if an opening animation is to be played. - */ -Maze.reset = function(first) { - var x, y; - - // Kill all tasks. - for (x = 0; x < Maze.pidList.length; x++) { - window.clearTimeout(Maze.pidList[x]); - } - Maze.pidList = []; - - // Move Pegman into position. - Maze.pegmanX = Maze.start_.x; - Maze.pegmanY = Maze.start_.y; - - if (first) { - Maze.pegmanD = Maze.startDirection + 1; - Maze.scheduleFinish(false); - Maze.pidList.push(setTimeout(function() { - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); - Maze.pegmanD++; - }, window.stepSpeed * 5)); - } else { - Maze.pegmanD = Maze.startDirection; - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); - } - - // Move the finish icon into position. - var finishIcon = document.getElementById('finish'); - finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - - finishIcon.getAttribute('width') / 2); - finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - - finishIcon.getAttribute('height')); - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); - - // Reset pegman's visibility. - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('opacity', 1); - pegmanIcon.setAttribute('visibility', 'visible'); - - // Reset the obstacle image. - var obsId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - var obsIcon = document.getElementById('obstacle' + obsId); - if (obsIcon) { - obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.obstacleIdle); - } - ++obsId; - } - } - -}; - - -/** - * Iterate through the recorded path and animate pegman's actions. - */ -Maze.animate = function() { - var action = Maze.log.shift(); - if (!action) { - // for (var x = 0; x < Maze.pidList.length; x++) { - // window.clearTimeout(Maze.pidList[x]); - // } - return; - } - - switch (action[0]) { - case 'north': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); - Maze.pegmanY--; - break; - case 'east': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); - Maze.pegmanX++; - break; - case 'south': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); - Maze.pegmanY++; - break; - case 'west': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); - Maze.pegmanX--; - break; - case 'look_north': - Maze.scheduleLook(Maze.DirectionType.NORTH); - break; - case 'look_east': - Maze.scheduleLook(Maze.DirectionType.EAST); - break; - case 'look_south': - Maze.scheduleLook(Maze.DirectionType.SOUTH); - break; - case 'look_west': - Maze.scheduleLook(Maze.DirectionType.WEST); - break; - case 'fail_forward': - Maze.scheduleFail(true); - break; - case 'fail_backward': - Maze.scheduleFail(false); - break; - case 'left': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); - break; - case 'right': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); - break; - case 'finish': - Maze.scheduleFinish(true); - break; - // TODO maybe add this - // case 'plant': - // Maze.animatePlant(); - // break; - } -}; - -/** - * Schedule the animations for a move or turn. - * @param {!Array.} startPos X, Y and direction starting points. - * @param {!Array.} endPos X, Y and direction ending points. - */ -Maze.schedule = function(startPos, endPos) { - var deltas = [(endPos[0] - startPos[0]) / 4, - (endPos[1] - startPos[1]) / 4, - (endPos[2] - startPos[2]) / 4 - ]; - Maze.displayPegman(startPos[0] + deltas[0], - startPos[1] + deltas[1], - Maze.constrainDirection16(startPos[2] + deltas[2])); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(startPos[0] + deltas[0] * 2, - startPos[1] + deltas[1] * 2, - Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(startPos[0] + deltas[0] * 3, - startPos[1] + deltas[1] * 3, - Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(endPos[0], endPos[1], - Maze.constrainDirection16(endPos[2])); - }, window.stepSpeed * 3)); - - if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { - Maze.pidList.push(setTimeout(function() { - var finishIcon = document.getElementById('finish'); - if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); - Blockly.getMainWorkspace().playAudio('win', 0.3); - } - }, window.stepSpeed * 4)); - } -}; - -/** - * Schedule the animations and sounds for a failed move. - * @param {boolean} forward True if forward, false if backward. - */ -Maze.scheduleFail = function(forward) { - var deltaX = 0; - var deltaY = 0; - switch (Maze.pegmanD) { - case Maze.DirectionType.NORTH: - deltaY = -1; - break; - case Maze.DirectionType.EAST: - deltaX = 1; - break; - case Maze.DirectionType.SOUTH: - deltaY = 1; - break; - case Maze.DirectionType.WEST: - deltaX = -1; - break; - } - if (!forward) { - deltaX = -deltaX; - deltaY = -deltaY; - } - - var targetX = Maze.pegmanX + deltaX * 2; - var targetY = Maze.pegmanY + deltaY * 2; - var squareType = Maze.map[targetY][targetX]; - - if (squareType === Maze.SquareType.OBSTACLE) { - BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); - // Play the sound - Blockly.getMainWorkspace().playAudio('obstacle'); - - // Play the animation - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - var obsId = targetX + Maze.COLS * targetY; - var obsIcon = document.getElementById('obstacle' + obsId); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.obstacleAnimation); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX + deltaX / 2, - Maze.pegmanY + deltaY / 2, - direction16); - }, window.stepSpeed)); - - - var pegmanIcon = document.getElementById('pegman'); - - Maze.pidList.push(setTimeout(function() { - pegmanIcon.setAttribute('visibility', 'hidden'); - }, window.stepSpeed * 2)); - - Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('failure'); - }, window.stepSpeed)); - } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { - BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); - // Bounce bounce. - deltaX /= 4; - deltaY /= 4; - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - Maze.displayPegman(Maze.pegmanX + deltaX, - Maze.pegmanY + deltaY, - direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, - Maze.pegmanY, - direction16); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX + deltaX, - Maze.pegmanY + deltaY, - direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); - }, window.stepSpeed * 3)); - } else { - // Add a small random delta away from the grid. - var deltaZ = (Math.random() - 0.5) * 10; - var deltaD = (Math.random() - 0.5) / 2; - deltaX += (Math.random() - 0.5) / 4; - deltaY += (Math.random() - 0.5) / 4; - deltaX /= 8; - deltaY /= 8; - var acceleration = 0; - if (Maze.SKIN.crashType == Maze.CRASH_FALL) { - acceleration = 0.01; - } - Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('fail', 0.5); - }, window.stepSpeed * 2)); - var setPosition = function(n) { - return function() { - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + - deltaD * n); - Maze.displayPegman(Maze.pegmanX + deltaX * n, - Maze.pegmanY + deltaY * n, - direction16, - deltaZ * n); - deltaY += acceleration; - }; - }; - // 100 frames should get Pegman offscreen. - for (var i = 1; i < 100; i++) { - Maze.pidList.push(setTimeout(setPosition(i), - window.stepSpeed * i / 2)); - } - } -}; - -/** - * Schedule the animations and sound for a victory dance. - * @param {boolean} sound Play the victory sound. - */ -Maze.scheduleFinish = function(sound) { - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); - if (sound) { - Blockly.getMainWorkspace().playAudio('win', 0.5); - } - window.stepSpeed = 250; // Slow down victory animation a bit. - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); - }, window.stepSpeed * 3)); -}; - -/** - * Display Pegman at the specified location, facing the specified direction. - * @param {number} x Horizontal grid (or fraction thereof). - * @param {number} y Vertical grid (or fraction thereof). - * @param {number} d Direction (0 - 15) or dance (16 - 17). - * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. - */ -Maze.displayPegman = function(x, y, d, opt_angle) { - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('x', - x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); - pegmanIcon.setAttribute('y', - Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); - if (opt_angle) { - pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + - (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + - (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); - } else { - pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); - } - - var clipRect = document.getElementById('clipRect'); - clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); - clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); -}; - -/** - * Display the look icon at Pegman's current location, - * in the specified direction. - * @param {!Maze.DirectionType} d Direction (0 - 3). - */ -Maze.scheduleLook = function(d) { - var x = Maze.pegmanX; - var y = Maze.pegmanY; - switch (d) { - case Maze.DirectionType.NORTH: - x += 0.5; - break; - case Maze.DirectionType.EAST: - x += 1; - y += 0.5; - break; - case Maze.DirectionType.SOUTH: - x += 0.5; - y += 1; - break; - case Maze.DirectionType.WEST: - y += 0.5; - break; - } - x *= Maze.SQUARE_SIZE; - y *= Maze.SQUARE_SIZE; - d = d * 90 - 45; - - var lookIcon = document.getElementById('look'); - lookIcon.setAttribute('transform', - 'translate(' + x + ', ' + y + ') ' + - 'rotate(' + d + ' 0 0) scale(.4)'); - var paths = lookIcon.getElementsByTagName('path'); - lookIcon.style.display = 'inline'; - for (var x = 0, path; path = paths[x]; x++) { - Maze.scheduleLookStep(path, window.stepSpeed * x); - } -}; - -/** - * Schedule one of the 'look' icon's waves to appear, then disappear. - * @param {!Element} path Element to make appear. - * @param {number} delay Milliseconds to wait before making wave appear. - */ -Maze.scheduleLookStep = function(path, delay) { - Maze.pidList.push(setTimeout(function() { - path.style.display = 'inline'; - setTimeout(function() { - path.style.display = 'none'; - }, window.stepSpeed * 2); - }, delay)); -}; - -/** - * Keep the direction within 0-3, wrapping at both ends. - * @param {number} d Potentially out-of-bounds direction value. - * @return {number} Legal direction value. - */ -Maze.constrainDirection4 = function(d) { - d = Math.round(d) % 4; - if (d < 0) { - d += 4; - } - return d; -}; - -/** - * Keep the direction within 0-15, wrapping at both ends. - * @param {number} d Potentially out-of-bounds direction value. - * @return {number} Legal direction value. - */ -Maze.constrainDirection16 = function(d) { - d = Math.round(d) % 16; - if (d < 0) { - d += 16; - } - return d; -}; - -// Core functions. - -/** - * Attempt to move pegman forward or backward. - * @param {number} direction Direction to move (0 = forward, 2 = backward). - * @param {string} id ID of block that triggered this action. - * @throws {true} If the end of the maze is reached. - * @throws {false} If Pegman collides with a wall. - */ -Maze.move = function(direction, id) { - var isNotAPath = !Maze.isPath(direction, null); - if (isNotAPath) { - Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); - Maze.result = Maze.ResultType.ERROR; - } - // If moving backward, flip the effective direction. - var effectiveDirection = Maze.pegmanD + direction; - var command; - switch (Maze.constrainDirection4(effectiveDirection)) { - case Maze.DirectionType.NORTH: - if (isNotAPath) Maze.pegmanY++; - command = 'north'; - break; - case Maze.DirectionType.EAST: - if (isNotAPath) Maze.pegmanX--; - command = 'east'; - break; - case Maze.DirectionType.SOUTH: - if (isNotAPath) Maze.pegmanY--; - command = 'south'; - break; - case Maze.DirectionType.WEST: - if (isNotAPath) Maze.pegmanX++; - command = 'west'; - break; - } - Maze.log.push([command, id]); - - // TODO maybe add this - // if (Maze.shouldCheckSuccessOnMove()) { - // Maze.checkSuccess(); - // } -}; - -/** - * Turn pegman left or right. - * @param {number} direction Direction to turn (0 = left, 1 = right). - * @param {string} id ID of block that triggered this action. - */ -Maze.turn = function(direction, id) { - if (direction) { - // Right turn (clockwise). - // Maze.pegmanD++; - Maze.log.push(['right', id]); - } else { - // Left turn (counterclockwise). - // Maze.pegmanD--; - Maze.log.push(['left', id]); - } - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); -}; - -/** - * Is there a path next to pegman? - * @param {number} direction Direction to look - * (0 = forward, 1 = right, 2 = backward, 3 = left). - * @param {?string} id ID of block that triggered this action. - * Null if called as a helper function in Maze.move(). - * @return {boolean} True if there is a path. - */ -Maze.isPath = function(direction, id) { - var effectiveDirection = Maze.pegmanD + direction; - var square; - var command; - switch (Maze.constrainDirection4(effectiveDirection)) { - case Maze.DirectionType.NORTH: - square = Maze.map[Maze.pegmanY - 1] && - Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; - command = 'look_north'; - break; - case Maze.DirectionType.EAST: - square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; - command = 'look_east'; - break; - case Maze.DirectionType.SOUTH: - square = Maze.map[Maze.pegmanY + 1] && - Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; - command = 'look_south'; - break; - case Maze.DirectionType.WEST: - square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; - command = 'look_west'; - break; - } - if (id) { - Maze.log.push([command, id]); - } - return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; -}; - -/** - * Is the player at the finish marker? - * @return {boolean} True if not done, false if done. - */ -Maze.notDone = function() { - return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; -}; - -if (document.getElementById('blocklySvgZone') != null) { - window.addEventListener('load', Maze.init); -} else { - console.warn('Cannot find blocklySvgZone element.'); -} diff --git a/Cours 1/Lecon1/10_maze/student/maze.tpl.py b/Cours 1/Lecon1/10_maze/student/maze.tpl.py deleted file mode 100644 index 8d57f64..0000000 --- a/Cours 1/Lecon1/10_maze/student/maze.tpl.py +++ /dev/null @@ -1,191 +0,0 @@ -''' -This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. -Try to forget the basic Python syntax for a while. -''' - - -class BadPathException(Exception): - pass - -MAP = [[0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 2, 0, 3, 0, 0], - [0, 0, 0, 1, 0, 1, 0, 0], - [0, 0, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0]] - -ROWS = len(MAP) -COLS = len(MAP[0]) - -UNSET = "UNSET" -SUCCESS = "SUCCESS" -FAILURE = "FAILURE" -TIMEOUT = "TIMEOUT" -ERROR = "ERROR" - -RESULT_TYPE = { - UNSET: 0, - SUCCESS: 1, - FAILURE: -1, - TIMEOUT: 2, - ERROR: -2 -} - -RESULT = RESULT_TYPE[UNSET] - -WALL = "WALL" -OPEN = "OPEN" -START = "START" -FINISH = "FINISH" -OBSTACLE = "OBSTACLE" - -SQUARE_TYPE = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4 -} - -PLAYER_POSITION = { - 'x': None, - 'y': None -} - -FINISH_POSITION = { - 'x': None, - 'y': None -} - -for y in range(ROWS): - for x in range(COLS): - if MAP[y][x] == SQUARE_TYPE[START]: - PLAYER_POSITION['x'] = x - PLAYER_POSITION['y'] = y - if MAP[y][x] == SQUARE_TYPE[FINISH]: - FINISH_POSITION['x'] = x - FINISH_POSITION['y'] = y - -EAST = "east" -SOUTH = "south" -WEST = "west" -NORTH = "north" - -DIRECTION_TYPE = { - NORTH: 0, - EAST: 1, - SOUTH: 2, - WEST: 3 -} - -MOVE_POSITION = { - DIRECTION_TYPE[EAST]: { - 'x': 1, - 'y': 0 - }, - DIRECTION_TYPE[SOUTH]: { - 'x': 0, - 'y': 1 - }, - DIRECTION_TYPE[WEST]: { - 'x': -1, - 'y': 0 - }, - DIRECTION_TYPE[NORTH]: { - 'x': 0, - 'y': -1 - } -} - -PLAYER_ORIENTATION = DIRECTION_TYPE[WEST] - - -def student_code(): -@ @code@@ - - -def constrain_direction4(direction): - d = direction % 4 - if d < 0: - d += 4 - return d - - -def isPath(direction): - global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE - effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) - test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] - test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] - if test_x < 0 or test_x >= COLS: - return False - elif test_y < 0 or test_y >= ROWS: - return False - else: - return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] - - -def isPathForward(): - return isPath(0) - - -def isPathRight(): - return isPath(1) - - -def isPathBackward(): - return isPath(2) - - -def isPathLeft(): - return isPath(3) - - -def moveForward(): - global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION - if isPathForward(): - PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] - PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] - else: - raise BadPathException() - - -def turnLeft(): - global PLAYER_ORIENTATION - PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], - DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], - DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], - DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] - }[PLAYER_ORIENTATION] - - -def turnRight(): - global PLAYER_ORIENTATION - PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], - DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], - DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], - DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] - }[PLAYER_ORIENTATION] - - -def isDone(): - global PLAYER_POSITION, FINISH_POSITION - if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: - return True - else: - return False - - -def notDone(): - return not isDone() - - -try: - student_code() - if isDone(): - print("True", end='', flush=True) - else: - print("Il y a une erreur dans votre code.", end='', flush=True) -except BadPathException: - print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1/Lecon1/11_maze/public/maze.js b/Cours 1/Lecon1/11_maze/public/maze.js deleted file mode 100644 index 3b2e6ba..0000000 --- a/Cours 1/Lecon1/11_maze/public/maze.js +++ /dev/null @@ -1,905 +0,0 @@ -/** - * Blockly Games: Maze - * - * Copyright 2012 Google Inc. - * https://github.com/google/blockly-games - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview JavaScript for Blockly's Maze application. - * @author fraser@google.com (Neil Fraser) - */ -"use strict"; - -var task_directory_path = window.location.pathname + "/"; - -window.Maze = {}; - -Maze.MAX_BLOCKS = Infinity; - -// Crash type constants. -Maze.CRASH_STOP = 1; -Maze.CRASH_SPIN = 2; -Maze.CRASH_FALL = 3; - -Maze.SKIN = { - sprite: task_directory_path + 'maze/avatar.png', - tiles: task_directory_path + 'maze/tiles.png', - marker: task_directory_path + 'maze/goalIdle.gif', - goalAnimation: task_directory_path + 'maze/goal.gif', - obstacleIdle: task_directory_path + 'maze/obstacleIdle.gif', - obstacleAnimation: task_directory_path + 'maze/obstacle.gif', - obstacleScale: 1.4, - background: task_directory_path + 'maze/background.png', - graph: false, - look: '#000', - obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], - winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], - crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], - crashType: Maze.CRASH_STOP -}; - -/** - * Milliseconds between each animation frame. - */ -window.stepSpeed = 250; - -/** - * The types of squares in the maze, which is represented - * as a 2D array of SquareType values. - * @enum {number} - */ -Maze.SquareType = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4, - STARTANDFINISH: 5 -}; - -// The maze square constants defined above are inlined here -// for ease of reading and writing the static mazes. -Maze.map = - // Level 11. - [ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 3, 0], - [0, 0, 0, 0, 0, 1, 1, 0], - [0, 0, 0, 0, 1, 1, 0, 0], - [0, 0, 0, 1, 1, 0, 0, 0], - [0, 0, 1, 1, 0, 0, 0, 0], - [0, 2, 1, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0] - ]; - -/** - * Measure maze dimensions and set sizes. - * ROWS: Number of tiles down. - * COLS: Number of tiles across. - * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). - */ -Maze.ROWS = Maze.map.length; -Maze.COLS = Maze.map[0].length; -Maze.SQUARE_SIZE = 50; -Maze.PEGMAN_HEIGHT = 52; -Maze.PEGMAN_WIDTH = 49; - -Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; -Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; -Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; - -/** - * Constants for cardinal directions. Subsequent code assumes these are - * in the range 0..3 and that opposites have an absolute difference of 2. - * @enum {number} - */ -Maze.DirectionType = { - NORTH: 0, - EAST: 1, - SOUTH: 2, - WEST: 3 -}; - -/** - * Outcomes of running the user program. - */ -Maze.ResultType = { - UNSET: 0, - SUCCESS: 1, - FAILURE: -1, - TIMEOUT: 2, - ERROR: -2 -}; - -/** - * Result of last execution. - */ -Maze.result = Maze.ResultType.UNSET; - -/** - * Starting direction. - */ -Maze.startDirection = Maze.DirectionType.EAST; - -/** - * PIDs of animation tasks currently executing. - */ -Maze.pidList = []; - -// Map each possible shape to a sprite. -// Input: Binary string representing Centre/North/West/South/East squares. -// Output: [x, y] coordinates of each tile's sprite in tiles.png. -Maze.tile_SHAPES = { - '10010': [4, 0], // Dead ends - '10001': [3, 3], - '11000': [0, 1], - '10100': [0, 2], - '11010': [4, 1], // Vertical - '10101': [3, 2], // Horizontal - '10110': [0, 0], // Elbows - '10011': [2, 0], - '11001': [4, 2], - '11100': [2, 3], - '11110': [1, 1], // Junctions - '10111': [1, 0], - '11011': [2, 1], - '11101': [1, 2], - '11111': [2, 2], // Cross - 'null0': [4, 3], // Empty - 'null1': [3, 0], - 'null2': [3, 1], - 'null3': [0, 3], - 'null4': [1, 3] -}; - -/** - * Create and layout all the nodes for the path, scenery, Pegman, and goal. - */ -Maze.drawMap = function() { - var svg = document.getElementById('blocklySvgZone'); - var x, y, tile; - var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; - svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); - svg.setAttribute('style', ''); - - // Draw the outer square. - var square = document.createElementNS(Blockly.SVG_NS, 'rect'); - square.setAttribute('width', Maze.MAZE_WIDTH); - square.setAttribute('height', Maze.MAZE_HEIGHT); - square.setAttribute('fill', '#F1EEE7'); - square.setAttribute('stroke-width', 1); - square.setAttribute('stroke', '#CCB'); - svg.appendChild(square); - - if (Maze.SKIN.background) { - var tile = document.createElementNS(Blockly.SVG_NS, 'image'); - tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.background); - tile.setAttribute('height', Maze.MAZE_HEIGHT); - tile.setAttribute('width', Maze.MAZE_WIDTH); - tile.setAttribute('x', 0); - tile.setAttribute('y', 0); - svg.appendChild(tile); - } - - if (Maze.SKIN.graph) { - // Draw the grid lines. - // The grid lines are offset so that the lines pass through the centre of - // each square. A half-pixel offset is also added to as standard SVG - // practice to avoid blurriness. - var offset = Maze.SQUARE_SIZE / 2 + 0.5; - for (var k = 0; k < Maze.ROWS; k++) { - var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); - h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('x2', Maze.MAZE_WIDTH); - h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('stroke', Maze.SKIN.graph); - h_line.setAttribute('stroke-width', 1); - svg.appendChild(h_line); - } - for (var k = 0; k < Maze.COLS; k++) { - var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); - v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('y2', Maze.MAZE_HEIGHT); - v_line.setAttribute('stroke', Maze.SKIN.graph); - v_line.setAttribute('stroke-width', 1); - svg.appendChild(v_line); - } - } - - // Draw the tiles making up the maze map. - - // Return a value of '0' if the specified square is wall or out of bounds, - // '1' otherwise (empty, start, finish). - var normalize = function(x, y) { - if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { - return '0'; - } - return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; - }; - - // Compute and draw the tile for each square. - var tileId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - // Compute the tile index. - tile = normalize(x, y) + - normalize(x, y - 1) + // North. - normalize(x + 1, y) + // West. - normalize(x, y + 1) + // South. - normalize(x - 1, y); // East. - - // Draw the tile. - if (!Maze.tile_SHAPES[tile]) { - // Empty square. Use null0 for large areas, with null1-4 for borders. - // Add some randomness to avoid large empty spaces. - if (tile == '00000' && Math.random() > 0.3) { - tile = 'null0'; - } else { - tile = 'null' + Math.floor(1 + Math.random() * 4); - } - } - var left = Maze.tile_SHAPES[tile][0]; - var top = Maze.tile_SHAPES[tile][1]; - // Tile's clipPath element. - var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); - tileClip.setAttribute('id', 'tileClipPath' + tileId); - var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); - clipRect.setAttribute('width', Maze.SQUARE_SIZE); - clipRect.setAttribute('height', Maze.SQUARE_SIZE); - - clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); - clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); - - tileClip.appendChild(clipRect); - svg.appendChild(tileClip); - // Tile sprite. - tile = document.createElementNS(Blockly.SVG_NS, 'image'); - tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.tiles); - // Position the tile sprite relative to the clipRect. - tile.setAttribute('height', Maze.SQUARE_SIZE * 4); - tile.setAttribute('width', Maze.SQUARE_SIZE * 5); - tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); - tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); - tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); - svg.appendChild(tile); - tileId++; - } - } - - // Add finish marker. - var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); - finishMarker.setAttribute('id', 'finish'); - finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.marker); - finishMarker.setAttribute('height', 43); - finishMarker.setAttribute('width', 50); - svg.appendChild(finishMarker); - - // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman - var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); - pegmanClip.setAttribute('id', 'pegmanClipPath'); - var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); - clipRect.setAttribute('id', 'clipRect'); - clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); - clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); - pegmanClip.appendChild(clipRect); - svg.appendChild(pegmanClip); - - // Add obstacles. - var obsId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { - var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); - obsIcon.setAttribute('id', 'obstacle' + obsId); - obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); - obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); - obsIcon.setAttribute('x', - Maze.SQUARE_SIZE * (x + 0.5) - - obsIcon.getAttribute('width') / 2); - obsIcon.setAttribute('y', - Maze.SQUARE_SIZE * (y + 0.9) - - obsIcon.getAttribute('height')); - svg.appendChild(obsIcon); - } - ++obsId; - } - } - - // Add Pegman. - var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); - pegmanIcon.setAttribute('id', 'pegman'); - pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.sprite); - pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); - pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 - pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); - svg.appendChild(pegmanIcon); -}; - -/** - * Initialize Blockly and the maze. Called on page load. - */ -Maze.init = function() { - - if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { - console.warn("Maze.init() called but Blockly or workspace was not loaded."); - window.setTimeout(Maze.init, 20); - return; - } - - // - // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); - // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); - - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.winSound, 'win'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.crashSound, 'fail'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.obstacleSound, 'obstacle'); - // Not really needed, there are no user-defined functions or variables. - Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + - 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); - - Maze.drawMap(); - - // Locate the start and finish squares. - for (var y = 0; y < Maze.ROWS; y++) { - for (var x = 0; x < Maze.COLS; x++) { - if (Maze.map[y][x] == Maze.SquareType.START) { - Maze.start_ = { - x: x, - y: y - }; - } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { - Maze.finish_ = { - x: x, - y: y - }; - } - } - } - - Maze.reset(true); - - // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); - - // Switch to zero-based indexing so that later JS levels match the blocks. - Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); - Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); -}; - -/** - * Reset the maze to the start position and kill any pending animation tasks. - * @param {boolean} first True if an opening animation is to be played. - */ -Maze.reset = function(first) { - var x, y; - - // Kill all tasks. - for (x = 0; x < Maze.pidList.length; x++) { - window.clearTimeout(Maze.pidList[x]); - } - Maze.pidList = []; - - // Move Pegman into position. - Maze.pegmanX = Maze.start_.x; - Maze.pegmanY = Maze.start_.y; - - if (first) { - Maze.pegmanD = Maze.startDirection + 1; - Maze.scheduleFinish(false); - Maze.pidList.push(setTimeout(function() { - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); - Maze.pegmanD++; - }, window.stepSpeed * 5)); - } else { - Maze.pegmanD = Maze.startDirection; - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); - } - - // Move the finish icon into position. - var finishIcon = document.getElementById('finish'); - finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - - finishIcon.getAttribute('width') / 2); - finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - - finishIcon.getAttribute('height')); - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); - - // Reset pegman's visibility. - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('opacity', 1); - pegmanIcon.setAttribute('visibility', 'visible'); - - // Reset the obstacle image. - var obsId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - var obsIcon = document.getElementById('obstacle' + obsId); - if (obsIcon) { - obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.obstacleIdle); - } - ++obsId; - } - } - -}; - - -/** - * Iterate through the recorded path and animate pegman's actions. - */ -Maze.animate = function() { - var action = Maze.log.shift(); - if (!action) { - // for (var x = 0; x < Maze.pidList.length; x++) { - // window.clearTimeout(Maze.pidList[x]); - // } - return; - } - - switch (action[0]) { - case 'north': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); - Maze.pegmanY--; - break; - case 'east': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); - Maze.pegmanX++; - break; - case 'south': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); - Maze.pegmanY++; - break; - case 'west': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); - Maze.pegmanX--; - break; - case 'look_north': - Maze.scheduleLook(Maze.DirectionType.NORTH); - break; - case 'look_east': - Maze.scheduleLook(Maze.DirectionType.EAST); - break; - case 'look_south': - Maze.scheduleLook(Maze.DirectionType.SOUTH); - break; - case 'look_west': - Maze.scheduleLook(Maze.DirectionType.WEST); - break; - case 'fail_forward': - Maze.scheduleFail(true); - break; - case 'fail_backward': - Maze.scheduleFail(false); - break; - case 'left': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); - break; - case 'right': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); - break; - case 'finish': - Maze.scheduleFinish(true); - break; - // TODO maybe add this - // case 'plant': - // Maze.animatePlant(); - // break; - } -}; - -/** - * Schedule the animations for a move or turn. - * @param {!Array.} startPos X, Y and direction starting points. - * @param {!Array.} endPos X, Y and direction ending points. - */ -Maze.schedule = function(startPos, endPos) { - var deltas = [(endPos[0] - startPos[0]) / 4, - (endPos[1] - startPos[1]) / 4, - (endPos[2] - startPos[2]) / 4 - ]; - Maze.displayPegman(startPos[0] + deltas[0], - startPos[1] + deltas[1], - Maze.constrainDirection16(startPos[2] + deltas[2])); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(startPos[0] + deltas[0] * 2, - startPos[1] + deltas[1] * 2, - Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(startPos[0] + deltas[0] * 3, - startPos[1] + deltas[1] * 3, - Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(endPos[0], endPos[1], - Maze.constrainDirection16(endPos[2])); - }, window.stepSpeed * 3)); - - if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { - Maze.pidList.push(setTimeout(function() { - var finishIcon = document.getElementById('finish'); - if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); - Blockly.getMainWorkspace().playAudio('win', 0.3); - } - }, window.stepSpeed * 4)); - } -}; - -/** - * Schedule the animations and sounds for a failed move. - * @param {boolean} forward True if forward, false if backward. - */ -Maze.scheduleFail = function(forward) { - var deltaX = 0; - var deltaY = 0; - switch (Maze.pegmanD) { - case Maze.DirectionType.NORTH: - deltaY = -1; - break; - case Maze.DirectionType.EAST: - deltaX = 1; - break; - case Maze.DirectionType.SOUTH: - deltaY = 1; - break; - case Maze.DirectionType.WEST: - deltaX = -1; - break; - } - if (!forward) { - deltaX = -deltaX; - deltaY = -deltaY; - } - - var targetX = Maze.pegmanX + deltaX * 2; - var targetY = Maze.pegmanY + deltaY * 2; - var squareType = Maze.map[targetY][targetX]; - - if (squareType === Maze.SquareType.OBSTACLE) { - BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); - // Play the sound - Blockly.getMainWorkspace().playAudio('obstacle'); - - // Play the animation - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - var obsId = targetX + Maze.COLS * targetY; - var obsIcon = document.getElementById('obstacle' + obsId); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.obstacleAnimation); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX + deltaX / 2, - Maze.pegmanY + deltaY / 2, - direction16); - }, window.stepSpeed)); - - - var pegmanIcon = document.getElementById('pegman'); - - Maze.pidList.push(setTimeout(function() { - pegmanIcon.setAttribute('visibility', 'hidden'); - }, window.stepSpeed * 2)); - - Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('failure'); - }, window.stepSpeed)); - } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { - BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); - // Bounce bounce. - deltaX /= 4; - deltaY /= 4; - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - Maze.displayPegman(Maze.pegmanX + deltaX, - Maze.pegmanY + deltaY, - direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, - Maze.pegmanY, - direction16); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX + deltaX, - Maze.pegmanY + deltaY, - direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); - }, window.stepSpeed * 3)); - } else { - // Add a small random delta away from the grid. - var deltaZ = (Math.random() - 0.5) * 10; - var deltaD = (Math.random() - 0.5) / 2; - deltaX += (Math.random() - 0.5) / 4; - deltaY += (Math.random() - 0.5) / 4; - deltaX /= 8; - deltaY /= 8; - var acceleration = 0; - if (Maze.SKIN.crashType == Maze.CRASH_FALL) { - acceleration = 0.01; - } - Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('fail', 0.5); - }, window.stepSpeed * 2)); - var setPosition = function(n) { - return function() { - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + - deltaD * n); - Maze.displayPegman(Maze.pegmanX + deltaX * n, - Maze.pegmanY + deltaY * n, - direction16, - deltaZ * n); - deltaY += acceleration; - }; - }; - // 100 frames should get Pegman offscreen. - for (var i = 1; i < 100; i++) { - Maze.pidList.push(setTimeout(setPosition(i), - window.stepSpeed * i / 2)); - } - } -}; - -/** - * Schedule the animations and sound for a victory dance. - * @param {boolean} sound Play the victory sound. - */ -Maze.scheduleFinish = function(sound) { - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); - if (sound) { - Blockly.getMainWorkspace().playAudio('win', 0.5); - } - window.stepSpeed = 250; // Slow down victory animation a bit. - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); - }, window.stepSpeed * 3)); -}; - -/** - * Display Pegman at the specified location, facing the specified direction. - * @param {number} x Horizontal grid (or fraction thereof). - * @param {number} y Vertical grid (or fraction thereof). - * @param {number} d Direction (0 - 15) or dance (16 - 17). - * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. - */ -Maze.displayPegman = function(x, y, d, opt_angle) { - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('x', - x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); - pegmanIcon.setAttribute('y', - Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); - if (opt_angle) { - pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + - (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + - (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); - } else { - pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); - } - - var clipRect = document.getElementById('clipRect'); - clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); - clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); -}; - -/** - * Display the look icon at Pegman's current location, - * in the specified direction. - * @param {!Maze.DirectionType} d Direction (0 - 3). - */ -Maze.scheduleLook = function(d) { - var x = Maze.pegmanX; - var y = Maze.pegmanY; - switch (d) { - case Maze.DirectionType.NORTH: - x += 0.5; - break; - case Maze.DirectionType.EAST: - x += 1; - y += 0.5; - break; - case Maze.DirectionType.SOUTH: - x += 0.5; - y += 1; - break; - case Maze.DirectionType.WEST: - y += 0.5; - break; - } - x *= Maze.SQUARE_SIZE; - y *= Maze.SQUARE_SIZE; - d = d * 90 - 45; - - var lookIcon = document.getElementById('look'); - lookIcon.setAttribute('transform', - 'translate(' + x + ', ' + y + ') ' + - 'rotate(' + d + ' 0 0) scale(.4)'); - var paths = lookIcon.getElementsByTagName('path'); - lookIcon.style.display = 'inline'; - for (var x = 0, path; path = paths[x]; x++) { - Maze.scheduleLookStep(path, window.stepSpeed * x); - } -}; - -/** - * Schedule one of the 'look' icon's waves to appear, then disappear. - * @param {!Element} path Element to make appear. - * @param {number} delay Milliseconds to wait before making wave appear. - */ -Maze.scheduleLookStep = function(path, delay) { - Maze.pidList.push(setTimeout(function() { - path.style.display = 'inline'; - setTimeout(function() { - path.style.display = 'none'; - }, window.stepSpeed * 2); - }, delay)); -}; - -/** - * Keep the direction within 0-3, wrapping at both ends. - * @param {number} d Potentially out-of-bounds direction value. - * @return {number} Legal direction value. - */ -Maze.constrainDirection4 = function(d) { - d = Math.round(d) % 4; - if (d < 0) { - d += 4; - } - return d; -}; - -/** - * Keep the direction within 0-15, wrapping at both ends. - * @param {number} d Potentially out-of-bounds direction value. - * @return {number} Legal direction value. - */ -Maze.constrainDirection16 = function(d) { - d = Math.round(d) % 16; - if (d < 0) { - d += 16; - } - return d; -}; - -// Core functions. - -/** - * Attempt to move pegman forward or backward. - * @param {number} direction Direction to move (0 = forward, 2 = backward). - * @param {string} id ID of block that triggered this action. - * @throws {true} If the end of the maze is reached. - * @throws {false} If Pegman collides with a wall. - */ -Maze.move = function(direction, id) { - var isNotAPath = !Maze.isPath(direction, null); - if (isNotAPath) { - Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); - Maze.result = Maze.ResultType.ERROR; - } - // If moving backward, flip the effective direction. - var effectiveDirection = Maze.pegmanD + direction; - var command; - switch (Maze.constrainDirection4(effectiveDirection)) { - case Maze.DirectionType.NORTH: - if (isNotAPath) Maze.pegmanY++; - command = 'north'; - break; - case Maze.DirectionType.EAST: - if (isNotAPath) Maze.pegmanX--; - command = 'east'; - break; - case Maze.DirectionType.SOUTH: - if (isNotAPath) Maze.pegmanY--; - command = 'south'; - break; - case Maze.DirectionType.WEST: - if (isNotAPath) Maze.pegmanX++; - command = 'west'; - break; - } - Maze.log.push([command, id]); - - // TODO maybe add this - // if (Maze.shouldCheckSuccessOnMove()) { - // Maze.checkSuccess(); - // } -}; - -/** - * Turn pegman left or right. - * @param {number} direction Direction to turn (0 = left, 1 = right). - * @param {string} id ID of block that triggered this action. - */ -Maze.turn = function(direction, id) { - if (direction) { - // Right turn (clockwise). - // Maze.pegmanD++; - Maze.log.push(['right', id]); - } else { - // Left turn (counterclockwise). - // Maze.pegmanD--; - Maze.log.push(['left', id]); - } - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); -}; - -/** - * Is there a path next to pegman? - * @param {number} direction Direction to look - * (0 = forward, 1 = right, 2 = backward, 3 = left). - * @param {?string} id ID of block that triggered this action. - * Null if called as a helper function in Maze.move(). - * @return {boolean} True if there is a path. - */ -Maze.isPath = function(direction, id) { - var effectiveDirection = Maze.pegmanD + direction; - var square; - var command; - switch (Maze.constrainDirection4(effectiveDirection)) { - case Maze.DirectionType.NORTH: - square = Maze.map[Maze.pegmanY - 1] && - Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; - command = 'look_north'; - break; - case Maze.DirectionType.EAST: - square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; - command = 'look_east'; - break; - case Maze.DirectionType.SOUTH: - square = Maze.map[Maze.pegmanY + 1] && - Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; - command = 'look_south'; - break; - case Maze.DirectionType.WEST: - square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; - command = 'look_west'; - break; - } - if (id) { - Maze.log.push([command, id]); - } - return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; -}; - -/** - * Is the player at the finish marker? - * @return {boolean} True if not done, false if done. - */ -Maze.notDone = function() { - return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; -}; - -if (document.getElementById('blocklySvgZone') != null) { - window.addEventListener('load', Maze.init); -} else { - console.warn('Cannot find blocklySvgZone element.'); -} diff --git a/Cours 1/Lecon1/11_maze/student/maze.tpl.py b/Cours 1/Lecon1/11_maze/student/maze.tpl.py deleted file mode 100644 index 9c056ff..0000000 --- a/Cours 1/Lecon1/11_maze/student/maze.tpl.py +++ /dev/null @@ -1,191 +0,0 @@ -''' -This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. -Try to forget the basic Python syntax for a while. -''' - - -class BadPathException(Exception): - pass - -MAP = [[0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 3, 0], - [0, 0, 0, 0, 0, 1, 1, 0], - [0, 0, 0, 0, 1, 1, 0, 0], - [0, 0, 0, 1, 1, 0, 0, 0], - [0, 0, 1, 1, 0, 0, 0, 0], - [0, 2, 1, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0]] - -ROWS = len(MAP) -COLS = len(MAP[0]) - -UNSET = "UNSET" -SUCCESS = "SUCCESS" -FAILURE = "FAILURE" -TIMEOUT = "TIMEOUT" -ERROR = "ERROR" - -RESULT_TYPE = { - UNSET: 0, - SUCCESS: 1, - FAILURE: -1, - TIMEOUT: 2, - ERROR: -2 -} - -RESULT = RESULT_TYPE[UNSET] - -WALL = "WALL" -OPEN = "OPEN" -START = "START" -FINISH = "FINISH" -OBSTACLE = "OBSTACLE" - -SQUARE_TYPE = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4 -} - -PLAYER_POSITION = { - 'x': None, - 'y': None -} - -FINISH_POSITION = { - 'x': None, - 'y': None -} - -for y in range(ROWS): - for x in range(COLS): - if MAP[y][x] == SQUARE_TYPE[START]: - PLAYER_POSITION['x'] = x - PLAYER_POSITION['y'] = y - if MAP[y][x] == SQUARE_TYPE[FINISH]: - FINISH_POSITION['x'] = x - FINISH_POSITION['y'] = y - -EAST = "east" -SOUTH = "south" -WEST = "west" -NORTH = "north" - -DIRECTION_TYPE = { - NORTH: 0, - EAST: 1, - SOUTH: 2, - WEST: 3 -} - -MOVE_POSITION = { - DIRECTION_TYPE[EAST]: { - 'x': 1, - 'y': 0 - }, - DIRECTION_TYPE[SOUTH]: { - 'x': 0, - 'y': 1 - }, - DIRECTION_TYPE[WEST]: { - 'x': -1, - 'y': 0 - }, - DIRECTION_TYPE[NORTH]: { - 'x': 0, - 'y': -1 - } -} - -PLAYER_ORIENTATION = DIRECTION_TYPE[EAST] - - -def student_code(): -@ @code@@ - - -def constrain_direction4(direction): - d = direction % 4 - if d < 0: - d += 4 - return d - - -def isPath(direction): - global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE - effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) - test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] - test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] - if test_x < 0 or test_x >= COLS: - return False - elif test_y < 0 or test_y >= ROWS: - return False - else: - return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] - - -def isPathForward(): - return isPath(0) - - -def isPathRight(): - return isPath(1) - - -def isPathBackward(): - return isPath(2) - - -def isPathLeft(): - return isPath(3) - - -def moveForward(): - global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION - if isPathForward(): - PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] - PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] - else: - raise BadPathException() - - -def turnLeft(): - global PLAYER_ORIENTATION - PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], - DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], - DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], - DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] - }[PLAYER_ORIENTATION] - - -def turnRight(): - global PLAYER_ORIENTATION - PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], - DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], - DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], - DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] - }[PLAYER_ORIENTATION] - - -def isDone(): - global PLAYER_POSITION, FINISH_POSITION - if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: - return True - else: - return False - - -def notDone(): - return not isDone() - - -try: - student_code() - if isDone(): - print("True", end='', flush=True) - else: - print("Il y a une erreur dans votre code.", end='', flush=True) -except BadPathException: - print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1/Lecon1/12_maze/public/maze.js b/Cours 1/Lecon1/12_maze/public/maze.js deleted file mode 100644 index 33bd06d..0000000 --- a/Cours 1/Lecon1/12_maze/public/maze.js +++ /dev/null @@ -1,905 +0,0 @@ -/** - * Blockly Games: Maze - * - * Copyright 2012 Google Inc. - * https://github.com/google/blockly-games - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview JavaScript for Blockly's Maze application. - * @author fraser@google.com (Neil Fraser) - */ -"use strict"; - -var task_directory_path = window.location.pathname + "/"; - -window.Maze = {}; - -Maze.MAX_BLOCKS = Infinity; - -// Crash type constants. -Maze.CRASH_STOP = 1; -Maze.CRASH_SPIN = 2; -Maze.CRASH_FALL = 3; - -Maze.SKIN = { - sprite: task_directory_path + 'maze/avatar.png', - tiles: task_directory_path + 'maze/tiles.png', - marker: task_directory_path + 'maze/goalIdle.gif', - goalAnimation: task_directory_path + 'maze/goal.gif', - obstacleIdle: task_directory_path + 'maze/obstacleIdle.gif', - obstacleAnimation: task_directory_path + 'maze/obstacle.gif', - obstacleScale: 1.4, - background: task_directory_path + 'maze/background.png', - graph: false, - look: '#000', - obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], - winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], - crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], - crashType: Maze.CRASH_STOP -}; - -/** - * Milliseconds between each animation frame. - */ -window.stepSpeed = 250; - -/** - * The types of squares in the maze, which is represented - * as a 2D array of SquareType values. - * @enum {number} - */ -Maze.SquareType = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4, - STARTANDFINISH: 5 -}; - -// The maze square constants defined above are inlined here -// for ease of reading and writing the static mazes. -Maze.map = - // Level 12. - [ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 2, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 0, 0, 0, 0, 0], - [0, 0, 1, 1, 0, 0, 0, 0], - [0, 0, 0, 1, 1, 0, 0, 0], - [0, 0, 0, 0, 1, 1, 0, 0], - [0, 0, 0, 0, 0, 1, 3, 0], - [0, 0, 0, 0, 0, 0, 0, 0] - ]; - -/** - * Measure maze dimensions and set sizes. - * ROWS: Number of tiles down. - * COLS: Number of tiles across. - * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). - */ -Maze.ROWS = Maze.map.length; -Maze.COLS = Maze.map[0].length; -Maze.SQUARE_SIZE = 50; -Maze.PEGMAN_HEIGHT = 52; -Maze.PEGMAN_WIDTH = 49; - -Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; -Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; -Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; - -/** - * Constants for cardinal directions. Subsequent code assumes these are - * in the range 0..3 and that opposites have an absolute difference of 2. - * @enum {number} - */ -Maze.DirectionType = { - NORTH: 0, - EAST: 1, - SOUTH: 2, - WEST: 3 -}; - -/** - * Outcomes of running the user program. - */ -Maze.ResultType = { - UNSET: 0, - SUCCESS: 1, - FAILURE: -1, - TIMEOUT: 2, - ERROR: -2 -}; - -/** - * Result of last execution. - */ -Maze.result = Maze.ResultType.UNSET; - -/** - * Starting direction. - */ -Maze.startDirection = Maze.DirectionType.EAST; - -/** - * PIDs of animation tasks currently executing. - */ -Maze.pidList = []; - -// Map each possible shape to a sprite. -// Input: Binary string representing Centre/North/West/South/East squares. -// Output: [x, y] coordinates of each tile's sprite in tiles.png. -Maze.tile_SHAPES = { - '10010': [4, 0], // Dead ends - '10001': [3, 3], - '11000': [0, 1], - '10100': [0, 2], - '11010': [4, 1], // Vertical - '10101': [3, 2], // Horizontal - '10110': [0, 0], // Elbows - '10011': [2, 0], - '11001': [4, 2], - '11100': [2, 3], - '11110': [1, 1], // Junctions - '10111': [1, 0], - '11011': [2, 1], - '11101': [1, 2], - '11111': [2, 2], // Cross - 'null0': [4, 3], // Empty - 'null1': [3, 0], - 'null2': [3, 1], - 'null3': [0, 3], - 'null4': [1, 3] -}; - -/** - * Create and layout all the nodes for the path, scenery, Pegman, and goal. - */ -Maze.drawMap = function() { - var svg = document.getElementById('blocklySvgZone'); - var x, y, tile; - var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; - svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); - svg.setAttribute('style', ''); - - // Draw the outer square. - var square = document.createElementNS(Blockly.SVG_NS, 'rect'); - square.setAttribute('width', Maze.MAZE_WIDTH); - square.setAttribute('height', Maze.MAZE_HEIGHT); - square.setAttribute('fill', '#F1EEE7'); - square.setAttribute('stroke-width', 1); - square.setAttribute('stroke', '#CCB'); - svg.appendChild(square); - - if (Maze.SKIN.background) { - var tile = document.createElementNS(Blockly.SVG_NS, 'image'); - tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.background); - tile.setAttribute('height', Maze.MAZE_HEIGHT); - tile.setAttribute('width', Maze.MAZE_WIDTH); - tile.setAttribute('x', 0); - tile.setAttribute('y', 0); - svg.appendChild(tile); - } - - if (Maze.SKIN.graph) { - // Draw the grid lines. - // The grid lines are offset so that the lines pass through the centre of - // each square. A half-pixel offset is also added to as standard SVG - // practice to avoid blurriness. - var offset = Maze.SQUARE_SIZE / 2 + 0.5; - for (var k = 0; k < Maze.ROWS; k++) { - var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); - h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('x2', Maze.MAZE_WIDTH); - h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); - h_line.setAttribute('stroke', Maze.SKIN.graph); - h_line.setAttribute('stroke-width', 1); - svg.appendChild(h_line); - } - for (var k = 0; k < Maze.COLS; k++) { - var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); - v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); - v_line.setAttribute('y2', Maze.MAZE_HEIGHT); - v_line.setAttribute('stroke', Maze.SKIN.graph); - v_line.setAttribute('stroke-width', 1); - svg.appendChild(v_line); - } - } - - // Draw the tiles making up the maze map. - - // Return a value of '0' if the specified square is wall or out of bounds, - // '1' otherwise (empty, start, finish). - var normalize = function(x, y) { - if (x < 0 || x >= Maze.COLS || y < 0 || y >= Maze.ROWS) { - return '0'; - } - return (Maze.map[y][x] == Maze.SquareType.WALL) ? '0' : '1'; - }; - - // Compute and draw the tile for each square. - var tileId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - // Compute the tile index. - tile = normalize(x, y) + - normalize(x, y - 1) + // North. - normalize(x + 1, y) + // West. - normalize(x, y + 1) + // South. - normalize(x - 1, y); // East. - - // Draw the tile. - if (!Maze.tile_SHAPES[tile]) { - // Empty square. Use null0 for large areas, with null1-4 for borders. - // Add some randomness to avoid large empty spaces. - if (tile == '00000' && Math.random() > 0.3) { - tile = 'null0'; - } else { - tile = 'null' + Math.floor(1 + Math.random() * 4); - } - } - var left = Maze.tile_SHAPES[tile][0]; - var top = Maze.tile_SHAPES[tile][1]; - // Tile's clipPath element. - var tileClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); - tileClip.setAttribute('id', 'tileClipPath' + tileId); - var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); - clipRect.setAttribute('width', Maze.SQUARE_SIZE); - clipRect.setAttribute('height', Maze.SQUARE_SIZE); - - clipRect.setAttribute('x', x * Maze.SQUARE_SIZE); - clipRect.setAttribute('y', y * Maze.SQUARE_SIZE); - - tileClip.appendChild(clipRect); - svg.appendChild(tileClip); - // Tile sprite. - tile = document.createElementNS(Blockly.SVG_NS, 'image'); - tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.tiles); - // Position the tile sprite relative to the clipRect. - tile.setAttribute('height', Maze.SQUARE_SIZE * 4); - tile.setAttribute('width', Maze.SQUARE_SIZE * 5); - tile.setAttribute('clip-path', 'url(#tileClipPath' + tileId + ')'); - tile.setAttribute('x', (x - left) * Maze.SQUARE_SIZE); - tile.setAttribute('y', (y - top) * Maze.SQUARE_SIZE); - svg.appendChild(tile); - tileId++; - } - } - - // Add finish marker. - var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); - finishMarker.setAttribute('id', 'finish'); - finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.marker); - finishMarker.setAttribute('height', 43); - finishMarker.setAttribute('width', 50); - svg.appendChild(finishMarker); - - // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman - var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); - pegmanClip.setAttribute('id', 'pegmanClipPath'); - var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); - clipRect.setAttribute('id', 'clipRect'); - clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); - clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); - pegmanClip.appendChild(clipRect); - svg.appendChild(pegmanClip); - - // Add obstacles. - var obsId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { - var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); - obsIcon.setAttribute('id', 'obstacle' + obsId); - obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); - obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); - obsIcon.setAttribute('x', - Maze.SQUARE_SIZE * (x + 0.5) - - obsIcon.getAttribute('width') / 2); - obsIcon.setAttribute('y', - Maze.SQUARE_SIZE * (y + 0.9) - - obsIcon.getAttribute('height')); - svg.appendChild(obsIcon); - } - ++obsId; - } - } - - // Add Pegman. - var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); - pegmanIcon.setAttribute('id', 'pegman'); - pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.sprite); - pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); - pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 - pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); - svg.appendChild(pegmanIcon); -}; - -/** - * Initialize Blockly and the maze. Called on page load. - */ -Maze.init = function() { - - if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { - console.warn("Maze.init() called but Blockly or workspace was not loaded."); - window.setTimeout(Maze.init, 20); - return; - } - - // - // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); - // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); - - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.winSound, 'win'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.crashSound, 'fail'); - Blockly.getMainWorkspace().loadAudio_(Maze.SKIN.obstacleSound, 'obstacle'); - // Not really needed, there are no user-defined functions or variables. - Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + - 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); - - Maze.drawMap(); - - // Locate the start and finish squares. - for (var y = 0; y < Maze.ROWS; y++) { - for (var x = 0; x < Maze.COLS; x++) { - if (Maze.map[y][x] == Maze.SquareType.START) { - Maze.start_ = { - x: x, - y: y - }; - } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { - Maze.finish_ = { - x: x, - y: y - }; - } - } - } - - Maze.reset(true); - - // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); - - // Switch to zero-based indexing so that later JS levels match the blocks. - Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); - Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); -}; - -/** - * Reset the maze to the start position and kill any pending animation tasks. - * @param {boolean} first True if an opening animation is to be played. - */ -Maze.reset = function(first) { - var x, y; - - // Kill all tasks. - for (x = 0; x < Maze.pidList.length; x++) { - window.clearTimeout(Maze.pidList[x]); - } - Maze.pidList = []; - - // Move Pegman into position. - Maze.pegmanX = Maze.start_.x; - Maze.pegmanY = Maze.start_.y; - - if (first) { - Maze.pegmanD = Maze.startDirection + 1; - Maze.scheduleFinish(false); - Maze.pidList.push(setTimeout(function() { - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); - Maze.pegmanD++; - }, window.stepSpeed * 5)); - } else { - Maze.pegmanD = Maze.startDirection; - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); - } - - // Move the finish icon into position. - var finishIcon = document.getElementById('finish'); - finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x + 0.5) - - finishIcon.getAttribute('width') / 2); - finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y + 0.6) - - finishIcon.getAttribute('height')); - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); - - // Reset pegman's visibility. - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('opacity', 1); - pegmanIcon.setAttribute('visibility', 'visible'); - - // Reset the obstacle image. - var obsId = 0; - for (y = 0; y < Maze.ROWS; y++) { - for (x = 0; x < Maze.COLS; x++) { - var obsIcon = document.getElementById('obstacle' + obsId); - if (obsIcon) { - obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.obstacleIdle); - } - ++obsId; - } - } - -}; - - -/** - * Iterate through the recorded path and animate pegman's actions. - */ -Maze.animate = function() { - var action = Maze.log.shift(); - if (!action) { - // for (var x = 0; x < Maze.pidList.length; x++) { - // window.clearTimeout(Maze.pidList[x]); - // } - return; - } - - switch (action[0]) { - case 'north': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); - Maze.pegmanY--; - break; - case 'east': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); - Maze.pegmanX++; - break; - case 'south': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); - Maze.pegmanY++; - break; - case 'west': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); - Maze.pegmanX--; - break; - case 'look_north': - Maze.scheduleLook(Maze.DirectionType.NORTH); - break; - case 'look_east': - Maze.scheduleLook(Maze.DirectionType.EAST); - break; - case 'look_south': - Maze.scheduleLook(Maze.DirectionType.SOUTH); - break; - case 'look_west': - Maze.scheduleLook(Maze.DirectionType.WEST); - break; - case 'fail_forward': - Maze.scheduleFail(true); - break; - case 'fail_backward': - Maze.scheduleFail(false); - break; - case 'left': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); - break; - case 'right': - Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); - break; - case 'finish': - Maze.scheduleFinish(true); - break; - // TODO maybe add this - // case 'plant': - // Maze.animatePlant(); - // break; - } -}; - -/** - * Schedule the animations for a move or turn. - * @param {!Array.} startPos X, Y and direction starting points. - * @param {!Array.} endPos X, Y and direction ending points. - */ -Maze.schedule = function(startPos, endPos) { - var deltas = [(endPos[0] - startPos[0]) / 4, - (endPos[1] - startPos[1]) / 4, - (endPos[2] - startPos[2]) / 4 - ]; - Maze.displayPegman(startPos[0] + deltas[0], - startPos[1] + deltas[1], - Maze.constrainDirection16(startPos[2] + deltas[2])); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(startPos[0] + deltas[0] * 2, - startPos[1] + deltas[1] * 2, - Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(startPos[0] + deltas[0] * 3, - startPos[1] + deltas[1] * 3, - Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(endPos[0], endPos[1], - Maze.constrainDirection16(endPos[2])); - }, window.stepSpeed * 3)); - - if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { - Maze.pidList.push(setTimeout(function() { - var finishIcon = document.getElementById('finish'); - if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { - finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); - Blockly.getMainWorkspace().playAudio('win', 0.3); - } - }, window.stepSpeed * 4)); - } -}; - -/** - * Schedule the animations and sounds for a failed move. - * @param {boolean} forward True if forward, false if backward. - */ -Maze.scheduleFail = function(forward) { - var deltaX = 0; - var deltaY = 0; - switch (Maze.pegmanD) { - case Maze.DirectionType.NORTH: - deltaY = -1; - break; - case Maze.DirectionType.EAST: - deltaX = 1; - break; - case Maze.DirectionType.SOUTH: - deltaY = 1; - break; - case Maze.DirectionType.WEST: - deltaX = -1; - break; - } - if (!forward) { - deltaX = -deltaX; - deltaY = -deltaY; - } - - var targetX = Maze.pegmanX + deltaX * 2; - var targetY = Maze.pegmanY + deltaY * 2; - var squareType = Maze.map[targetY][targetX]; - - if (squareType === Maze.SquareType.OBSTACLE) { - BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); - // Play the sound - Blockly.getMainWorkspace().playAudio('obstacle'); - - // Play the animation - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - var obsId = targetX + Maze.COLS * targetY; - var obsIcon = document.getElementById('obstacle' + obsId); - obsIcon.setAttributeNS( - 'http://www.w3.org/1999/xlink', 'xlink:href', - Maze.SKIN.obstacleAnimation); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX + deltaX / 2, - Maze.pegmanY + deltaY / 2, - direction16); - }, window.stepSpeed)); - - - var pegmanIcon = document.getElementById('pegman'); - - Maze.pidList.push(setTimeout(function() { - pegmanIcon.setAttribute('visibility', 'hidden'); - }, window.stepSpeed * 2)); - - Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('failure'); - }, window.stepSpeed)); - } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { - BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); - // Bounce bounce. - deltaX /= 4; - deltaY /= 4; - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - Maze.displayPegman(Maze.pegmanX + deltaX, - Maze.pegmanY + deltaY, - direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, - Maze.pegmanY, - direction16); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX + deltaX, - Maze.pegmanY + deltaY, - direction16); - Blockly.getMainWorkspace().playAudio('fail', 0.5); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); - }, window.stepSpeed * 3)); - } else { - // Add a small random delta away from the grid. - var deltaZ = (Math.random() - 0.5) * 10; - var deltaD = (Math.random() - 0.5) / 2; - deltaX += (Math.random() - 0.5) / 4; - deltaY += (Math.random() - 0.5) / 4; - deltaX /= 8; - deltaY /= 8; - var acceleration = 0; - if (Maze.SKIN.crashType == Maze.CRASH_FALL) { - acceleration = 0.01; - } - Maze.pidList.push(setTimeout(function() { - Blockly.getMainWorkspace().playAudio('fail', 0.5); - }, window.stepSpeed * 2)); - var setPosition = function(n) { - return function() { - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + - deltaD * n); - Maze.displayPegman(Maze.pegmanX + deltaX * n, - Maze.pegmanY + deltaY * n, - direction16, - deltaZ * n); - deltaY += acceleration; - }; - }; - // 100 frames should get Pegman offscreen. - for (var i = 1; i < 100; i++) { - Maze.pidList.push(setTimeout(setPosition(i), - window.stepSpeed * i / 2)); - } - } -}; - -/** - * Schedule the animations and sound for a victory dance. - * @param {boolean} sound Play the victory sound. - */ -Maze.scheduleFinish = function(sound) { - var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); - if (sound) { - Blockly.getMainWorkspace().playAudio('win', 0.5); - } - window.stepSpeed = 250; // Slow down victory animation a bit. - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); - }, window.stepSpeed)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); - }, window.stepSpeed * 2)); - Maze.pidList.push(setTimeout(function() { - Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); - }, window.stepSpeed * 3)); -}; - -/** - * Display Pegman at the specified location, facing the specified direction. - * @param {number} x Horizontal grid (or fraction thereof). - * @param {number} y Vertical grid (or fraction thereof). - * @param {number} d Direction (0 - 15) or dance (16 - 17). - * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. - */ -Maze.displayPegman = function(x, y, d, opt_angle) { - var pegmanIcon = document.getElementById('pegman'); - pegmanIcon.setAttribute('x', - x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); - pegmanIcon.setAttribute('y', - Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2 - 8); - if (opt_angle) { - pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + - (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + - (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); - } else { - pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); - } - - var clipRect = document.getElementById('clipRect'); - clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); - clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); -}; - -/** - * Display the look icon at Pegman's current location, - * in the specified direction. - * @param {!Maze.DirectionType} d Direction (0 - 3). - */ -Maze.scheduleLook = function(d) { - var x = Maze.pegmanX; - var y = Maze.pegmanY; - switch (d) { - case Maze.DirectionType.NORTH: - x += 0.5; - break; - case Maze.DirectionType.EAST: - x += 1; - y += 0.5; - break; - case Maze.DirectionType.SOUTH: - x += 0.5; - y += 1; - break; - case Maze.DirectionType.WEST: - y += 0.5; - break; - } - x *= Maze.SQUARE_SIZE; - y *= Maze.SQUARE_SIZE; - d = d * 90 - 45; - - var lookIcon = document.getElementById('look'); - lookIcon.setAttribute('transform', - 'translate(' + x + ', ' + y + ') ' + - 'rotate(' + d + ' 0 0) scale(.4)'); - var paths = lookIcon.getElementsByTagName('path'); - lookIcon.style.display = 'inline'; - for (var x = 0, path; path = paths[x]; x++) { - Maze.scheduleLookStep(path, window.stepSpeed * x); - } -}; - -/** - * Schedule one of the 'look' icon's waves to appear, then disappear. - * @param {!Element} path Element to make appear. - * @param {number} delay Milliseconds to wait before making wave appear. - */ -Maze.scheduleLookStep = function(path, delay) { - Maze.pidList.push(setTimeout(function() { - path.style.display = 'inline'; - setTimeout(function() { - path.style.display = 'none'; - }, window.stepSpeed * 2); - }, delay)); -}; - -/** - * Keep the direction within 0-3, wrapping at both ends. - * @param {number} d Potentially out-of-bounds direction value. - * @return {number} Legal direction value. - */ -Maze.constrainDirection4 = function(d) { - d = Math.round(d) % 4; - if (d < 0) { - d += 4; - } - return d; -}; - -/** - * Keep the direction within 0-15, wrapping at both ends. - * @param {number} d Potentially out-of-bounds direction value. - * @return {number} Legal direction value. - */ -Maze.constrainDirection16 = function(d) { - d = Math.round(d) % 16; - if (d < 0) { - d += 16; - } - return d; -}; - -// Core functions. - -/** - * Attempt to move pegman forward or backward. - * @param {number} direction Direction to move (0 = forward, 2 = backward). - * @param {string} id ID of block that triggered this action. - * @throws {true} If the end of the maze is reached. - * @throws {false} If Pegman collides with a wall. - */ -Maze.move = function(direction, id) { - var isNotAPath = !Maze.isPath(direction, null); - if (isNotAPath) { - Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); - Maze.result = Maze.ResultType.ERROR; - } - // If moving backward, flip the effective direction. - var effectiveDirection = Maze.pegmanD + direction; - var command; - switch (Maze.constrainDirection4(effectiveDirection)) { - case Maze.DirectionType.NORTH: - if (isNotAPath) Maze.pegmanY++; - command = 'north'; - break; - case Maze.DirectionType.EAST: - if (isNotAPath) Maze.pegmanX--; - command = 'east'; - break; - case Maze.DirectionType.SOUTH: - if (isNotAPath) Maze.pegmanY--; - command = 'south'; - break; - case Maze.DirectionType.WEST: - if (isNotAPath) Maze.pegmanX++; - command = 'west'; - break; - } - Maze.log.push([command, id]); - - // TODO maybe add this - // if (Maze.shouldCheckSuccessOnMove()) { - // Maze.checkSuccess(); - // } -}; - -/** - * Turn pegman left or right. - * @param {number} direction Direction to turn (0 = left, 1 = right). - * @param {string} id ID of block that triggered this action. - */ -Maze.turn = function(direction, id) { - if (direction) { - // Right turn (clockwise). - // Maze.pegmanD++; - Maze.log.push(['right', id]); - } else { - // Left turn (counterclockwise). - // Maze.pegmanD--; - Maze.log.push(['left', id]); - } - Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); -}; - -/** - * Is there a path next to pegman? - * @param {number} direction Direction to look - * (0 = forward, 1 = right, 2 = backward, 3 = left). - * @param {?string} id ID of block that triggered this action. - * Null if called as a helper function in Maze.move(). - * @return {boolean} True if there is a path. - */ -Maze.isPath = function(direction, id) { - var effectiveDirection = Maze.pegmanD + direction; - var square; - var command; - switch (Maze.constrainDirection4(effectiveDirection)) { - case Maze.DirectionType.NORTH: - square = Maze.map[Maze.pegmanY - 1] && - Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; - command = 'look_north'; - break; - case Maze.DirectionType.EAST: - square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; - command = 'look_east'; - break; - case Maze.DirectionType.SOUTH: - square = Maze.map[Maze.pegmanY + 1] && - Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; - command = 'look_south'; - break; - case Maze.DirectionType.WEST: - square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; - command = 'look_west'; - break; - } - if (id) { - Maze.log.push([command, id]); - } - return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; -}; - -/** - * Is the player at the finish marker? - * @return {boolean} True if not done, false if done. - */ -Maze.notDone = function() { - return Maze.pegmanX != Maze.finish_.x || Maze.pegmanY != Maze.finish_.y; -}; - -if (document.getElementById('blocklySvgZone') != null) { - window.addEventListener('load', Maze.init); -} else { - console.warn('Cannot find blocklySvgZone element.'); -} diff --git a/Cours 1/Lecon1/12_maze/student/maze.tpl.py b/Cours 1/Lecon1/12_maze/student/maze.tpl.py deleted file mode 100644 index bc37a8b..0000000 --- a/Cours 1/Lecon1/12_maze/student/maze.tpl.py +++ /dev/null @@ -1,191 +0,0 @@ -''' -This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. -Try to forget the basic Python syntax for a while. -''' - - -class BadPathException(Exception): - pass - -MAP = [[0, 0, 0, 0, 0, 0, 0, 0], - [0, 2, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 0, 0, 0, 0, 0], - [0, 0, 1, 1, 0, 0, 0, 0], - [0, 0, 0, 1, 1, 0, 0, 0], - [0, 0, 0, 0, 1, 1, 0, 0], - [0, 0, 0, 0, 0, 1, 3, 0], - [0, 0, 0, 0, 0, 0, 0, 0]] - -ROWS = len(MAP) -COLS = len(MAP[0]) - -UNSET = "UNSET" -SUCCESS = "SUCCESS" -FAILURE = "FAILURE" -TIMEOUT = "TIMEOUT" -ERROR = "ERROR" - -RESULT_TYPE = { - UNSET: 0, - SUCCESS: 1, - FAILURE: -1, - TIMEOUT: 2, - ERROR: -2 -} - -RESULT = RESULT_TYPE[UNSET] - -WALL = "WALL" -OPEN = "OPEN" -START = "START" -FINISH = "FINISH" -OBSTACLE = "OBSTACLE" - -SQUARE_TYPE = { - WALL: 0, - OPEN: 1, - START: 2, - FINISH: 3, - OBSTACLE: 4 -} - -PLAYER_POSITION = { - 'x': None, - 'y': None -} - -FINISH_POSITION = { - 'x': None, - 'y': None -} - -for y in range(ROWS): - for x in range(COLS): - if MAP[y][x] == SQUARE_TYPE[START]: - PLAYER_POSITION['x'] = x - PLAYER_POSITION['y'] = y - if MAP[y][x] == SQUARE_TYPE[FINISH]: - FINISH_POSITION['x'] = x - FINISH_POSITION['y'] = y - -EAST = "east" -SOUTH = "south" -WEST = "west" -NORTH = "north" - -DIRECTION_TYPE = { - NORTH: 0, - EAST: 1, - SOUTH: 2, - WEST: 3 -} - -MOVE_POSITION = { - DIRECTION_TYPE[EAST]: { - 'x': 1, - 'y': 0 - }, - DIRECTION_TYPE[SOUTH]: { - 'x': 0, - 'y': 1 - }, - DIRECTION_TYPE[WEST]: { - 'x': -1, - 'y': 0 - }, - DIRECTION_TYPE[NORTH]: { - 'x': 0, - 'y': -1 - } -} - -PLAYER_ORIENTATION = DIRECTION_TYPE[EAST] - - -def student_code(): -@ @code@@ - - -def constrain_direction4(direction): - d = direction % 4 - if d < 0: - d += 4 - return d - - -def isPath(direction): - global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE - effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) - test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] - test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] - if test_x < 0 or test_x >= COLS: - return False - elif test_y < 0 or test_y >= ROWS: - return False - else: - return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] - - -def isPathForward(): - return isPath(0) - - -def isPathRight(): - return isPath(1) - - -def isPathBackward(): - return isPath(2) - - -def isPathLeft(): - return isPath(3) - - -def moveForward(): - global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION - if isPathForward(): - PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] - PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] - else: - raise BadPathException() - - -def turnLeft(): - global PLAYER_ORIENTATION - PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], - DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], - DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], - DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] - }[PLAYER_ORIENTATION] - - -def turnRight(): - global PLAYER_ORIENTATION - PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], - DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], - DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], - DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] - }[PLAYER_ORIENTATION] - - -def isDone(): - global PLAYER_POSITION, FINISH_POSITION - if PLAYER_POSITION['x'] == FINISH_POSITION['x'] and PLAYER_POSITION['y'] == FINISH_POSITION['y']: - return True - else: - return False - - -def notDone(): - return not isDone() - - -try: - student_code() - if isDone(): - print("True", end='', flush=True) - else: - print("Il y a une erreur dans votre code.", end='', flush=True) -except BadPathException: - print("Le personnage emprunte un chemin inexistant.") diff --git a/Cours 1/Lecon2/Artist_1/public/turtle.js b/Cours 1/Lecon2/Artist_1/public/turtle.js deleted file mode 100644 index 9fdc4bf..0000000 --- a/Cours 1/Lecon2/Artist_1/public/turtle.js +++ /dev/null @@ -1,344 +0,0 @@ -"use strict"; - -var task_directory_path = window.location.pathname + "/"; -window.Turtle = {}; -window.Maze = {}; - -//Get the json file and it's information -var turtle_file = "" -if(task_directory_path.includes("edit")){ //When we are editing the task - turtle_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"turtle_config.json" -}else { //When displaying the task - turtle_file = task_directory_path + "turtle_config.json"; -} -var request = new XMLHttpRequest(); -request.open("GET", turtle_file, false); -request.send(null) -var json = JSON.parse(request.responseText); - -//Code of the solution -var solution = function(){ - //Here, put the javascript corresponding to the solved exercice - var i; - for(i = 0; i < 4; i++){ - Turtle.move(250); - Turtle.turnRight(90); - } -} -//Code of the decor -var decoration = function(){ - //Here, put the code for any decor, not part of the exercice -} - -//Canvas size -Turtle.CANVAS_WIDTH = json.width; -Turtle.CANVAS_HEIGHT = json.height; - -//Starting position and radius of turtle -Turtle.START_X = json.startX; -Turtle.START_Y = json.startY; -Turtle.RADIUS = json.radius; - -//Current coordinates of turtle -Turtle.CURRENT_COORD = { - x:Turtle.START_X, - y:Turtle.START_Y -} - -//Current heading of turtle -Turtle.HEADING = json.startAngle; - -//Weater or not the pen is down and it's width -Turtle.PEN_DOWN = true; //At start, the pen is always down -Turtle.PEN_WIDTH = json.strokeWidth; -Turtle.PEN_COLOUR = json.strokeColour; - -//Variables used to draw on the solution canvas or the decor canvas -var sol = false; -var decor = false; - -//milisec between each frame -window.stepSpeed = json.animationRate; - -/** -* -* Animations functions -* -*/ - -Turtle.drawMap = function() { - var div = document.getElementById('visualization'); - - // Add the canvas for the turtle - var canvas = document.createElement('canvas'); - canvas.setAttribute('width', Turtle.CANVAS_WIDTH); - canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); - canvas.setAttribute("style", "border:1px solid #F1EEE7;") - canvas.setAttribute("id", "turtle-canvas"); - div.appendChild(canvas); - // Add the canvas for the user. - canvas = document.createElement('canvas'); - canvas.setAttribute('width', Turtle.CANVAS_WIDTH); - canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); - canvas.setAttribute('style', 'display: none'); - canvas.setAttribute("id", "user-canvas"); - div.appendChild(canvas); - // Add the canvas for the solution. - canvas = document.createElement('canvas'); - canvas.setAttribute('width', Turtle.CANVAS_WIDTH); - canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); - canvas.setAttribute('style', 'display: none'); - canvas.setAttribute("id", "solution-canvas"); - div.appendChild(canvas); - //Add the canvas for the decor - canvas = document.createElement('canvas'); - canvas.setAttribute('width', Turtle.CANVAS_WIDTH); - canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); - canvas.setAttribute('style', 'display: none') - canvas.setAttribute("id", "decor-canvas"); - div.appendChild(canvas) - - //Draw the decor - Turtle.drawDecor(); - - //Draw the solution - Turtle.drawSolution(); - - //Draw the turtle - Turtle.updateImage(); - - -} - -Turtle.drawTurtle = function(){ - var c = document.getElementById("turtle-canvas"); - var ctx = c.getContext("2d") - - //Draw the turtle body - ctx.beginPath(); - ctx.strokeStyle = Turtle.PEN_COLOUR; - ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, Turtle.RADIUS, 0, 2 * Math.PI); - ctx.stroke(); - - // Draw the turtle head. - var WIDTH = 0.3; - var HEAD_TIP = 10; - var ARROW_TIP = 4; - var BEND = 6; - var radians = 2 * Math.PI * Turtle.HEADING / 360; - var tipX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + HEAD_TIP) * Math.sin(radians); - var tipY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + HEAD_TIP) * Math.cos(radians); - radians -= WIDTH; - var leftX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); - var leftY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); - radians += WIDTH / 2; - var leftControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); - var leftControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); - radians += WIDTH; - var rightControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); - var rightControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); - radians += WIDTH / 2; - var rightX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); - var rightY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); - - ctx.beginPath(); - ctx.moveTo(tipX, tipY); - ctx.lineTo(leftX, leftY); - ctx.bezierCurveTo(leftControlX, leftControlY, - rightControlX, rightControlY, rightX, rightY); - ctx.closePath(); - ctx.fillStyle = Turtle.PEN_COLOUR; - ctx.fill(); -} - -Turtle.resetTurtle = function(){ - var c = document.getElementById("turtle-canvas"); - var ctx = c.getContext("2d") - //Clear any previous turtle - ctx.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); - -} - -Turtle.drawSolution = function(){ - var c = document.getElementById("solution-canvas"); - var ctx = c.getContext("2d") - ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent - sol = true; - solution(); - sol = false; - Turtle.reset(true); -} - -Turtle.drawDecor = function(){ - var c = document.getElementById("decor-canvas"); - var ctx = c.getContext("2d") - decor = true; - decoration(); - decor = false; - Turtle.reset(true); -} - -Turtle.animate = function(id) { - switch(id){ - case "move" : - Turtle.updateImage(); - break; - case "turn" : - Turtle.updateImage(); - break; - case "colour": - Turtle.updateImage(); - break; - default: - //This should not happen - console.warn("Unknown animation"); - break; - } -} - -Turtle.updateImage = function(){ - Turtle.resetTurtle(); - var c1 = document.getElementById("turtle-canvas"); - var ctx1 = c1.getContext("2d") - var c2 = document.getElementById("user-canvas"); - var c3 = document.getElementById("solution-canvas"); - var c4 = document.getElementById("decor-canvas"); - ctx1.drawImage(c4, 0, 0); //Fuse any decor - ctx1.drawImage(c3, 0, 0); //Fuse solution canvas - ctx1.drawImage(c2, 0, 0); //Fuse user canvas - Turtle.drawTurtle(); //Add the turtle -} - -Turtle.init = function() { - - if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { - console.warn("Turtle.init() called but Blockly or workspace was not loaded."); - window.setTimeout(Maze.init, 20); - } - Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + - 'turnRight,turnLeft,penUp,penDown,penWidth,penColour,'); - - Turtle.drawMap(); -}; - -Turtle.reset = function(bool){ - if(bool){ - Turtle.CURRENT_COORD.x = Turtle.START_X; - Turtle.CURRENT_COORD.y = Turtle.START_Y; - - Turtle.PEN_DOWN = true; - Turtle.PEN_WIDTH = json.strokeWidth; - Turtle.PEN_COLOUR = json.strokeColour; - - Turtle.HEADING = json.startAngle; - } - Turtle.updateImage(); -} - -//Workaround current plugin implementation that uses Maze as a variable -Maze.reset = function(bool){ - Turtle.CURRENT_COORD.x = Turtle.START_X; - Turtle.CURRENT_COORD.y = Turtle.START_Y; - - Turtle.PEN_DOWN = true; - Turtle.PEN_WIDTH = json.strokeWidth; - Turtle.PEN_COLOUR = json.strokeColour; - - Turtle.HEADING = json.startAngle; - if(!bool){ - var c1 = document.getElementById("turtle-canvas"); - var ctx1 = c1.getContext("2d"); - var c2 = document.getElementById("user-canvas"); - var ctx2 = c2.getContext("2d"); - ctx1.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); - ctx2.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); - } - Turtle.updateImage(); -} - -/** -* -* Blocks functions -* -*/ -Turtle.move = function(length){ - var c; - if(sol) - c = document.getElementById("solution-canvas"); - else if (decor) - c = document.getElementById("decor-canvas"); - else - c = document.getElementById("user-canvas"); - var ctx = c.getContext("2d") - if (Turtle.PEN_DOWN) { - ctx.beginPath(); - ctx.moveTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); - } - if(length){ - Turtle.CURRENT_COORD.x += length * Math.sin(2 * Math.PI * Turtle.HEADING / 360); - Turtle.CURRENT_COORD.y -= length * Math.cos(2 * Math.PI * Turtle.HEADING / 360); - } - if(Turtle.PEN_DOWN){ - ctx.lineWidth = Turtle.PEN_WIDTH; - ctx.strokeStyle = Turtle.PEN_COLOUR; - ctx.lineTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); - ctx.stroke(); - } - Turtle.animate("move"); -} - -Turtle.moveForward = function(length){ - Turtle.move(length); -} - - -Turtle.moveBackwards = function(length){ - Turtle.move(-length); -} - -Turtle.turn = function(angle, direction){ - switch (direction){ - case 0: - Turtle.HEADING = (Turtle.HEADING - angle) % 360; - if (Turtle.HEADING < 0) { - Turtle.HEADING += 360; - } - break; - case 1: - Turtle.HEADING = (Turtle.HEADING + angle) % 360; - break; - } - Turtle.animate("turn"); -} - -Turtle.turnRight = function(angle){ - Turtle.turn(angle, 1); -} - -Turtle.turnLeft = function(angle){ - Turtle.turn(angle, 0); -} - -Turtle.penWidth = function(width){ - Turtle.PEN_WIDTH = width; -} - -Turtle.penUp = function(){ - Turtle.PEN_DOWN = false; -} - -Turtle.penDown = function(){ - Turtle.PEN_DOWN = true; -} - -Turtle.penColour = function(colour){ - Turtle.PEN_COLOUR = colour; - Turtle.animate("colour"); -} - -//Called to draw -if (document.getElementById('visualization') != null) { - window.addEventListener('load', Turtle.init); -} else { - console.warn('Cannot find visualization element.'); -} \ No newline at end of file diff --git a/Cours 1/Lecon2/Artist_3/public/turtle.js b/Cours 1/Lecon2/Artist_3/public/turtle.js deleted file mode 100644 index 6414a42..0000000 --- a/Cours 1/Lecon2/Artist_3/public/turtle.js +++ /dev/null @@ -1,351 +0,0 @@ -"use strict"; - -var task_directory_path = window.location.pathname + "/"; -window.Turtle = {}; -window.Maze = {}; - -//Get the json file and it's information -var turtle_file = "" -if(task_directory_path.includes("edit")){ //When we are editing the task - turtle_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"turtle_config.json" -}else { //When displaying the task - turtle_file = task_directory_path + "turtle_config.json"; -} -var request = new XMLHttpRequest(); -request.open("GET", turtle_file, false); -request.send(null) -var json = JSON.parse(request.responseText); - -//Code of the solution -var solution = function(){ - //Here, put the javascript corresponding to the solved exercice - var i; - for(i = 0; i < 8; i++){ - Turtle.penColour(randomColour()); - Turtle.moveForward(100); - Turtle.moveBackwards(100); - Turtle.turnRight(45); - } -} -//Code of the decor -var decoration = function(){ - //Here, put the code for any decor, not part of the exercice -} - -var randomColour = function(){ - var colour = Math.floor(Math.random()*16777215); - return "#"+colour.toString(16).toUpperCase() -} - -//Canvas size -Turtle.CANVAS_WIDTH = json.width; -Turtle.CANVAS_HEIGHT = json.height; - -//Starting position and radius of turtle -Turtle.START_X = json.startX; -Turtle.START_Y = json.startY; -Turtle.RADIUS = json.radius; - -//Current coordinates of turtle -Turtle.CURRENT_COORD = { - x:Turtle.START_X, - y:Turtle.START_Y -} - -//Current heading of turtle -Turtle.HEADING = json.startAngle; - -//Weater or not the pen is down and it's width -Turtle.PEN_DOWN = true; //At start, the pen is always down -Turtle.PEN_WIDTH = json.strokeWidth; -Turtle.PEN_COLOUR = json.strokeColour; - -//Variables used to draw on the solution canvas or the decor canvas -var sol = false; -var decor = false; - -//milisec between each frame -window.stepSpeed = json.animationRate; - -/** -* -* Animations functions -* -*/ - -Turtle.drawMap = function() { - var div = document.getElementById('visualization'); - - // Add the canvas for the turtle - var canvas = document.createElement('canvas'); - canvas.setAttribute('width', Turtle.CANVAS_WIDTH); - canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); - canvas.setAttribute("style", "border:1px solid #F1EEE7;") - canvas.setAttribute("id", "turtle-canvas"); - div.appendChild(canvas); - // Add the canvas for the user. - canvas = document.createElement('canvas'); - canvas.setAttribute('width', Turtle.CANVAS_WIDTH); - canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); - canvas.setAttribute('style', 'display: none'); - canvas.setAttribute("id", "user-canvas"); - div.appendChild(canvas); - // Add the canvas for the solution. - canvas = document.createElement('canvas'); - canvas.setAttribute('width', Turtle.CANVAS_WIDTH); - canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); - canvas.setAttribute('style', 'display: none'); - canvas.setAttribute("id", "solution-canvas"); - div.appendChild(canvas); - //Add the canvas for the decor - canvas = document.createElement('canvas'); - canvas.setAttribute('width', Turtle.CANVAS_WIDTH); - canvas.setAttribute('height', Turtle.CANVAS_HEIGHT); - canvas.setAttribute('style', 'display: none') - canvas.setAttribute("id", "decor-canvas"); - div.appendChild(canvas) - - //Draw the decor - Turtle.drawDecor(); - - //Draw the solution - Turtle.drawSolution(); - - //Draw the turtle - Turtle.updateImage(); - - -} - -Turtle.drawTurtle = function(){ - var c = document.getElementById("turtle-canvas"); - var ctx = c.getContext("2d") - - //Draw the turtle body - ctx.beginPath(); - ctx.strokeStyle = Turtle.PEN_COLOUR; - ctx.arc(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y, Turtle.RADIUS, 0, 2 * Math.PI); - ctx.stroke(); - - // Draw the turtle head. - var WIDTH = 0.3; - var HEAD_TIP = 10; - var ARROW_TIP = 4; - var BEND = 6; - var radians = 2 * Math.PI * Turtle.HEADING / 360; - var tipX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + HEAD_TIP) * Math.sin(radians); - var tipY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + HEAD_TIP) * Math.cos(radians); - radians -= WIDTH; - var leftX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); - var leftY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); - radians += WIDTH / 2; - var leftControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); - var leftControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); - radians += WIDTH; - var rightControlX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + BEND) * Math.sin(radians); - var rightControlY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + BEND) * Math.cos(radians); - radians += WIDTH / 2; - var rightX = Turtle.CURRENT_COORD.x + (Turtle.RADIUS + ARROW_TIP) * Math.sin(radians); - var rightY = Turtle.CURRENT_COORD.y - (Turtle.RADIUS + ARROW_TIP) * Math.cos(radians); - - ctx.beginPath(); - ctx.moveTo(tipX, tipY); - ctx.lineTo(leftX, leftY); - ctx.bezierCurveTo(leftControlX, leftControlY, - rightControlX, rightControlY, rightX, rightY); - ctx.closePath(); - ctx.fillStyle = Turtle.PEN_COLOUR; - ctx.fill(); -} - -Turtle.resetTurtle = function(){ - var c = document.getElementById("turtle-canvas"); - var ctx = c.getContext("2d") - //Clear any previous turtle - ctx.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); - -} - -Turtle.drawSolution = function(){ - var c = document.getElementById("solution-canvas"); - var ctx = c.getContext("2d") - ctx.globalAlpha = 0.4; //The solution drawing is a bit transparent - sol = true; - solution(); - sol = false; - Turtle.reset(true); -} - -Turtle.drawDecor = function(){ - var c = document.getElementById("decor-canvas"); - var ctx = c.getContext("2d") - decor = true; - decoration(); - decor = false; - Turtle.reset(true); -} - -Turtle.animate = function(id) { - switch(id){ - case "move" : - Turtle.updateImage(); - break; - case "turn" : - Turtle.updateImage(); - break; - case "colour": - Turtle.updateImage(); - break; - default: - //This should not happen - console.warn("Unknown animation"); - break; - } -} - -Turtle.updateImage = function(){ - Turtle.resetTurtle(); - var c1 = document.getElementById("turtle-canvas"); - var ctx1 = c1.getContext("2d") - var c2 = document.getElementById("user-canvas"); - var c3 = document.getElementById("solution-canvas"); - var c4 = document.getElementById("decor-canvas"); - ctx1.drawImage(c4, 0, 0); //Fuse any decor - ctx1.drawImage(c3, 0, 0); //Fuse solution canvas - ctx1.drawImage(c2, 0, 0); //Fuse user canvas - Turtle.drawTurtle(); //Add the turtle -} - -Turtle.init = function() { - - if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { - console.warn("Turtle.init() called but Blockly or workspace was not loaded."); - window.setTimeout(Maze.init, 20); - } - Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + - 'turnRight,turnLeft,penUp,penDown,penWidth,penColour,'); - - Turtle.drawMap(); -}; - -Turtle.reset = function(bool){ - if(bool){ - Turtle.CURRENT_COORD.x = Turtle.START_X; - Turtle.CURRENT_COORD.y = Turtle.START_Y; - - Turtle.PEN_DOWN = true; - Turtle.PEN_WIDTH = json.strokeWidth; - Turtle.PEN_COLOUR = json.strokeColour; - - Turtle.HEADING = json.startAngle; - } - Turtle.updateImage(); -} - -//Workaround current plugin implementation that uses Maze as a variable -Maze.reset = function(bool){ - Turtle.CURRENT_COORD.x = Turtle.START_X; - Turtle.CURRENT_COORD.y = Turtle.START_Y; - - Turtle.PEN_DOWN = true; - Turtle.PEN_WIDTH = json.strokeWidth; - Turtle.PEN_COLOUR = json.strokeColour; - - Turtle.HEADING = json.startAngle; - if(!bool){ - var c1 = document.getElementById("turtle-canvas"); - var ctx1 = c1.getContext("2d"); - var c2 = document.getElementById("user-canvas"); - var ctx2 = c2.getContext("2d"); - ctx1.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); - ctx2.clearRect(0, 0, Turtle.CANVAS_WIDTH, Turtle.CANVAS_HEIGHT); - } - Turtle.updateImage(); -} - -/** -* -* Blocks functions -* -*/ -Turtle.move = function(length){ - var c; - if(sol) - c = document.getElementById("solution-canvas"); - else if (decor) - c = document.getElementById("decor-canvas"); - else - c = document.getElementById("user-canvas"); - var ctx = c.getContext("2d") - if (Turtle.PEN_DOWN) { - ctx.beginPath(); - ctx.moveTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); - } - if(length){ - Turtle.CURRENT_COORD.x += length * Math.sin(2 * Math.PI * Turtle.HEADING / 360); - Turtle.CURRENT_COORD.y -= length * Math.cos(2 * Math.PI * Turtle.HEADING / 360); - } - if(Turtle.PEN_DOWN){ - ctx.lineWidth = Turtle.PEN_WIDTH; - ctx.strokeStyle = Turtle.PEN_COLOUR; - ctx.lineTo(Turtle.CURRENT_COORD.x, Turtle.CURRENT_COORD.y); - ctx.stroke(); - } - Turtle.animate("move"); -} - -Turtle.moveForward = function(length){ - Turtle.move(length); -} - - -Turtle.moveBackwards = function(length){ - Turtle.move(-length); -} - -Turtle.turn = function(angle, direction){ - switch (direction){ - case 0: - Turtle.HEADING = (Turtle.HEADING - angle) % 360; - if (Turtle.HEADING < 0) { - Turtle.HEADING += 360; - } - break; - case 1: - Turtle.HEADING = (Turtle.HEADING + angle) % 360; - break; - } - Turtle.animate("turn"); -} - -Turtle.turnRight = function(angle){ - Turtle.turn(angle, 1); -} - -Turtle.turnLeft = function(angle){ - Turtle.turn(angle, 0); -} - -Turtle.penWidth = function(width){ - Turtle.PEN_WIDTH = width; -} - -Turtle.penUp = function(){ - Turtle.PEN_DOWN = false; -} - -Turtle.penDown = function(){ - Turtle.PEN_DOWN = true; -} - -Turtle.penColour = function(colour){ - Turtle.PEN_COLOUR = colour; - Turtle.animate("colour"); -} - -//Called to draw -if (document.getElementById('visualization') != null) { - window.addEventListener('load', Turtle.init); -} else { - console.warn('Cannot find visualization element.'); -} \ No newline at end of file diff --git a/Cours 1/Lecon2/course.yaml b/Cours 1/Lecon2/course.yaml deleted file mode 100644 index 3f6ccc6..0000000 --- a/Cours 1/Lecon2/course.yaml +++ /dev/null @@ -1,2 +0,0 @@ -accessible: false -name: Cours2 diff --git a/Tri_PDS/1_le_plus_leger/run b/Tri_PDS/1_le_plus_leger/run new file mode 100644 index 0000000..0f68998 --- /dev/null +++ b/Tri_PDS/1_le_plus_leger/run @@ -0,0 +1,38 @@ +#!/bin/python3 +# +# Copyright (c) 2017 Olivier Martin +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + input.parse_template("sort.py") + p = subprocess.Popen(shlex.split("python3 sort.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("Your code could not be executed. Please verify that all your blocks are correctly connected.") + exit(0) + elif make_output == "True\n": + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback("Blocs incorrects ! " + make_output) + diff --git a/Tri_PDS/1_le_plus_leger/sort.py b/Tri_PDS/1_le_plus_leger/sort.py new file mode 100644 index 0000000..5dd2888 --- /dev/null +++ b/Tri_PDS/1_le_plus_leger/sort.py @@ -0,0 +1,34 @@ +#!/bin/python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Olivier Martin +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +from contextlib import redirect_stdout +import random +import copy + +@@leger@@ + + +if __name__ == "__main__": + random.seed(55) + tab = list() + for i in range(10): + for j in range(10): #lists of 6 elements + tab.append(random.randint(0,100)) + if(min(tab) != le_plus_leger(tab)): + print("Pour la liste "+str(tab)+", tu as retourné "+str(le_plus_leger(tab)) + " alors que la bonne réponse est "+str(min(tab))) + exit() + print("True") + diff --git a/Tri_PDS/1_le_plus_leger/task.yaml b/Tri_PDS/1_le_plus_leger/task.yaml new file mode 100644 index 0000000..f875354 --- /dev/null +++ b/Tri_PDS/1_le_plus_leger/task.yaml @@ -0,0 +1,108 @@ +accessible: true +author: '' +context: |- + Avant de trier toute une liste, attaquons-nous à un problème plus simple : trouver le plus petit élément. + + Peux-tu créer un algorithme qui retourne le plus petit élément de la liste avec ces blocs ? + + Attention, souviens-toi bien que la liste ne sera pas dans l'ordre ! +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: 1. Le plus léger (blocs normaux) +network_grading: false +problems: + leger: + options: + zoom: + startScale: 1.0 + controls: true + scaleSpeed: 1.2 + maxScale: 3.0 + minScale: 0.3 + wheel: false + oneBasedIndex: true + maxBlocks: Infinity + grid: + length: 3 + spacing: 20 + colour: '#ccc' + snap: true + css: true + scrollbars: true + toolboxPosition: start + media: plugins/blockly/static/blockly/media/ + type: blockly + workspace: |- + + + + name: '' + header: '' + toolbox: |- + + + liste + element + plus_leger + + + + + + le_plus_leger + Décrire cette fonction… + + + plus_leger + + + + + element + + + liste + + + + + + element + + + plus_leger + + + + GET + FROM_START + + + liste + + + + + 0 + + + + + plus_leger + + + LT + + +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/Tri_PDS/2_le_plus_leger_custom/public/customblocks.js b/Tri_PDS/2_le_plus_leger_custom/public/customblocks.js new file mode 100644 index 0000000..e633a17 --- /dev/null +++ b/Tri_PDS/2_le_plus_leger_custom/public/customblocks.js @@ -0,0 +1,213 @@ +/* +# Copyright (c) 2018 Ilias Boutchichi +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +*/ + +/** +* @author Ilias Boutchichi +*/ + +'use strict'; + +var newlist = function() { + var liste = []; + for(var i = 0; i<10; i++){ + liste.push(Math.floor((Math.random() * 100) + 1)); + } + return liste; +}; + +var Lst = {}; +Lst.liste = newlist(); + +Blockly.Blocks['custom_for_elem'] = { + init: function() { + this.appendDummyInput() + .appendField("TANT QUE (il reste des bouteilles à comparer)"); + this.appendStatementInput("STAT") + .setCheck(null); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(105); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_for_elem'] = function(block) { + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'for(var i = 1; i < liste.length; i++){\n'+statements_stat+'}\n'; + return code; +}; + +Blockly.Python['custom_for_elem'] = function(block) { + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'for i in range(len(liste)):\n'+statements_stat; + return code; +}; + +Blockly.Blocks['custom_for_all'] = { + init: function() { + this.appendDummyInput() + .appendField("TANT QUE (il reste des bouteilles à trier)"); + this.appendStatementInput("STAT") + .setCheck(null); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(105); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_for_all'] = function(block) { + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'for(var j = 0; j < n; j++){\n'+statements_stat+'}\n'; + return code; +}; + +Blockly.Python['custom_for_all'] = function(block) { + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'for j in range(len(liste)):\n'+statements_stat; + return code; +}; + +Blockly.Blocks['create_plus_leger'] = { + init: function() { + this.appendDummyInput() + .appendField("Choisir une bouteille A"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['create_plus_leger'] = function(block) { + var code = 'var current = 0;\n currentComp(0);\n'; + return code; +}; + +Blockly.Python['create_plus_leger'] = function(block) { + var code = 'leger = 0\n'; + return code; +}; + +Blockly.Blocks['get_other'] = { + init: function() { + this.appendDummyInput() + .appendField("Prendre une bouteille B parmi les autres"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['get_other'] = function(block) { + var code = 'var other = i; otherComp(i)\n'; + return code; +}; + +Blockly.Python['get_other'] = function(block) { + var code = 'other = i\n'; + return code; +}; + +Blockly.Blocks['b_lighter_a'] = { + init: function() { + this.appendDummyInput() + .appendField("la bouteille B est plus légère que la A"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['b_lighter_a'] = function(block) { + var code = 'liste[other] < liste[current]'; + return [code, Blockly.JavaScript.ORDER_RELATIONAL]; +}; + +Blockly.Python['b_lighter_a'] = function(block) { + var code = 'liste[other] < liste[leger]'; + return [code, Blockly.Python.ORDER_RELATIONAL]; +}; + +Blockly.Blocks['b_is_a'] = { + init: function() { + this.appendDummyInput() + .appendField("la bouteille B devient la bouteille A"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['b_is_a'] = function(block) { + var code = 'current = i; currentComp(i);\n'; + return code; +}; + +Blockly.Python['b_is_a'] = function(block) { + var code = 'leger = i\n'; + return code; +}; + +Blockly.Blocks['store_a'] = { + init: function() { + this.appendDummyInput() + .appendField("placer la bouteille A dans la file déjà triée"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['store_a'] = function(block) { + var code = 'move(current);\n liste.splice(current, 1);\n'; + return code; +}; + +Blockly.Python['store_a'] = function(block) { + var code = 'B.append(liste[leger]) \nliste.remove(liste[leger])'; + return code; +}; + +Blockly.Blocks['new_list'] = { + init: function() { + this.appendDummyInput() + .appendField("créer une nouvelle liste"); + this.setNextStatement(true, null); + this.setColour(290); + this.setTooltip("A = []"); + this.setHelpUrl(""); + } +}; + +Blockly.Python['new_list'] = function(block) { + var code = 'global liste, B\nliste = [' + Lst.liste + ']\nB = []\n'; + return code; +}; + +Blockly.JavaScript['new_list'] = function(block) { + var code = 'liste = [' + Lst.liste + '];\nvar n = liste.length;\n'; + return code; +}; \ No newline at end of file diff --git a/Tri_PDS/2_le_plus_leger_custom/public/example.gif b/Tri_PDS/2_le_plus_leger_custom/public/example.gif new file mode 100644 index 0000000..96c1b12 Binary files /dev/null and b/Tri_PDS/2_le_plus_leger_custom/public/example.gif differ diff --git a/Tri_PDS/2_le_plus_leger_custom/public/insertion_sort.js b/Tri_PDS/2_le_plus_leger_custom/public/insertion_sort.js new file mode 100644 index 0000000..32f2ecb --- /dev/null +++ b/Tri_PDS/2_le_plus_leger_custom/public/insertion_sort.js @@ -0,0 +1,203 @@ +/* +* Copyright (c) 2018 Ilias Boutchichi +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +*/ + +/** +* @author Ilias Boutchichi +*/ + +'use strict'; //more secure + +var TEXT = 0, RECT = 1, INDEX = 2, VALUE = 3, RECT_HEIGHT = 60; + +var Insertion = {}; + +var currentBlue = -1; +var currentRed = -1; +var currentlyClassed = 0; + +Insertion.animations = []; +Insertion.initList = []; +Insertion.case = []; +Insertion.animid = 0; +Insertion.tmp = null; +window.stepSpeed = 500; + +/** + * Interpreter for custom functions in blocks generators + */ +var initInterpreterApi = function(interpreter, scope) { + interpreter.setProperty(scope, 'currentComp', + interpreter.createNativeFunction(function(index) { + if(currentRed != -1){ + Insertion.tmp = Insertion.case[currentRed]; + Insertion.animations.push(Insertion.changeBackgroundColor('none', Insertion.tmp[RECT])); + } + Insertion.tmp = Insertion.case[Number(index)]; + Insertion.animations.push(Insertion.changeBackgroundColor('red', Insertion.tmp[RECT])); + currentRed = Number(index) + if(currentRed == currentBlue){ + currentBlue = -1 + } + })); + + interpreter.setProperty(scope, 'otherComp', + interpreter.createNativeFunction(function(index) { + if(currentBlue != -1){ + Insertion.tmp = Insertion.case[currentBlue]; + Insertion.animations.push(Insertion.changeBackgroundColor('none', Insertion.tmp[RECT])); + } + Insertion.tmp = Insertion.case[Number(index)]; + Insertion.animations.push(Insertion.changeBackgroundColor('blue', Insertion.tmp[RECT])); + currentBlue = Number(index) + })); + + interpreter.setProperty(scope, 'move', + interpreter.createNativeFunction(function(index) { + Insertion.tmp = Insertion.case[Number(index)]; + Insertion.animations.push(Insertion.dmove(0, -Number(index), Insertion.tmp)); + var dist = 0 + if(currentlyClassed == 0) + dist = 5 - Insertion.tmp[RECT].y() + else + dist = 5 + currentlyClassed*RECT_HEIGHT + 2 - Insertion.tmp[RECT].y() + Insertion.animations.push(Insertion.dmove(250, dist, Insertion.tmp)); + currentlyClassed += 1; + Insertion.animations.push(Insertion.changeBackgroundColor('none', Insertion.tmp[RECT])); + currentRed = -1; + if(currentBlue != -1){ + Insertion.tmp = Insertion.case[currentBlue]; + Insertion.animations.push(Insertion.changeBackgroundColor('none', Insertion.tmp[RECT])); + currentBlue = -1; + } + Insertion.tmp = Insertion.case.splice(index, 1); + })); + + Insertion.reset(); +}; + +/** + * Function called after the evaluation of each block for animations + */ +var animate = function() { + Insertion.animate(); +}; + +/** + * Initialisation of Blockly, svg, and display of the random list + */ +Insertion.init = function() { + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" + || Blockly.getMainWorkspace() === null) { + console.warn("Insertion.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Insertion.init, 20); + return; + } + var svg = document.getElementById('blocklySvgZone'); + svg.setAttribute('style', ''); + svg.setAttribute('viewBox', '0, 0, 400, 800'); + svg.setAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml'); + + Insertion.list = Lst.liste; + + var svg = SVG('blocklySvgZone').size('100%', '100%'); + Insertion.list.forEach(function(item, index, array) { + var text = svg.text('' + item).font({ fill: 'black', family: 'Inconsolata', size:40}) + .move(0, index*RECT_HEIGHT); + var rect = svg.rect(100,RECT_HEIGHT).fill('none').stroke({width:4,color:'black'}) + .move(0,RECT_HEIGHT*index); + Insertion.case.push([text,rect,index,item]); + }); + Insertion.initList = Insertion.case.slice(); +}; + +/** + * Reset function called when clicking on the 'restart' button + */ +Insertion.reset = function() { + for(var x=0; x < Insertion.animations.length; x++){ + clearTimeout(Insertion.animations[x]); + } + Insertion.animations = []; + if(currentBlue != -1){ + Insertion.case[currentBlue][RECT].fill("none"); + currentBlue = -1; + } + if(currentRed != -1){ + Insertion.case[currentRed][RECT].fill('none'); + currentRed = -1; + } + Insertion.case = Insertion.initList.slice(); + + Insertion.case.forEach(function(item, index, array) { + item[TEXT].stroke('black').move(0,index*RECT_HEIGHT); + item[RECT].stroke('black').move(0,index*RECT_HEIGHT); + }); +}; + +var Maze = {}; +Maze.reset = Insertion.reset; //Trick 'cause plugin + +/** + * Function that changes the color of a rectangle + * @param {string} color the color value, either common value + * or hexadecimal value + * @param {rectangle} rect the rectangle that needs to change color + */ +Insertion.changeColor = function(color, rect) { + return function() { + rect.animate(window.stepSpeed, '<>').stroke(color); + }; +}; + +Insertion.changeBackgroundColor = function(color, rect) { + return function() { + rect.animate(window.stepSpeed, '<>').fill(color).opacity(0.4); + }; +}; + +/** + * Function that move a case (text + rectangle) by adding (x,y) to + * its coordinates + * @param {number} x value to add to the abscissa of the case 'elem' + * @param {number} y value to add to the ordinate of the case 'elem' + * @param {case} elem element representing the case that needs to move + */ +Insertion.dmove = function(x,y,elem) { + return function() { + elem[TEXT].animate(window.stepSpeed, '<>').dmove(x,y); + elem[RECT].animate(window.stepSpeed, '<>').dmove(x,y); + }; +}; + +/** + * My animation function + */ +Insertion.animate = function() { + while(Insertion.animations.length) { + window.setTimeout(Insertion.animations.shift(), + Insertion.animid++*window.stepSpeed); + } + Insertion.animid = 0; +}; + +/** + * Throws a warning if no visual interface is found on the web page, + * otherwise load the visuals + */ +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Insertion.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Tri_PDS/2_le_plus_leger_custom/public/svg_min.js b/Tri_PDS/2_le_plus_leger_custom/public/svg_min.js new file mode 100644 index 0000000..b451608 --- /dev/null +++ b/Tri_PDS/2_le_plus_leger_custom/public/svg_min.js @@ -0,0 +1,3 @@ +/*! svg.js v2.6.5 MIT*/;!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t,t.document)}):"object"==typeof exports?module.exports=t.document?e(t,t.document):function(t){return e(t,t.document)}:t.SVG=e(t,t.document)}("undefined"!=typeof window?window:this,function(t,e){function i(t,e,i,n){return i+n.replace(w.regex.dots," .")}function n(t){for(var e=t.slice(0),i=e.length;i--;)Array.isArray(e[i])&&(e[i]=n(e[i]));return e}function r(t,e){return t instanceof e}function s(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}function o(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function a(t){return t.charAt(0).toUpperCase()+t.slice(1)}function h(t){return 4==t.length?["#",t.substring(1,2),t.substring(1,2),t.substring(2,3),t.substring(2,3),t.substring(3,4),t.substring(3,4)].join(""):t}function u(t){var e=t.toString(16);return 1==e.length?"0"+e:e}function l(t,e,i){if(null==e||null==i){var n=t.bbox();null==e?e=n.width/n.height*i:null==i&&(i=n.height/n.width*e)}return{width:e,height:i}}function c(t,e,i){return{x:e*t.a+i*t.c+0,y:e*t.b+i*t.d+0}}function f(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function d(t){return t instanceof w.Matrix||(t=new w.Matrix(t)),t}function p(t,e){t.cx=null==t.cx?e.bbox().cx:t.cx,t.cy=null==t.cy?e.bbox().cy:t.cy}function m(t){for(var e=0,i=t.length,n="";e=0;i--)e.childNodes[i]instanceof t.SVGElement&&x(e.childNodes[i]);return w.adopt(e).id(w.eid(e.nodeName))}function y(t){return null==t.x&&(t.x=0,t.y=0,t.width=0,t.height=0),t.w=t.width,t.h=t.height,t.x2=t.x+t.width,t.y2=t.y+t.height,t.cx=t.x+t.width/2,t.cy=t.y+t.height/2,t}function v(t){var e=(t||"").toString().match(w.regex.reference);if(e)return e[1]}function g(t){return Math.abs(t)>1e-37?t:0}var w=this.SVG=function(t){if(w.supported)return t=new w.Doc(t),w.parser.draw||w.prepare(),t};if(w.ns="http://www.w3.org/2000/svg",w.xmlns="http://www.w3.org/2000/xmlns/",w.xlink="http://www.w3.org/1999/xlink",w.svgjs="http://svgjs.com/svgjs",w.supported=function(){return!!e.createElementNS&&!!e.createElementNS(w.ns,"svg").createSVGRect}(),!w.supported)return!1;w.did=1e3,w.eid=function(t){return"Svgjs"+a(t)+w.did++},w.create=function(t){var i=e.createElementNS(this.ns,t);return i.setAttribute("id",this.eid(t)),i},w.extend=function(){var t,e,i,n;for(t=[].slice.call(arguments),e=t.pop(),n=t.length-1;n>=0;n--)if(t[n])for(i in e)t[n].prototype[i]=e[i];w.Set&&w.Set.inherit&&w.Set.inherit()},w.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,w.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&w.extend(e,t.extend),t.construct&&w.extend(t.parent||w.Container,t.construct),e},w.adopt=function(e){if(!e)return null;if(e.instance)return e.instance;var i;return i="svg"==e.nodeName?e.parentNode instanceof t.SVGElement?new w.Nested:new w.Doc:"linearGradient"==e.nodeName?new w.Gradient("linear"):"radialGradient"==e.nodeName?new w.Gradient("radial"):w[a(e.nodeName)]?new(w[a(e.nodeName)]):new w.Element(e),i.type=e.nodeName,i.node=e,e.instance=i,i instanceof w.Doc&&i.namespace().defs(),i.setData(JSON.parse(e.getAttribute("svgjs:data"))||{}),i},w.prepare=function(){var t=e.getElementsByTagName("body")[0],i=(t?new w.Doc(t):w.adopt(e.documentElement).nested()).size(2,0);w.parser={body:t||e.documentElement,draw:i.style("opacity:0;position:absolute;left:-100%;top:-100%;overflow:hidden").node,poly:i.polyline().node,path:i.path().node,native:w.create("svg")}},w.parser={native:w.create("svg")},e.addEventListener("DOMContentLoaded",function(){w.parser.draw||w.prepare()},!1),w.regex={numberAndUnit:/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,transforms:/\)\s*,?\s*/,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,isPercent:/^-?[\d\.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,delimiter:/[\s,]+/,hyphen:/([^e])\-/gi,pathLetters:/[MLHVCSQTAZ]/gi,isPathLetter:/[MLHVCSQTAZ]/i,numbersWithDots:/((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi,dots:/\./g},w.utils={map:function(t,e){var i,n=t.length,r=[];for(i=0;i1?1:t,new w.Color({r:~~(this.r+(this.destination.r-this.r)*t),g:~~(this.g+(this.destination.g-this.g)*t),b:~~(this.b+(this.destination.b-this.b)*t)})):this}}),w.Color.test=function(t){return t+="",w.regex.isHex.test(t)||w.regex.isRgb.test(t)},w.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},w.Color.isColor=function(t){return w.Color.isRgb(t)||w.Color.test(t)},w.Array=function(t,e){t=(t||[]).valueOf(),0==t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},w.extend(w.Array,{morph:function(t){if(this.destination=this.parse(t),this.value.length!=this.destination.length){for(var e=this.value[this.value.length-1],i=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(i);for(;this.value.length=0;n--)this.value[n]=[this.value[n][0]+t,this.value[n][1]+e];return this},size:function(t,e){var i,n=this.bbox();for(i=this.value.length-1;i>=0;i--)n.width&&(this.value[i][0]=(this.value[i][0]-n.x)*t/n.width+n.x),n.height&&(this.value[i][1]=(this.value[i][1]-n.y)*e/n.height+n.y);return this},bbox:function(){return w.parser.poly.setAttribute("points",this.toString()),w.parser.poly.getBBox()}});for(var b={M:function(t,e,i){return e.x=i.x=t[0],e.y=i.y=t[1],["M",e.x,e.y]},L:function(t,e){return e.x=t[0],e.y=t[1],["L",t[0],t[1]]},H:function(t,e){return e.x=t[0],["H",t[0]]},V:function(t,e){return e.y=t[0],["V",t[0]]},C:function(t,e){return e.x=t[4],e.y=t[5],["C",t[0],t[1],t[2],t[3],t[4],t[5]]},S:function(t,e){return e.x=t[2],e.y=t[3],["S",t[0],t[1],t[2],t[3]]},Q:function(t,e){return e.x=t[2],e.y=t[3],["Q",t[0],t[1],t[2],t[3]]},T:function(t,e){return e.x=t[0],e.y=t[1],["T",t[0],t[1]]},Z:function(t,e,i){return e.x=i.x,e.y=i.y,["Z"]},A:function(t,e){return e.x=t[5],e.y=t[6],["A",t[0],t[1],t[2],t[3],t[4],t[5],t[6]]}},C="mlhvqtcsaz".split(""),N=0,A=C.length;N=0;r--)n=this.value[r][0],"M"==n||"L"==n||"T"==n?(this.value[r][1]+=t,this.value[r][2]+=e):"H"==n?this.value[r][1]+=t:"V"==n?this.value[r][1]+=e:"C"==n||"S"==n||"Q"==n?(this.value[r][1]+=t,this.value[r][2]+=e,this.value[r][3]+=t,this.value[r][4]+=e,"C"==n&&(this.value[r][5]+=t,this.value[r][6]+=e)):"A"==n&&(this.value[r][6]+=t,this.value[r][7]+=e);return this},size:function(t,e){var i,n,r=this.bbox();for(i=this.value.length-1;i>=0;i--)n=this.value[i][0],"M"==n||"L"==n||"T"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y):"H"==n?this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x:"V"==n?this.value[i][1]=(this.value[i][1]-r.y)*e/r.height+r.y:"C"==n||"S"==n||"Q"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y,this.value[i][3]=(this.value[i][3]-r.x)*t/r.width+r.x,this.value[i][4]=(this.value[i][4]-r.y)*e/r.height+r.y,"C"==n&&(this.value[i][5]=(this.value[i][5]-r.x)*t/r.width+r.x,this.value[i][6]=(this.value[i][6]-r.y)*e/r.height+r.y)):"A"==n&&(this.value[i][1]=this.value[i][1]*t/r.width,this.value[i][2]=this.value[i][2]*e/r.height,this.value[i][6]=(this.value[i][6]-r.x)*t/r.width+r.x,this.value[i][7]=(this.value[i][7]-r.y)*e/r.height+r.y);return this},equalCommands:function(t){var e,i,n;for(t=new w.PathArray(t),n=this.value.length===t.value.length,e=0,i=this.value.length;n&&ea);return n},bbox:function(){return w.parser.path.setAttribute("d",this.toString()),w.parser.path.getBBox()}}),w.Number=w.invent({create:function(t,e){this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:t<0?-3.4e38:3.4e38:"string"==typeof t?(e=t.match(w.regex.numberAndUnit))&&(this.value=parseFloat(e[1]),"%"==e[5]?this.value/=100:"s"==e[5]&&(this.value*=1e3),this.unit=e[5]):t instanceof w.Number&&(this.value=t.valueOf(),this.unit=t.unit)},extend:{toString:function(){return("%"==this.unit?~~(1e8*this.value)/1e6:"s"==this.unit?this.value/1e3:this.value)+this.unit},toJSON:function(){return this.toString()},valueOf:function(){return this.value},plus:function(t){return t=new w.Number(t),new w.Number(this+t,this.unit||t.unit)},minus:function(t){return t=new w.Number(t),new w.Number(this-t,this.unit||t.unit)},times:function(t){return t=new w.Number(t),new w.Number(this*t,this.unit||t.unit)},divide:function(t){return t=new w.Number(t),new w.Number(this/t,this.unit||t.unit)},to:function(t){var e=new w.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new w.Number(t),t.relative&&(this.destination.value+=this.value),this},at:function(t){return this.destination?new w.Number(this.destination).minus(this).times(t).plus(this):this}}}),w.Element=w.invent({create:function(t){this._stroke=w.defaults.attrs.stroke,this._event=null,this.dom={},(this.node=t)&&(this.type=t.nodeName,this.node.instance=this,this._stroke=t.getAttribute("stroke")||this._stroke)},extend:{x:function(t){return this.attr("x",t)},y:function(t){return this.attr("y",t)},cx:function(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)},cy:function(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},width:function(t){return this.attr("width",t)},height:function(t){return this.attr("height",t)},size:function(t,e){var i=l(this,t,e);return this.width(new w.Number(i.width)).height(new w.Number(i.height))},clone:function(t){this.writeDataToDom();var e=x(this.node.cloneNode(!0));return t?t.add(e):this.after(e),e},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(t){return this.after(t).remove(),t},addTo:function(t){return t.put(this)},putIn:function(t){return t.add(this)},id:function(t){return this.attr("id",t)},inside:function(t,e){var i=this.bbox();return t>i.x&&e>i.y&&t/,"").replace(/<\/svg>$/,"");i.innerHTML=""+t.replace(/\n/,"").replace(/<([\w:-]+)([^<]+?)\/>/g,"<$1$2>")+"";for(var n=0,r=i.firstChild.childNodes.length;n":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return 1-Math.cos(t*Math.PI/2)}},w.morph=function(t){return function(e,i){return new w.MorphObj(e,i).at(t)}},w.Situation=w.invent({create:function(t){this.init=!1,this.reversed=!1,this.reversing=!1,this.duration=new w.Number(t.duration).valueOf(),this.delay=new w.Number(t.delay).valueOf(),this.start=+new Date+this.delay,this.finish=this.start+this.duration,this.ease=t.ease,this.loop=0,this.loops=!1,this.animations={},this.attrs={},this.styles={},this.transforms=[],this.once={}}}),w.FX=w.invent({create:function(t){this._target=t,this.situations=[],this.active=!1,this.situation=null,this.paused=!1,this.lastPos=0,this.pos=0,this.absPos=0,this._speed=1},extend:{animate:function(t,e,i){"object"==typeof t&&(e=t.ease,i=t.delay,t=t.duration);var n=new w.Situation({duration:t||1e3,delay:i||0,ease:w.easing[e||"-"]||e});return this.queue(n),this},delay:function(t){var e=new w.Situation({duration:t,delay:0,ease:w.easing["-"]});return this.queue(e)},target:function(t){return t&&t instanceof w.Element?(this._target=t,this):this._target},timeToAbsPos:function(t){return(t-this.situation.start)/(this.situation.duration/this._speed)},absPosToTime:function(t){return this.situation.duration/this._speed*t+this.situation.start},startAnimFrame:function(){this.stopAnimFrame(),this.animationFrame=t.requestAnimationFrame(function(){this.step()}.bind(this))},stopAnimFrame:function(){t.cancelAnimationFrame(this.animationFrame)},start:function(){return!this.active&&this.situation&&(this.active=!0,this.startCurrent()),this},startCurrent:function(){return this.situation.start=+new Date+this.situation.delay/this._speed,this.situation.finish=this.situation.start+this.situation.duration/this._speed,this.initAnimations().step()},queue:function(t){return("function"==typeof t||t instanceof w.Situation)&&this.situations.push(t),this.situation||(this.situation=this.situations.shift()),this},dequeue:function(){return this.stop(),this.situation=this.situations.shift(),this.situation&&(this.situation instanceof w.Situation?this.start():this.situation.call(this)),this},initAnimations:function(){var t,e,i,n=this.situation;if(n.init)return this;for(t in n.animations)for(i=this.target()[t](),Array.isArray(i)||(i=[i]),Array.isArray(n.animations[t])||(n.animations[t]=[n.animations[t]]),e=i.length;e--;)n.animations[t][e]instanceof w.Number&&(i[e]=new w.Number(i[e])),n.animations[t][e]=i[e].morph(n.animations[t][e]);for(t in n.attrs)n.attrs[t]=new w.MorphObj(this.target().attr(t),n.attrs[t]);for(t in n.styles)n.styles[t]=new w.MorphObj(this.target().style(t),n.styles[t]);return n.initialTransformation=this.target().matrixify(),n.init=!0,this},clearQueue:function(){return this.situations=[],this},clearCurrent:function(){return this.situation=null,this},stop:function(t,e){var i=this.active;return this.active=!1,e&&this.clearQueue(),t&&this.situation&&(!i&&this.startCurrent(),this.atEnd()),this.stopAnimFrame(),this.clearCurrent()},reset:function(){if(this.situation){var t=this.situation;this.stop(),this.situation=t,this.atStart()}return this},finish:function(){for(this.stop(!0,!1);this.dequeue().situation&&this.stop(!0,!1););return this.clearQueue().clearCurrent(),this},atStart:function(){return this.at(0,!0)},atEnd:function(){return!0===this.situation.loops&&(this.situation.loops=this.situation.loop+1),"number"==typeof this.situation.loops?this.at(this.situation.loops,!0):this.at(1,!0)},at:function(t,e){var i=this.situation.duration/this._speed;return this.absPos=t,e||(this.situation.reversed&&(this.absPos=1-this.absPos),this.absPos+=this.situation.loop),this.situation.start=+new Date-this.absPos*i,this.situation.finish=this.situation.start+i,this.step(!0)},speed:function(t){return 0===t?this.pause():t?(this._speed=t,this.at(this.absPos,!0)):this._speed},loop:function(t,e){var i=this.last();return i.loops=null==t||t,i.loop=0,e&&(i.reversing=!0),this},pause:function(){return this.paused=!0,this.stopAnimFrame(),this},play:function(){return this.paused?(this.paused=!1,this.at(this.absPos,!0)):this},reverse:function(t){var e=this.last();return e.reversed=void 0===t?!e.reversed:t,this},progress:function(t){return t?this.situation.ease(this.pos):this.pos},after:function(t){var e=this.last(),i=function i(n){n.detail.situation==e&&(t.call(this,e),this.off("finished.fx",i))};return this.target().on("finished.fx",i),this._callStart()},during:function(t){var e=this.last(),i=function(i){i.detail.situation==e&&t.call(this,i.detail.pos,w.morph(i.detail.pos),i.detail.eased,e)};return this.target().off("during.fx",i).on("during.fx",i),this.after(function(){this.off("during.fx",i)}),this._callStart()},afterAll:function(t){var e=function e(i){t.call(this),this.off("allfinished.fx",e)};return this.target().off("allfinished.fx",e).on("allfinished.fx",e),this._callStart()},duringAll:function(t){var e=function(e){t.call(this,e.detail.pos,w.morph(e.detail.pos),e.detail.eased,e.detail.situation)};return this.target().off("during.fx",e).on("during.fx",e),this.afterAll(function(){this.off("during.fx",e)}),this._callStart()},last:function(){return this.situations.length?this.situations[this.situations.length-1]:this.situation},add:function(t,e,i){return this.last()[i||"animations"][t]=e,this._callStart()},step:function(t){if(t||(this.absPos=this.timeToAbsPos(+new Date)),!1!==this.situation.loops){var e,i,n;e=Math.max(this.absPos,0),i=Math.floor(e),!0===this.situation.loops||ithis.lastPos&&s<=r&&(this.situation.once[s].call(this.target(),this.pos,r),delete this.situation.once[s]);return this.active&&this.target().fire("during",{pos:this.pos,eased:r,fx:this,situation:this.situation}),this.situation?(this.eachAt(),1==this.pos&&!this.situation.reversed||this.situation.reversed&&0==this.pos?(this.stopAnimFrame(),this.target().fire("finished",{fx:this,situation:this.situation}),this.situations.length||(this.target().fire("allfinished"),this.situations.length||(this.target().off(".fx"),this.active=!1)),this.active?this.dequeue():this.clearCurrent()):!this.paused&&this.active&&this.startAnimFrame(),this.lastPos=r,this):this},eachAt:function(){var t,e,i,n=this,r=this.target(),s=this.situation;for(t in s.animations)i=[].concat(s.animations[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r[t].apply(r,i);for(t in s.attrs)i=[t].concat(s.attrs[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.attr.apply(r,i);for(t in s.styles)i=[t].concat(s.styles[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.style.apply(r,i);if(s.transforms.length){for(i=s.initialTransformation,t=0,e=s.transforms.length;t=0;--e)this[P[e]]=null!=t[P[e]]?t[P[e]]:i[P[e]]},extend:{extract:function(){var t=c(this,0,1),e=c(this,1,0),i=180/Math.PI*Math.atan2(t.y,t.x)-90;return{x:this.e,y:this.f,transformedX:(this.e*Math.cos(i*Math.PI/180)+this.f*Math.sin(i*Math.PI/180))/Math.sqrt(this.a*this.a+this.b*this.b),transformedY:(this.f*Math.cos(i*Math.PI/180)+this.e*Math.sin(-i*Math.PI/180))/Math.sqrt(this.c*this.c+this.d*this.d),skewX:-i,skewY:180/Math.PI*Math.atan2(e.y,e.x),scaleX:Math.sqrt(this.a*this.a+this.b*this.b),scaleY:Math.sqrt(this.c*this.c+this.d*this.d),rotation:i,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f,matrix:new w.Matrix(this)}},clone:function(){return new w.Matrix(this)},morph:function(t){return this.destination=new w.Matrix(t),this},at:function(t){return this.destination?new w.Matrix({a:this.a+(this.destination.a-this.a)*t,b:this.b+(this.destination.b-this.b)*t,c:this.c+(this.destination.c-this.c)*t,d:this.d+(this.destination.d-this.d)*t,e:this.e+(this.destination.e-this.e)*t,f:this.f+(this.destination.f-this.f)*t}):this},multiply:function(t){return new w.Matrix(this.native().multiply(d(t).native()))},inverse:function(){return new w.Matrix(this.native().inverse())},translate:function(t,e){return new w.Matrix(this.native().translate(t||0,e||0))},scale:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),this.around(i,n,new w.Matrix(t,0,0,e,0,0))},rotate:function(t,e,i){return t=w.utils.radians(t),this.around(e,i,new w.Matrix(Math.cos(t),Math.sin(t),-Math.sin(t),Math.cos(t),0,0))},flip:function(t,e){return"x"==t?this.scale(-1,1,e,0):"y"==t?this.scale(1,-1,0,e):this.scale(-1,-1,t,null!=e?e:t)},skew:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),t=w.utils.radians(t),e=w.utils.radians(e), +this.around(i,n,new w.Matrix(1,Math.tan(e),Math.tan(t),1,0,0))},skewX:function(t,e,i){return this.skew(t,0,e,i)},skewY:function(t,e,i){return this.skew(0,t,e,i)},around:function(t,e,i){return this.multiply(new w.Matrix(1,0,0,1,t||0,e||0)).multiply(i).multiply(new w.Matrix(1,0,0,1,-t||0,-e||0))},native:function(){for(var t=w.parser.native.createSVGMatrix(),e=P.length-1;e>=0;e--)t[P[e]]=this[P[e]];return t},toString:function(){return"matrix("+g(this.a)+","+g(this.b)+","+g(this.c)+","+g(this.d)+","+g(this.e)+","+g(this.f)+")"}},parent:w.Element,construct:{ctm:function(){return new w.Matrix(this.node.getCTM())},screenCTM:function(){if(this instanceof w.Nested){var t=this.rect(1,1),e=t.node.getScreenCTM();return t.remove(),new w.Matrix(e)}return new w.Matrix(this.node.getScreenCTM())}}}),w.Point=w.invent({create:function(t,e){var i,n={x:0,y:0};i=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:null!=t?{x:t,y:null!=e?e:t}:n,this.x=i.x,this.y=i.y},extend:{clone:function(){return new w.Point(this)},morph:function(t,e){return this.destination=new w.Point(t,e),this},at:function(t){return this.destination?new w.Point({x:this.x+(this.destination.x-this.x)*t,y:this.y+(this.destination.y-this.y)*t}):this},native:function(){var t=w.parser.native.createSVGPoint();return t.x=this.x,t.y=this.y,t},transform:function(t){return new w.Point(this.native().matrixTransform(t.native()))}}}),w.extend(w.Element,{point:function(t,e){return new w.Point(t,e).transform(this.screenCTM().inverse())}}),w.extend(w.Element,{attr:function(t,e,i){if(null==t){for(t={},e=this.node.attributes,i=e.length-1;i>=0;i--)t[e[i].nodeName]=w.regex.isNumber.test(e[i].nodeValue)?parseFloat(e[i].nodeValue):e[i].nodeValue;return t}if("object"==typeof t)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return e=this.node.getAttribute(t),null==e?w.defaults.attrs[t]:w.regex.isNumber.test(e)?parseFloat(e):e;"stroke-width"==t?this.attr("stroke",parseFloat(e)>0?this._stroke:null):"stroke"==t&&(this._stroke=e),"fill"!=t&&"stroke"!=t||(w.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),e instanceof w.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new w.Number(e):w.Color.isColor(e)?e=new w.Color(e):Array.isArray(e)&&(e=new w.Array(e)),"leading"==t?this.leading&&this.leading(e):"string"==typeof i?this.node.setAttributeNS(i,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!=t&&"x"!=t||this.rebuild(t,e)}return this}}),w.extend(w.Element,{transform:function(t,e){var i,n,r=this;if("object"!=typeof t)return i=new w.Matrix(r).extract(),"string"==typeof t?i[t]:i;if(i=new w.Matrix(r),e=!!e||!!t.relative,null!=t.a)i=e?i.multiply(new w.Matrix(t)):new w.Matrix(t);else if(null!=t.rotation)p(t,r),i=e?i.rotate(t.rotation,t.cx,t.cy):i.rotate(t.rotation-i.extract().rotation,t.cx,t.cy);else if(null!=t.scale||null!=t.scaleX||null!=t.scaleY){if(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,!e){var s=i.extract();t.scaleX=1*t.scaleX/s.scaleX,t.scaleY=1*t.scaleY/s.scaleY}i=i.scale(t.scaleX,t.scaleY,t.cx,t.cy)}else if(null!=t.skew||null!=t.skewX||null!=t.skewY){if(p(t,r),t.skewX=null!=t.skew?t.skew:null!=t.skewX?t.skewX:0,t.skewY=null!=t.skew?t.skew:null!=t.skewY?t.skewY:0,!e){var s=i.extract();i=i.multiply((new w.Matrix).skew(s.skewX,s.skewY,t.cx,t.cy).inverse())}i=i.skew(t.skewX,t.skewY,t.cx,t.cy)}else t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new w.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(e?i=i.translate(t.x,t.y):(null!=t.x&&(i.e=t.x),null!=t.y&&(i.f=t.y)));return this.attr("transform",i)}}),w.extend(w.FX,{transform:function(t,e){var i,n,r=this.target();return"object"!=typeof t?(i=new w.Matrix(r).extract(),"string"==typeof t?i[t]:i):(e=!!e||!!t.relative,null!=t.a?i=new w.Matrix(t):null!=t.rotation?(p(t,r),i=new w.Rotate(t.rotation,t.cx,t.cy)):null!=t.scale||null!=t.scaleX||null!=t.scaleY?(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,i=new w.Scale(t.scaleX,t.scaleY,t.cx,t.cy)):null!=t.skewX||null!=t.skewY?(p(t,r),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,i=new w.Skew(t.skewX,t.skewY,t.cx,t.cy)):t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new w.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(i=new w.Translate(t.x,t.y)),i?(i.relative=e,this.last().transforms.push(i),this._callStart()):this)}}),w.extend(w.Element,{untransform:function(){return this.attr("transform",null)},matrixify:function(){return(this.attr("transform")||"").split(w.regex.transforms).slice(0,-1).map(function(t){var e=t.trim().split("(");return[e[0],e[1].split(w.regex.delimiter).map(function(t){return parseFloat(t)})]}).reduce(function(t,e){return"matrix"==e[0]?t.multiply(f(e[1])):t[e[0]].apply(t,e[1])},new w.Matrix)},toParent:function(t){if(this==t)return this;var e=this.screenCTM(),i=t.screenCTM().inverse();return this.addTo(t).untransform().transform(i.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),w.Transformation=w.invent({create:function(t,e){if(arguments.length>1&&"boolean"!=typeof e)return this.constructor.call(this,[].slice.call(arguments));if(Array.isArray(t))for(var i=0,n=this.arguments.length;i=0},index:function(t){return[].slice.call(this.node.childNodes).indexOf(t.node)},get:function(t){return w.adopt(this.node.childNodes[t])},first:function(){return this.get(0)},last:function(){return this.get(this.node.childNodes.length-1)},each:function(t,e){var i,n,r=this.children();for(i=0,n=r.length;in/r?this.height/r:this.width/n,this.x=e,this.y=i,this.width=n,this.height=r)}else t="string"==typeof t?t.match(c).map(function(t){return parseFloat(t)}):Array.isArray(t)?t:"object"==typeof t?[t.x,t.y,t.width,t.height]:4==arguments.length?[].slice.call(arguments):h,this.x=t[0],this.y=t[1],this.width=t[2],this.height=t[3]},extend:{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height},morph:function(t,e,i,n){return this.destination=new w.ViewBox(t,e,i,n),this},at:function(t){return this.destination?new w.ViewBox([this.x+(this.destination.x-this.x)*t,this.y+(this.destination.y-this.y)*t,this.width+(this.destination.width-this.width)*t,this.height+(this.destination.height-this.height)*t]):this}},parent:w.Container,construct:{viewbox:function(t,e,i,n){return 0==arguments.length?new w.ViewBox(this):this.attr("viewBox",new w.ViewBox(t,e,i,n))}}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){w.Element.prototype[t]=function(e){return w.on(this.node,t,e),this}}),w.listeners=[],w.handlerMap=[],w.listenerId=0,w.on=function(t,e,i,n,r){var s=i.bind(n||t.instance||t),o=(w.handlerMap.indexOf(t)+1||w.handlerMap.push(t))-1,a=e.split(".")[0],h=e.split(".")[1]||"*";w.listeners[o]=w.listeners[o]||{},w.listeners[o][a]=w.listeners[o][a]||{},w.listeners[o][a][h]=w.listeners[o][a][h]||{},i._svgjsListenerId||(i._svgjsListenerId=++w.listenerId),w.listeners[o][a][h][i._svgjsListenerId]=s,t.addEventListener(a,s,r||!1)},w.off=function(t,e,i){var n=w.handlerMap.indexOf(t),r=e&&e.split(".")[0],s=e&&e.split(".")[1],o="";if(-1!=n)if(i){if("function"==typeof i&&(i=i._svgjsListenerId),!i)return;w.listeners[n][r]&&w.listeners[n][r][s||"*"]&&(t.removeEventListener(r,w.listeners[n][r][s||"*"][i],!1),delete w.listeners[n][r][s||"*"][i])}else if(s&&r){if(w.listeners[n][r]&&w.listeners[n][r][s]){for(i in w.listeners[n][r][s])w.off(t,[r,s].join("."),i);delete w.listeners[n][r][s]}}else if(s)for(e in w.listeners[n])for(o in w.listeners[n][e])s===o&&w.off(t,[e,s].join("."));else if(r){if(w.listeners[n][r]){for(o in w.listeners[n][r])w.off(t,[r,o].join("."));delete w.listeners[n][r]}}else{for(e in w.listeners[n])w.off(t,e);delete w.listeners[n],delete w.handlerMap[n]}},w.extend(w.Element,{on:function(t,e,i,n){return w.on(this.node,t,e,i,n),this},off:function(t,e){return w.off(this.node,t,e),this},fire:function(e,i){return e instanceof t.Event?this.node.dispatchEvent(e):this.node.dispatchEvent(e=new w.CustomEvent(e,{detail:i,cancelable:!0})),this._event=e,this},event:function(){return this._event}}),w.Defs=w.invent({create:"defs",inherit:w.Container}),w.G=w.invent({create:"g",inherit:w.Container,extend:{x:function(t){return null==t?this.transform("x"):this.transform({x:t-this.x()},!0)},y:function(t){return null==t?this.transform("y"):this.transform({y:t-this.y()},!0)},cx:function(t){return null==t?this.gbox().cx:this.x(t-this.gbox().width/2)},cy:function(t){return null==t?this.gbox().cy:this.y(t-this.gbox().height/2)},gbox:function(){var t=this.bbox(),e=this.transform();return t.x+=e.x,t.x2+=e.x,t.cx+=e.x,t.y+=e.y,t.y2+=e.y,t.cy+=e.y,t}},construct:{group:function(){return this.put(new w.G)}}}),w.Doc=w.invent({create:function(t){t&&(t="string"==typeof t?e.getElementById(t):t,"svg"==t.nodeName?this.constructor.call(this,t):(this.constructor.call(this,w.create("svg")),t.appendChild(this.node),this.size("100%","100%")),this.namespace().defs())},inherit:w.Container,extend:{namespace:function(){return this.attr({xmlns:w.ns,version:"1.1"}).attr("xmlns:xlink",w.xlink,w.xmlns).attr("xmlns:svgjs",w.svgjs,w.xmlns)},defs:function(){if(!this._defs){var t;(t=this.node.getElementsByTagName("defs")[0])?this._defs=w.adopt(t):this._defs=new w.Defs,this.node.appendChild(this._defs.node)}return this._defs},parent:function(){return this.node.parentNode&&"#document"!=this.node.parentNode.nodeName?this.node.parentNode:null},spof:function(){var t=this.node.getScreenCTM();return t&&this.style("left",-t.e%1+"px").style("top",-t.f%1+"px"),this},remove:function(){return this.parent()&&this.parent().removeChild(this.node),this},clear:function(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return delete this._defs,w.parser.draw.parentNode||this.node.appendChild(w.parser.draw),this},clone:function(t){this.writeDataToDom();var e=this.node,i=x(e.cloneNode(!0));return t?(t.node||t).appendChild(i.node):e.parentNode.insertBefore(i.node,e.nextSibling),i}}}),w.extend(w.Element,{siblings:function(){return this.parent().children()},position:function(){return this.parent().index(this)},next:function(){return this.siblings()[this.position()+1]},previous:function(){return this.siblings()[this.position()-1]},forward:function(){var t=this.position()+1,e=this.parent();return e.removeElement(this).add(this,t),e instanceof w.Doc&&e.node.appendChild(e.defs().node),this},backward:function(){var t=this.position();return t>0&&this.parent().removeElement(this).add(this,t-1),this},front:function(){var t=this.parent();return t.node.appendChild(this.node),t instanceof w.Doc&&t.node.appendChild(t.defs().node),this},back:function(){return this.position()>0&&this.parent().removeElement(this).add(this,0),this},before:function(t){t.remove();var e=this.position();return this.parent().add(t,e),this},after:function(t){t.remove();var e=this.position();return this.parent().add(t,e+1),this}}),w.Mask=w.invent({create:function(){this.constructor.call(this,w.create("mask")),this.targets=[]},inherit:w.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unmask();return this.targets=[],w.Element.prototype.remove.call(this),this}},construct:{mask:function(){return this.defs().put(new w.Mask)}}}),w.extend(w.Element,{maskWith:function(t){return this.masker=t instanceof w.Mask?t:this.parent().mask().add(t),this.masker.targets.push(this),this.attr("mask",'url("#'+this.masker.attr("id")+'")')},unmask:function(){return delete this.masker,this.attr("mask",null)}}),w.ClipPath=w.invent({create:function(){this.constructor.call(this,w.create("clipPath")),this.targets=[]},inherit:w.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unclip();return this.targets=[],this.parent().removeElement(this),this}},construct:{clip:function(){return this.defs().put(new w.ClipPath)}}}),w.extend(w.Element,{clipWith:function(t){return this.clipper=t instanceof w.ClipPath?t:this.parent().clip().add(t),this.clipper.targets.push(this),this.attr("clip-path",'url("#'+this.clipper.attr("id")+'")')},unclip:function(){return delete this.clipper,this.attr("clip-path",null)}}),w.Gradient=w.invent({create:function(t){this.constructor.call(this,w.create(t+"Gradient")),this.type=t},inherit:w.Container,extend:{at:function(t,e,i){return this.put(new w.Stop).update(t,e,i)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},fill:function(){return"url(#"+this.id()+")"},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="gradientTransform"),w.Container.prototype.attr.call(this,t,e,i)}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),w.extend(w.Gradient,w.FX,{from:function(t,e){return"radial"==(this._target||this).type?this.attr({fx:new w.Number(t),fy:new w.Number(e)}):this.attr({x1:new w.Number(t),y1:new w.Number(e)})},to:function(t,e){return"radial"==(this._target||this).type?this.attr({cx:new w.Number(t),cy:new w.Number(e)}):this.attr({x2:new w.Number(t),y2:new w.Number(e)})}}),w.extend(w.Defs,{gradient:function(t,e){return this.put(new w.Gradient(t)).update(e)}}),w.Stop=w.invent({create:"stop",inherit:w.Element,extend:{update:function(t){return("number"==typeof t||t instanceof w.Number)&&(t={offset:arguments[0],color:arguments[1],opacity:arguments[2]}),null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new w.Number(t.offset)),this}}}),w.Pattern=w.invent({create:"pattern",inherit:w.Container,extend:{fill:function(){return"url(#"+this.id()+")"},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="patternTransform"),w.Container.prototype.attr.call(this,t,e,i)}},construct:{pattern:function(t,e,i){return this.defs().pattern(t,e,i)}}}),w.extend(w.Defs,{pattern:function(t,e,i){return this.put(new w.Pattern).update(i).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),w.Shape=w.invent({create:function(t){this.constructor.call(this,t)},inherit:w.Element}),w.Bare=w.invent({create:function(t,e){if(this.constructor.call(this,w.create(t)),e)for(var i in e.prototype)"function"==typeof e.prototype[i]&&(this[i]=e.prototype[i])},inherit:w.Element,extend:{words:function(t){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this.node.appendChild(e.createTextNode(t)),this}}}),w.extend(w.Parent,{element:function(t,e){return this.put(new w.Bare(t,e))}}),w.Symbol=w.invent({create:"symbol",inherit:w.Container,construct:{symbol:function(){return this.put(new w.Symbol)}}}),w.Use=w.invent({create:"use",inherit:w.Shape,extend:{element:function(t,e){return this.attr("href",(e||"")+"#"+t,w.xlink)}},construct:{use:function(t,e){return this.put(new w.Use).element(t,e)}}}),w.Rect=w.invent({create:"rect",inherit:w.Shape,construct:{rect:function(t,e){return this.put(new w.Rect).size(t,e)}}}),w.Circle=w.invent({create:"circle",inherit:w.Shape,construct:{circle:function(t){return this.put(new w.Circle).rx(new w.Number(t).divide(2)).move(0,0)}}}),w.extend(w.Circle,w.FX,{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),w.Ellipse=w.invent({create:"ellipse",inherit:w.Shape,construct:{ellipse:function(t,e){return this.put(new w.Ellipse).size(t,e).move(0,0)}}}),w.extend(w.Ellipse,w.Rect,w.FX,{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),w.extend(w.Circle,w.Ellipse,{x:function(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())},y:function(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())},cx:function(t){return null==t?this.attr("cx"):this.attr("cx",t)},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",t)},width:function(t){return null==t?2*this.rx():this.rx(new w.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new w.Number(t).divide(2))},size:function(t,e){var i=l(this,t,e);return this.rx(new w.Number(i.width).divide(2)).ry(new w.Number(i.height).divide(2))}}),w.Line=w.invent({create:"line",inherit:w.Shape,extend:{array:function(){return new w.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,i,n){return null==t?this.array():(t=void 0!==e?{x1:t,y1:e,x2:i,y2:n}:new w.PointArray(t).toLine(),this.attr(t))},move:function(t,e){return this.attr(this.array().move(t,e).toLine())},size:function(t,e){var i=l(this,t,e);return this.attr(this.array().size(i.width,i.height).toLine())}},construct:{line:function(t,e,i,n){return w.Line.prototype.plot.apply(this.put(new w.Line),null!=t?[t,e,i,n]:[0,0,0,0])}}}),w.Polyline=w.invent({create:"polyline",inherit:w.Shape,construct:{polyline:function(t){return this.put(new w.Polyline).plot(t||new w.PointArray)}}}),w.Polygon=w.invent({create:"polygon",inherit:w.Shape,construct:{polygon:function(t){return this.put(new w.Polygon).plot(t||new w.PointArray)}}}),w.extend(w.Polyline,w.Polygon,{array:function(){return this._array||(this._array=new w.PointArray(this.attr("points")))},plot:function(t){return null==t?this.array():this.clear().attr("points","string"==typeof t?t:this._array=new w.PointArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("points",this.array().move(t,e))},size:function(t,e){var i=l(this,t,e);return this.attr("points",this.array().size(i.width,i.height))}}),w.extend(w.Line,w.Polyline,w.Polygon,{morphArray:w.PointArray,x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},width:function(t){var e=this.bbox();return null==t?e.width:this.size(t,e.height)},height:function(t){var e=this.bbox();return null==t?e.height:this.size(e.width,t)}}),w.Path=w.invent({create:"path",inherit:w.Shape,extend:{morphArray:w.PathArray,array:function(){return this._array||(this._array=new w.PathArray(this.attr("d")))},plot:function(t){return null==t?this.array():this.clear().attr("d","string"==typeof t?t:this._array=new w.PathArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("d",this.array().move(t,e))},x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},size:function(t,e){var i=l(this,t,e);return this.attr("d",this.array().size(i.width,i.height))},width:function(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)},height:function(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}},construct:{path:function(t){return this.put(new w.Path).plot(t||new w.PathArray)}}}),w.Image=w.invent({create:"image",inherit:w.Shape,extend:{load:function(e){if(!e)return this;var i=this,n=new t.Image;return w.on(n,"load",function(){w.off(n);var t=i.parent(w.Pattern);null!==t&&(0==i.width()&&0==i.height()&&i.size(n.width,n.height),t&&0==t.width()&&0==t.height()&&t.size(i.width(),i.height()),"function"==typeof i._loaded&&i._loaded.call(i,{width:n.width,height:n.height,ratio:n.width/n.height,url:e}))}),w.on(n,"error",function(t){w.off(n),"function"==typeof i._error&&i._error.call(i,t)}),this.attr("href",n.src=this.src=e,w.xlink)},loaded:function(t){return this._loaded=t,this},error:function(t){return this._error=t,this}},construct:{image:function(t,e,i){return this.put(new w.Image).load(t).size(e||0,i||e||0)}}}),w.Text=w.invent({create:function(){this.constructor.call(this,w.create("text")),this.dom.leading=new w.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",w.defaults.attrs["font-family"])},inherit:w.Shape,extend:{x:function(t){return null==t?this.attr("x"):this.attr("x",t)},y:function(t){var e=this.attr("y"),i="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-i:e:this.attr("y","number"==typeof t.valueOf()?t+i:t)},cx:function(t){return null==t?this.bbox().cx:this.x(t-this.bbox().width/2)},cy:function(t){return null==t?this.bbox().cy:this.y(t-this.bbox().height/2)},text:function(t){if(void 0===t){for(var t="",e=this.node.childNodes,i=0,n=e.length;i=0;e--)null!=i[M[t][e]]&&this.attr(M.prefix(t,M[t][e]),i[M[t][e]]);return this},w.extend(w.Element,w.FX,i)}),w.extend(w.Element,w.FX,{rotate:function(t,e,i){return this.transform({rotation:t,cx:e,cy:i})},skew:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({skew:t,cx:e,cy:i}):this.transform({skewX:t,skewY:e,cx:i,cy:n})},scale:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({scale:t,cx:e,cy:i}):this.transform({scaleX:t,scaleY:e,cx:i,cy:n})},translate:function(t,e){return this.transform({x:t,y:e})},flip:function(t,e){return e="number"==typeof t?t:e,this.transform({flip:t||"both",offset:e})},matrix:function(t){return this.attr("transform",new w.Matrix(6==arguments.length?[].slice.call(arguments):t))},opacity:function(t){return this.attr("opacity",t)},dx:function(t){return this.x(new w.Number(t).plus(this instanceof w.FX?0:this.x()),!0)},dy:function(t){return this.y(new w.Number(t).plus(this instanceof w.FX?0:this.y()),!0)},dmove:function(t,e){return this.dx(t).dy(e)}}),w.extend(w.Rect,w.Ellipse,w.Circle,w.Gradient,w.FX,{radius:function(t,e){var i=(this._target||this).type;return"radial"==i||"circle"==i?this.attr("r",new w.Number(t)):this.rx(t).ry(null==e?t:e)}}),w.extend(w.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),w.extend(w.Parent,w.Text,w.Tspan,w.FX,{font:function(t,e){if("object"==typeof t)for(e in t)this.font(e,t[e]);return"leading"==t?this.leading(e):"anchor"==t?this.attr("text-anchor",e):"size"==t||"family"==t||"weight"==t||"stretch"==t||"variant"==t||"style"==t?this.attr("font-"+t,e):this.attr(t,e)}}),w.Set=w.invent({create:function(t){Array.isArray(t)?this.members=t:this.clear()},extend:{add:function(){var t,e,i=[].slice.call(arguments);for(t=0,e=i.length;t-1&&this.members.splice(e,1),this},each:function(t){for(var e=0,i=this.members.length;e=0},index:function(t){return this.members.indexOf(t)},get:function(t){return this.members[t]},first:function(){return this.get(0)},last:function(){return this.get(this.members.length-1)},valueOf:function(){return this.members},bbox:function(){if(0==this.members.length)return new w.RBox;var t=this.members[0].rbox(this.members[0].doc());return this.each(function(){t=t.merge(this.rbox(this.doc()))}),t}},construct:{set:function(t){return new w.Set(t)}}}),w.FX.Set=w.invent({create:function(t){this.set=t}}),w.Set.inherit=function(){ +var t,e=[];for(var t in w.Shape.prototype)"function"==typeof w.Shape.prototype[t]&&"function"!=typeof w.Set.prototype[t]&&e.push(t);e.forEach(function(t){w.Set.prototype[t]=function(){for(var e=0,i=this.members.length;e=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),w.get=function(t){var i=e.getElementById(v(t)||t);return w.adopt(i)},w.select=function(t,i){return new w.Set(w.utils.map((i||e).querySelectorAll(t),function(t){return w.adopt(t)}))},w.extend(w.Parent,{select:function(t){return w.select(t,this.node)}});var P="abcdef".split("");if("function"!=typeof t.CustomEvent){var k=function(t,i){i=i||{bubbles:!1,cancelable:!1,detail:void 0};var n=e.createEvent("CustomEvent");return n.initCustomEvent(t,i.bubbles,i.cancelable,i.detail),n};k.prototype=t.Event.prototype,w.CustomEvent=k}else w.CustomEvent=t.CustomEvent;return function(e){for(var i=0,n=["moz","webkit"],r=0;r. +# + +import os, subprocess, shlex +from inginious import feedback +from inginious import input + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("insertion_sort.py") + + p = subprocess.Popen(shlex.split("python3 insertion_sort.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("Quelque chose s'est mal passé. Veuillez vérifier que l'agencement de vos blocs est correct. "+make_output) + exit(0) + if make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Félicitations ! Tu as bien sélectionné le plus petit élément.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback("Mauvaise réponse. " + make_output) diff --git a/Tri_PDS/2_le_plus_leger_custom/student/insertion_sort.py b/Tri_PDS/2_le_plus_leger_custom/student/insertion_sort.py new file mode 100644 index 0000000..49dafb5 --- /dev/null +++ b/Tri_PDS/2_le_plus_leger_custom/student/insertion_sort.py @@ -0,0 +1,32 @@ +#!/bin/python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018 Ilias Boutchichi +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +def student_code(): +@ @algo@@ + return leger + +if __name__ == "__main__": + result = 0 + try: + result = student_code() + except: + print("Unexpected error:", sys.exc_info()[0]) + correct = min(liste) + if correct == liste[result]: + print('True', end='', flush=True) + else: + print('Vous obtenez l\'élément suivant : ' + str(B[result]) + ' au lieu de: ' + str(correct)) diff --git a/Tri_PDS/2_le_plus_leger_custom/task.yaml b/Tri_PDS/2_le_plus_leger_custom/task.yaml new file mode 100644 index 0000000..fef362d --- /dev/null +++ b/Tri_PDS/2_le_plus_leger_custom/task.yaml @@ -0,0 +1,79 @@ +accessible: true +author: Celine Deknop +context: |- + Avant de trier toute une liste, attaquons-nous à un problème plus simple : trouver le plus petit élément. + + Peux-tu créer un algorithme qui retourne le plus petit élément de la liste avec ces blocs ? + + Attention, souviens-toi bien que la liste ne sera pas dans l'ordre ! Tu peux appuyer sur le bouton vert en haut à gauche pour voir ce que tu code fait : la bouteille A est en rouge et la B en bleu. Est-ce que la plus petite valeur est bien en rouge à la fin de l'animation ? +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '10' +name: 1. Le plus léger (blocs custom) +network_grading: false +problems: + algo: + options: + css: true + zoom: + scaleSpeed: 1.2 + maxScale: 3.0 + minScale: 0.3 + controls: true + startScale: 1.0 + wheel: false + grid: + snap: true + colour: '#ccc' + length: 3 + spacing: 20 + maxBlocks: '100' + scrollbars: true + toolboxPosition: start + media: plugins/blockly/static/blockly/media/ + visual: + position: left + files: + - svg_min.js + - insertion_sort.js + workspace: |- + + + + + toolbox: |- + + + + + + + + + + + + + + + + + + + + type: blockly + name: '' + blocks_files: + - customblocks.js + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/Tri_PDS/3_QCM1/task.yaml b/Tri_PDS/3_QCM1/task.yaml new file mode 100644 index 0000000..48be7be --- /dev/null +++ b/Tri_PDS/3_QCM1/task.yaml @@ -0,0 +1,62 @@ +accessible: true +author: '' +context: |- + Peux-tu compter les comparaisons nécéssaires pour trouver les éléments les plus légers des listes suivantes ? + + Attention, souviens-toi que l'ordinateur ne peut pas se rendre compte qu'il a l'élément le plus léger avant de les avoir tous examinés ! +environment: mcq +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: 2. Questions - le plus léger +network_grading: false +problems: + qcm: + header: Combien faut-il faire de comparaisons pour trouver le plus petit élément + de la liste [1,2,3,4] ? + name: '' + choices: + - feedback: N'oublie pas que l'ordinateur ne sait voir que deux chiffres + à la fois ! + text: '1' + - text: '3' + valid: true + - text: '4' + feedback: On ne peut pas faire 4 comparaisons différentes ! + - text: '2' + limit: 0 + type: multiple_choice + qcm2: + choices: + - text: '1' + - text: '2' + - text: '3' + valid: true + - text: '4' + header: Combien faut-il faire de comparaisons pour trouver le plus petit élément + de la liste [4,3,2,1] ? + limit: 0 + name: '' + type: multiple_choice + qcm3: + choices: + - text: '1' + - text: '2' + - valid: true + text: '3' + - text: '4' + name: '' + header: Combien faut-il faire de comparaisons pour trouver le plus petit élément + de la liste [2,1,3,1] ? + type: multiple_choice + limit: 0 +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/Tri_PDS/4_selection_sort/public/customblocks.js b/Tri_PDS/4_selection_sort/public/customblocks.js new file mode 100644 index 0000000..e633a17 --- /dev/null +++ b/Tri_PDS/4_selection_sort/public/customblocks.js @@ -0,0 +1,213 @@ +/* +# Copyright (c) 2018 Ilias Boutchichi +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +*/ + +/** +* @author Ilias Boutchichi +*/ + +'use strict'; + +var newlist = function() { + var liste = []; + for(var i = 0; i<10; i++){ + liste.push(Math.floor((Math.random() * 100) + 1)); + } + return liste; +}; + +var Lst = {}; +Lst.liste = newlist(); + +Blockly.Blocks['custom_for_elem'] = { + init: function() { + this.appendDummyInput() + .appendField("TANT QUE (il reste des bouteilles à comparer)"); + this.appendStatementInput("STAT") + .setCheck(null); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(105); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_for_elem'] = function(block) { + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'for(var i = 1; i < liste.length; i++){\n'+statements_stat+'}\n'; + return code; +}; + +Blockly.Python['custom_for_elem'] = function(block) { + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'for i in range(len(liste)):\n'+statements_stat; + return code; +}; + +Blockly.Blocks['custom_for_all'] = { + init: function() { + this.appendDummyInput() + .appendField("TANT QUE (il reste des bouteilles à trier)"); + this.appendStatementInput("STAT") + .setCheck(null); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(105); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_for_all'] = function(block) { + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'for(var j = 0; j < n; j++){\n'+statements_stat+'}\n'; + return code; +}; + +Blockly.Python['custom_for_all'] = function(block) { + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'for j in range(len(liste)):\n'+statements_stat; + return code; +}; + +Blockly.Blocks['create_plus_leger'] = { + init: function() { + this.appendDummyInput() + .appendField("Choisir une bouteille A"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['create_plus_leger'] = function(block) { + var code = 'var current = 0;\n currentComp(0);\n'; + return code; +}; + +Blockly.Python['create_plus_leger'] = function(block) { + var code = 'leger = 0\n'; + return code; +}; + +Blockly.Blocks['get_other'] = { + init: function() { + this.appendDummyInput() + .appendField("Prendre une bouteille B parmi les autres"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['get_other'] = function(block) { + var code = 'var other = i; otherComp(i)\n'; + return code; +}; + +Blockly.Python['get_other'] = function(block) { + var code = 'other = i\n'; + return code; +}; + +Blockly.Blocks['b_lighter_a'] = { + init: function() { + this.appendDummyInput() + .appendField("la bouteille B est plus légère que la A"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['b_lighter_a'] = function(block) { + var code = 'liste[other] < liste[current]'; + return [code, Blockly.JavaScript.ORDER_RELATIONAL]; +}; + +Blockly.Python['b_lighter_a'] = function(block) { + var code = 'liste[other] < liste[leger]'; + return [code, Blockly.Python.ORDER_RELATIONAL]; +}; + +Blockly.Blocks['b_is_a'] = { + init: function() { + this.appendDummyInput() + .appendField("la bouteille B devient la bouteille A"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['b_is_a'] = function(block) { + var code = 'current = i; currentComp(i);\n'; + return code; +}; + +Blockly.Python['b_is_a'] = function(block) { + var code = 'leger = i\n'; + return code; +}; + +Blockly.Blocks['store_a'] = { + init: function() { + this.appendDummyInput() + .appendField("placer la bouteille A dans la file déjà triée"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['store_a'] = function(block) { + var code = 'move(current);\n liste.splice(current, 1);\n'; + return code; +}; + +Blockly.Python['store_a'] = function(block) { + var code = 'B.append(liste[leger]) \nliste.remove(liste[leger])'; + return code; +}; + +Blockly.Blocks['new_list'] = { + init: function() { + this.appendDummyInput() + .appendField("créer une nouvelle liste"); + this.setNextStatement(true, null); + this.setColour(290); + this.setTooltip("A = []"); + this.setHelpUrl(""); + } +}; + +Blockly.Python['new_list'] = function(block) { + var code = 'global liste, B\nliste = [' + Lst.liste + ']\nB = []\n'; + return code; +}; + +Blockly.JavaScript['new_list'] = function(block) { + var code = 'liste = [' + Lst.liste + '];\nvar n = liste.length;\n'; + return code; +}; \ No newline at end of file diff --git a/Tri_PDS/4_selection_sort/public/example.gif b/Tri_PDS/4_selection_sort/public/example.gif new file mode 100644 index 0000000..96c1b12 Binary files /dev/null and b/Tri_PDS/4_selection_sort/public/example.gif differ diff --git a/Tri_PDS/4_selection_sort/public/insertion_sort.js b/Tri_PDS/4_selection_sort/public/insertion_sort.js new file mode 100644 index 0000000..e982dab --- /dev/null +++ b/Tri_PDS/4_selection_sort/public/insertion_sort.js @@ -0,0 +1,208 @@ +/* +* Copyright (c) 2018 Ilias Boutchichi +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +*/ + +/** +* @author Ilias Boutchichi +*/ + +'use strict'; //more secure + +var TEXT = 0, RECT = 1, INDEX = 2, VALUE = 3, RECT_HEIGHT = 60; + +var Insertion = {}; + +var currentBlue = -1; +var currentRed = -1; +var currentlyClassed = 0; + +Insertion.animations = []; +Insertion.initList = []; +Insertion.case = []; +Insertion.animid = 0; +Insertion.tmp = null; +window.stepSpeed = 500; + +/** + * Interpreter for custom functions in blocks generators + */ +var initInterpreterApi = function(interpreter, scope) { + interpreter.setProperty(scope, 'currentComp', + interpreter.createNativeFunction(function(index) { + if(currentRed != -1){ + Insertion.tmp = Insertion.case[currentRed]; + Insertion.animations.push(Insertion.changeBackgroundColor('none', Insertion.tmp[RECT])); + } + Insertion.tmp = Insertion.case[Number(index)]; + Insertion.animations.push(Insertion.changeBackgroundColor('red', Insertion.tmp[RECT])); + currentRed = Number(index) + if(currentRed == currentBlue){ + currentBlue = -1 + } + })); + + interpreter.setProperty(scope, 'otherComp', + interpreter.createNativeFunction(function(index) { + if(currentBlue != -1){ + Insertion.tmp = Insertion.case[currentBlue]; + Insertion.animations.push(Insertion.changeBackgroundColor('none', Insertion.tmp[RECT])); + } + Insertion.tmp = Insertion.case[Number(index)]; + Insertion.animations.push(Insertion.changeBackgroundColor('blue', Insertion.tmp[RECT])); + currentBlue = Number(index) + })); + + interpreter.setProperty(scope, 'move', + interpreter.createNativeFunction(function(index) { + Insertion.tmp = Insertion.case[Number(index)]; + Insertion.animations.push(Insertion.dmove(0, -Number(index), Insertion.tmp)); + var dist = 0 + if(currentlyClassed == 0) + dist = 5 - Insertion.tmp[RECT].y() + else + dist = 5 + currentlyClassed*RECT_HEIGHT + 2 - Insertion.tmp[RECT].y() + Insertion.animations.push(Insertion.dmove(250, dist, Insertion.tmp)); + currentlyClassed += 1; + Insertion.animations.push(Insertion.changeBackgroundColor('none', Insertion.tmp[RECT])); + currentRed = -1; + if(currentBlue != -1){ + Insertion.tmp = Insertion.case[currentBlue]; + Insertion.animations.push(Insertion.changeBackgroundColor('none', Insertion.tmp[RECT])); + currentBlue = -1; + } + Insertion.tmp = Insertion.case.splice(index, 1); + })); + + Insertion.reset(); +}; + +/** + * Function called after the evaluation of each block for animations + */ +var animate = function() { + Insertion.animate(); +}; + +/** + * Initialisation of Blockly, svg, and display of the random list + */ +Insertion.init = function() { + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" + || Blockly.getMainWorkspace() === null) { + console.warn("Insertion.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Insertion.init, 20); + return; + } + var svg = document.getElementById('blocklySvgZone'); + svg.setAttribute('style', ''); + svg.setAttribute('viewBox', '0, 0, 400, 800'); + svg.setAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml'); + + Insertion.list = Lst.liste; + + var svg = SVG('blocklySvgZone').size('100%', '100%'); + Insertion.list.forEach(function(item, index, array) { + var text = svg.text('' + item).font({ fill: 'black', family: 'Inconsolata', size:40}) + .move(5, index*RECT_HEIGHT); + var rect = svg.rect(100,RECT_HEIGHT).fill('none').stroke({width:4,color:'black'}) + .move(5,RECT_HEIGHT*index); + Insertion.case.push([text,rect,index,item]); + }); + Insertion.initList = Insertion.case.slice(); +}; + +/** + * Reset function called when clicking on the 'restart' button + */ +Insertion.reset = function() { + for(var x=0; x < Insertion.animations.length; x++){ + clearTimeout(Insertion.animations[x]); + } + Insertion.animations = []; + if(currentBlue != -1){ + Insertion.case[currentBlue][RECT].fill("none"); + currentBlue = -1; + } + if(currentRed != -1){ + Insertion.case[currentRed][RECT].fill('none'); + currentRed = -1; + } + Insertion.case = Insertion.initList.slice(); + + Insertion.case.forEach(function(item, index, array) { + item[TEXT].stroke('black').move(5,index*RECT_HEIGHT); + item[RECT].stroke('black').move(5,index*RECT_HEIGHT); + }); +}; + +var Maze = {}; +Maze.reset = Insertion.reset; //Trick 'cause plugin + +/** + * Function that changes the color of a rectangle + * @param {string} color the color value, either common value + * or hexadecimal value + * @param {rectangle} rect the rectangle that needs to change color + */ +Insertion.changeColor = function(color, rect) { + return function() { + rect.animate(window.stepSpeed, '<>').stroke(color); + }; +}; + +Insertion.changeBackgroundColor = function(color, rect) { + return function() { + if(color == 'none'){ + rect.fill(color); + rect.animate(window.stepSpeed, '<>').opacity(1); + }else{ + rect.animate(window.stepSpeed, '<>').fill(color).opacity(0.4); + } + }; +}; + +/** + * Function that move a case (text + rectangle) by adding (x,y) to + * its coordinates + * @param {number} x value to add to the abscissa of the case 'elem' + * @param {number} y value to add to the ordinate of the case 'elem' + * @param {case} elem element representing the case that needs to move + */ +Insertion.dmove = function(x,y,elem) { + return function() { + elem[TEXT].animate(window.stepSpeed, '<>').dmove(x,y); + elem[RECT].animate(window.stepSpeed, '<>').dmove(x,y); + }; +}; + +/** + * My animation function + */ +Insertion.animate = function() { + while(Insertion.animations.length) { + window.setTimeout(Insertion.animations.shift(), + Insertion.animid++*window.stepSpeed); + } + Insertion.animid = 0; +}; + +/** + * Throws a warning if no visual interface is found on the web page, + * otherwise load the visuals + */ +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Insertion.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Tri_PDS/4_selection_sort/public/svg_min.js b/Tri_PDS/4_selection_sort/public/svg_min.js new file mode 100644 index 0000000..b451608 --- /dev/null +++ b/Tri_PDS/4_selection_sort/public/svg_min.js @@ -0,0 +1,3 @@ +/*! svg.js v2.6.5 MIT*/;!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t,t.document)}):"object"==typeof exports?module.exports=t.document?e(t,t.document):function(t){return e(t,t.document)}:t.SVG=e(t,t.document)}("undefined"!=typeof window?window:this,function(t,e){function i(t,e,i,n){return i+n.replace(w.regex.dots," .")}function n(t){for(var e=t.slice(0),i=e.length;i--;)Array.isArray(e[i])&&(e[i]=n(e[i]));return e}function r(t,e){return t instanceof e}function s(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}function o(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function a(t){return t.charAt(0).toUpperCase()+t.slice(1)}function h(t){return 4==t.length?["#",t.substring(1,2),t.substring(1,2),t.substring(2,3),t.substring(2,3),t.substring(3,4),t.substring(3,4)].join(""):t}function u(t){var e=t.toString(16);return 1==e.length?"0"+e:e}function l(t,e,i){if(null==e||null==i){var n=t.bbox();null==e?e=n.width/n.height*i:null==i&&(i=n.height/n.width*e)}return{width:e,height:i}}function c(t,e,i){return{x:e*t.a+i*t.c+0,y:e*t.b+i*t.d+0}}function f(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function d(t){return t instanceof w.Matrix||(t=new w.Matrix(t)),t}function p(t,e){t.cx=null==t.cx?e.bbox().cx:t.cx,t.cy=null==t.cy?e.bbox().cy:t.cy}function m(t){for(var e=0,i=t.length,n="";e=0;i--)e.childNodes[i]instanceof t.SVGElement&&x(e.childNodes[i]);return w.adopt(e).id(w.eid(e.nodeName))}function y(t){return null==t.x&&(t.x=0,t.y=0,t.width=0,t.height=0),t.w=t.width,t.h=t.height,t.x2=t.x+t.width,t.y2=t.y+t.height,t.cx=t.x+t.width/2,t.cy=t.y+t.height/2,t}function v(t){var e=(t||"").toString().match(w.regex.reference);if(e)return e[1]}function g(t){return Math.abs(t)>1e-37?t:0}var w=this.SVG=function(t){if(w.supported)return t=new w.Doc(t),w.parser.draw||w.prepare(),t};if(w.ns="http://www.w3.org/2000/svg",w.xmlns="http://www.w3.org/2000/xmlns/",w.xlink="http://www.w3.org/1999/xlink",w.svgjs="http://svgjs.com/svgjs",w.supported=function(){return!!e.createElementNS&&!!e.createElementNS(w.ns,"svg").createSVGRect}(),!w.supported)return!1;w.did=1e3,w.eid=function(t){return"Svgjs"+a(t)+w.did++},w.create=function(t){var i=e.createElementNS(this.ns,t);return i.setAttribute("id",this.eid(t)),i},w.extend=function(){var t,e,i,n;for(t=[].slice.call(arguments),e=t.pop(),n=t.length-1;n>=0;n--)if(t[n])for(i in e)t[n].prototype[i]=e[i];w.Set&&w.Set.inherit&&w.Set.inherit()},w.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,w.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&w.extend(e,t.extend),t.construct&&w.extend(t.parent||w.Container,t.construct),e},w.adopt=function(e){if(!e)return null;if(e.instance)return e.instance;var i;return i="svg"==e.nodeName?e.parentNode instanceof t.SVGElement?new w.Nested:new w.Doc:"linearGradient"==e.nodeName?new w.Gradient("linear"):"radialGradient"==e.nodeName?new w.Gradient("radial"):w[a(e.nodeName)]?new(w[a(e.nodeName)]):new w.Element(e),i.type=e.nodeName,i.node=e,e.instance=i,i instanceof w.Doc&&i.namespace().defs(),i.setData(JSON.parse(e.getAttribute("svgjs:data"))||{}),i},w.prepare=function(){var t=e.getElementsByTagName("body")[0],i=(t?new w.Doc(t):w.adopt(e.documentElement).nested()).size(2,0);w.parser={body:t||e.documentElement,draw:i.style("opacity:0;position:absolute;left:-100%;top:-100%;overflow:hidden").node,poly:i.polyline().node,path:i.path().node,native:w.create("svg")}},w.parser={native:w.create("svg")},e.addEventListener("DOMContentLoaded",function(){w.parser.draw||w.prepare()},!1),w.regex={numberAndUnit:/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,transforms:/\)\s*,?\s*/,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,isPercent:/^-?[\d\.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,delimiter:/[\s,]+/,hyphen:/([^e])\-/gi,pathLetters:/[MLHVCSQTAZ]/gi,isPathLetter:/[MLHVCSQTAZ]/i,numbersWithDots:/((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi,dots:/\./g},w.utils={map:function(t,e){var i,n=t.length,r=[];for(i=0;i1?1:t,new w.Color({r:~~(this.r+(this.destination.r-this.r)*t),g:~~(this.g+(this.destination.g-this.g)*t),b:~~(this.b+(this.destination.b-this.b)*t)})):this}}),w.Color.test=function(t){return t+="",w.regex.isHex.test(t)||w.regex.isRgb.test(t)},w.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},w.Color.isColor=function(t){return w.Color.isRgb(t)||w.Color.test(t)},w.Array=function(t,e){t=(t||[]).valueOf(),0==t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},w.extend(w.Array,{morph:function(t){if(this.destination=this.parse(t),this.value.length!=this.destination.length){for(var e=this.value[this.value.length-1],i=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(i);for(;this.value.length=0;n--)this.value[n]=[this.value[n][0]+t,this.value[n][1]+e];return this},size:function(t,e){var i,n=this.bbox();for(i=this.value.length-1;i>=0;i--)n.width&&(this.value[i][0]=(this.value[i][0]-n.x)*t/n.width+n.x),n.height&&(this.value[i][1]=(this.value[i][1]-n.y)*e/n.height+n.y);return this},bbox:function(){return w.parser.poly.setAttribute("points",this.toString()),w.parser.poly.getBBox()}});for(var b={M:function(t,e,i){return e.x=i.x=t[0],e.y=i.y=t[1],["M",e.x,e.y]},L:function(t,e){return e.x=t[0],e.y=t[1],["L",t[0],t[1]]},H:function(t,e){return e.x=t[0],["H",t[0]]},V:function(t,e){return e.y=t[0],["V",t[0]]},C:function(t,e){return e.x=t[4],e.y=t[5],["C",t[0],t[1],t[2],t[3],t[4],t[5]]},S:function(t,e){return e.x=t[2],e.y=t[3],["S",t[0],t[1],t[2],t[3]]},Q:function(t,e){return e.x=t[2],e.y=t[3],["Q",t[0],t[1],t[2],t[3]]},T:function(t,e){return e.x=t[0],e.y=t[1],["T",t[0],t[1]]},Z:function(t,e,i){return e.x=i.x,e.y=i.y,["Z"]},A:function(t,e){return e.x=t[5],e.y=t[6],["A",t[0],t[1],t[2],t[3],t[4],t[5],t[6]]}},C="mlhvqtcsaz".split(""),N=0,A=C.length;N=0;r--)n=this.value[r][0],"M"==n||"L"==n||"T"==n?(this.value[r][1]+=t,this.value[r][2]+=e):"H"==n?this.value[r][1]+=t:"V"==n?this.value[r][1]+=e:"C"==n||"S"==n||"Q"==n?(this.value[r][1]+=t,this.value[r][2]+=e,this.value[r][3]+=t,this.value[r][4]+=e,"C"==n&&(this.value[r][5]+=t,this.value[r][6]+=e)):"A"==n&&(this.value[r][6]+=t,this.value[r][7]+=e);return this},size:function(t,e){var i,n,r=this.bbox();for(i=this.value.length-1;i>=0;i--)n=this.value[i][0],"M"==n||"L"==n||"T"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y):"H"==n?this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x:"V"==n?this.value[i][1]=(this.value[i][1]-r.y)*e/r.height+r.y:"C"==n||"S"==n||"Q"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y,this.value[i][3]=(this.value[i][3]-r.x)*t/r.width+r.x,this.value[i][4]=(this.value[i][4]-r.y)*e/r.height+r.y,"C"==n&&(this.value[i][5]=(this.value[i][5]-r.x)*t/r.width+r.x,this.value[i][6]=(this.value[i][6]-r.y)*e/r.height+r.y)):"A"==n&&(this.value[i][1]=this.value[i][1]*t/r.width,this.value[i][2]=this.value[i][2]*e/r.height,this.value[i][6]=(this.value[i][6]-r.x)*t/r.width+r.x,this.value[i][7]=(this.value[i][7]-r.y)*e/r.height+r.y);return this},equalCommands:function(t){var e,i,n;for(t=new w.PathArray(t),n=this.value.length===t.value.length,e=0,i=this.value.length;n&&ea);return n},bbox:function(){return w.parser.path.setAttribute("d",this.toString()),w.parser.path.getBBox()}}),w.Number=w.invent({create:function(t,e){this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:t<0?-3.4e38:3.4e38:"string"==typeof t?(e=t.match(w.regex.numberAndUnit))&&(this.value=parseFloat(e[1]),"%"==e[5]?this.value/=100:"s"==e[5]&&(this.value*=1e3),this.unit=e[5]):t instanceof w.Number&&(this.value=t.valueOf(),this.unit=t.unit)},extend:{toString:function(){return("%"==this.unit?~~(1e8*this.value)/1e6:"s"==this.unit?this.value/1e3:this.value)+this.unit},toJSON:function(){return this.toString()},valueOf:function(){return this.value},plus:function(t){return t=new w.Number(t),new w.Number(this+t,this.unit||t.unit)},minus:function(t){return t=new w.Number(t),new w.Number(this-t,this.unit||t.unit)},times:function(t){return t=new w.Number(t),new w.Number(this*t,this.unit||t.unit)},divide:function(t){return t=new w.Number(t),new w.Number(this/t,this.unit||t.unit)},to:function(t){var e=new w.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new w.Number(t),t.relative&&(this.destination.value+=this.value),this},at:function(t){return this.destination?new w.Number(this.destination).minus(this).times(t).plus(this):this}}}),w.Element=w.invent({create:function(t){this._stroke=w.defaults.attrs.stroke,this._event=null,this.dom={},(this.node=t)&&(this.type=t.nodeName,this.node.instance=this,this._stroke=t.getAttribute("stroke")||this._stroke)},extend:{x:function(t){return this.attr("x",t)},y:function(t){return this.attr("y",t)},cx:function(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)},cy:function(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},width:function(t){return this.attr("width",t)},height:function(t){return this.attr("height",t)},size:function(t,e){var i=l(this,t,e);return this.width(new w.Number(i.width)).height(new w.Number(i.height))},clone:function(t){this.writeDataToDom();var e=x(this.node.cloneNode(!0));return t?t.add(e):this.after(e),e},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(t){return this.after(t).remove(),t},addTo:function(t){return t.put(this)},putIn:function(t){return t.add(this)},id:function(t){return this.attr("id",t)},inside:function(t,e){var i=this.bbox();return t>i.x&&e>i.y&&t/,"").replace(/<\/svg>$/,"");i.innerHTML=""+t.replace(/\n/,"").replace(/<([\w:-]+)([^<]+?)\/>/g,"<$1$2>")+"";for(var n=0,r=i.firstChild.childNodes.length;n":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return 1-Math.cos(t*Math.PI/2)}},w.morph=function(t){return function(e,i){return new w.MorphObj(e,i).at(t)}},w.Situation=w.invent({create:function(t){this.init=!1,this.reversed=!1,this.reversing=!1,this.duration=new w.Number(t.duration).valueOf(),this.delay=new w.Number(t.delay).valueOf(),this.start=+new Date+this.delay,this.finish=this.start+this.duration,this.ease=t.ease,this.loop=0,this.loops=!1,this.animations={},this.attrs={},this.styles={},this.transforms=[],this.once={}}}),w.FX=w.invent({create:function(t){this._target=t,this.situations=[],this.active=!1,this.situation=null,this.paused=!1,this.lastPos=0,this.pos=0,this.absPos=0,this._speed=1},extend:{animate:function(t,e,i){"object"==typeof t&&(e=t.ease,i=t.delay,t=t.duration);var n=new w.Situation({duration:t||1e3,delay:i||0,ease:w.easing[e||"-"]||e});return this.queue(n),this},delay:function(t){var e=new w.Situation({duration:t,delay:0,ease:w.easing["-"]});return this.queue(e)},target:function(t){return t&&t instanceof w.Element?(this._target=t,this):this._target},timeToAbsPos:function(t){return(t-this.situation.start)/(this.situation.duration/this._speed)},absPosToTime:function(t){return this.situation.duration/this._speed*t+this.situation.start},startAnimFrame:function(){this.stopAnimFrame(),this.animationFrame=t.requestAnimationFrame(function(){this.step()}.bind(this))},stopAnimFrame:function(){t.cancelAnimationFrame(this.animationFrame)},start:function(){return!this.active&&this.situation&&(this.active=!0,this.startCurrent()),this},startCurrent:function(){return this.situation.start=+new Date+this.situation.delay/this._speed,this.situation.finish=this.situation.start+this.situation.duration/this._speed,this.initAnimations().step()},queue:function(t){return("function"==typeof t||t instanceof w.Situation)&&this.situations.push(t),this.situation||(this.situation=this.situations.shift()),this},dequeue:function(){return this.stop(),this.situation=this.situations.shift(),this.situation&&(this.situation instanceof w.Situation?this.start():this.situation.call(this)),this},initAnimations:function(){var t,e,i,n=this.situation;if(n.init)return this;for(t in n.animations)for(i=this.target()[t](),Array.isArray(i)||(i=[i]),Array.isArray(n.animations[t])||(n.animations[t]=[n.animations[t]]),e=i.length;e--;)n.animations[t][e]instanceof w.Number&&(i[e]=new w.Number(i[e])),n.animations[t][e]=i[e].morph(n.animations[t][e]);for(t in n.attrs)n.attrs[t]=new w.MorphObj(this.target().attr(t),n.attrs[t]);for(t in n.styles)n.styles[t]=new w.MorphObj(this.target().style(t),n.styles[t]);return n.initialTransformation=this.target().matrixify(),n.init=!0,this},clearQueue:function(){return this.situations=[],this},clearCurrent:function(){return this.situation=null,this},stop:function(t,e){var i=this.active;return this.active=!1,e&&this.clearQueue(),t&&this.situation&&(!i&&this.startCurrent(),this.atEnd()),this.stopAnimFrame(),this.clearCurrent()},reset:function(){if(this.situation){var t=this.situation;this.stop(),this.situation=t,this.atStart()}return this},finish:function(){for(this.stop(!0,!1);this.dequeue().situation&&this.stop(!0,!1););return this.clearQueue().clearCurrent(),this},atStart:function(){return this.at(0,!0)},atEnd:function(){return!0===this.situation.loops&&(this.situation.loops=this.situation.loop+1),"number"==typeof this.situation.loops?this.at(this.situation.loops,!0):this.at(1,!0)},at:function(t,e){var i=this.situation.duration/this._speed;return this.absPos=t,e||(this.situation.reversed&&(this.absPos=1-this.absPos),this.absPos+=this.situation.loop),this.situation.start=+new Date-this.absPos*i,this.situation.finish=this.situation.start+i,this.step(!0)},speed:function(t){return 0===t?this.pause():t?(this._speed=t,this.at(this.absPos,!0)):this._speed},loop:function(t,e){var i=this.last();return i.loops=null==t||t,i.loop=0,e&&(i.reversing=!0),this},pause:function(){return this.paused=!0,this.stopAnimFrame(),this},play:function(){return this.paused?(this.paused=!1,this.at(this.absPos,!0)):this},reverse:function(t){var e=this.last();return e.reversed=void 0===t?!e.reversed:t,this},progress:function(t){return t?this.situation.ease(this.pos):this.pos},after:function(t){var e=this.last(),i=function i(n){n.detail.situation==e&&(t.call(this,e),this.off("finished.fx",i))};return this.target().on("finished.fx",i),this._callStart()},during:function(t){var e=this.last(),i=function(i){i.detail.situation==e&&t.call(this,i.detail.pos,w.morph(i.detail.pos),i.detail.eased,e)};return this.target().off("during.fx",i).on("during.fx",i),this.after(function(){this.off("during.fx",i)}),this._callStart()},afterAll:function(t){var e=function e(i){t.call(this),this.off("allfinished.fx",e)};return this.target().off("allfinished.fx",e).on("allfinished.fx",e),this._callStart()},duringAll:function(t){var e=function(e){t.call(this,e.detail.pos,w.morph(e.detail.pos),e.detail.eased,e.detail.situation)};return this.target().off("during.fx",e).on("during.fx",e),this.afterAll(function(){this.off("during.fx",e)}),this._callStart()},last:function(){return this.situations.length?this.situations[this.situations.length-1]:this.situation},add:function(t,e,i){return this.last()[i||"animations"][t]=e,this._callStart()},step:function(t){if(t||(this.absPos=this.timeToAbsPos(+new Date)),!1!==this.situation.loops){var e,i,n;e=Math.max(this.absPos,0),i=Math.floor(e),!0===this.situation.loops||ithis.lastPos&&s<=r&&(this.situation.once[s].call(this.target(),this.pos,r),delete this.situation.once[s]);return this.active&&this.target().fire("during",{pos:this.pos,eased:r,fx:this,situation:this.situation}),this.situation?(this.eachAt(),1==this.pos&&!this.situation.reversed||this.situation.reversed&&0==this.pos?(this.stopAnimFrame(),this.target().fire("finished",{fx:this,situation:this.situation}),this.situations.length||(this.target().fire("allfinished"),this.situations.length||(this.target().off(".fx"),this.active=!1)),this.active?this.dequeue():this.clearCurrent()):!this.paused&&this.active&&this.startAnimFrame(),this.lastPos=r,this):this},eachAt:function(){var t,e,i,n=this,r=this.target(),s=this.situation;for(t in s.animations)i=[].concat(s.animations[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r[t].apply(r,i);for(t in s.attrs)i=[t].concat(s.attrs[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.attr.apply(r,i);for(t in s.styles)i=[t].concat(s.styles[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.style.apply(r,i);if(s.transforms.length){for(i=s.initialTransformation,t=0,e=s.transforms.length;t=0;--e)this[P[e]]=null!=t[P[e]]?t[P[e]]:i[P[e]]},extend:{extract:function(){var t=c(this,0,1),e=c(this,1,0),i=180/Math.PI*Math.atan2(t.y,t.x)-90;return{x:this.e,y:this.f,transformedX:(this.e*Math.cos(i*Math.PI/180)+this.f*Math.sin(i*Math.PI/180))/Math.sqrt(this.a*this.a+this.b*this.b),transformedY:(this.f*Math.cos(i*Math.PI/180)+this.e*Math.sin(-i*Math.PI/180))/Math.sqrt(this.c*this.c+this.d*this.d),skewX:-i,skewY:180/Math.PI*Math.atan2(e.y,e.x),scaleX:Math.sqrt(this.a*this.a+this.b*this.b),scaleY:Math.sqrt(this.c*this.c+this.d*this.d),rotation:i,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f,matrix:new w.Matrix(this)}},clone:function(){return new w.Matrix(this)},morph:function(t){return this.destination=new w.Matrix(t),this},at:function(t){return this.destination?new w.Matrix({a:this.a+(this.destination.a-this.a)*t,b:this.b+(this.destination.b-this.b)*t,c:this.c+(this.destination.c-this.c)*t,d:this.d+(this.destination.d-this.d)*t,e:this.e+(this.destination.e-this.e)*t,f:this.f+(this.destination.f-this.f)*t}):this},multiply:function(t){return new w.Matrix(this.native().multiply(d(t).native()))},inverse:function(){return new w.Matrix(this.native().inverse())},translate:function(t,e){return new w.Matrix(this.native().translate(t||0,e||0))},scale:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),this.around(i,n,new w.Matrix(t,0,0,e,0,0))},rotate:function(t,e,i){return t=w.utils.radians(t),this.around(e,i,new w.Matrix(Math.cos(t),Math.sin(t),-Math.sin(t),Math.cos(t),0,0))},flip:function(t,e){return"x"==t?this.scale(-1,1,e,0):"y"==t?this.scale(1,-1,0,e):this.scale(-1,-1,t,null!=e?e:t)},skew:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),t=w.utils.radians(t),e=w.utils.radians(e), +this.around(i,n,new w.Matrix(1,Math.tan(e),Math.tan(t),1,0,0))},skewX:function(t,e,i){return this.skew(t,0,e,i)},skewY:function(t,e,i){return this.skew(0,t,e,i)},around:function(t,e,i){return this.multiply(new w.Matrix(1,0,0,1,t||0,e||0)).multiply(i).multiply(new w.Matrix(1,0,0,1,-t||0,-e||0))},native:function(){for(var t=w.parser.native.createSVGMatrix(),e=P.length-1;e>=0;e--)t[P[e]]=this[P[e]];return t},toString:function(){return"matrix("+g(this.a)+","+g(this.b)+","+g(this.c)+","+g(this.d)+","+g(this.e)+","+g(this.f)+")"}},parent:w.Element,construct:{ctm:function(){return new w.Matrix(this.node.getCTM())},screenCTM:function(){if(this instanceof w.Nested){var t=this.rect(1,1),e=t.node.getScreenCTM();return t.remove(),new w.Matrix(e)}return new w.Matrix(this.node.getScreenCTM())}}}),w.Point=w.invent({create:function(t,e){var i,n={x:0,y:0};i=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:null!=t?{x:t,y:null!=e?e:t}:n,this.x=i.x,this.y=i.y},extend:{clone:function(){return new w.Point(this)},morph:function(t,e){return this.destination=new w.Point(t,e),this},at:function(t){return this.destination?new w.Point({x:this.x+(this.destination.x-this.x)*t,y:this.y+(this.destination.y-this.y)*t}):this},native:function(){var t=w.parser.native.createSVGPoint();return t.x=this.x,t.y=this.y,t},transform:function(t){return new w.Point(this.native().matrixTransform(t.native()))}}}),w.extend(w.Element,{point:function(t,e){return new w.Point(t,e).transform(this.screenCTM().inverse())}}),w.extend(w.Element,{attr:function(t,e,i){if(null==t){for(t={},e=this.node.attributes,i=e.length-1;i>=0;i--)t[e[i].nodeName]=w.regex.isNumber.test(e[i].nodeValue)?parseFloat(e[i].nodeValue):e[i].nodeValue;return t}if("object"==typeof t)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return e=this.node.getAttribute(t),null==e?w.defaults.attrs[t]:w.regex.isNumber.test(e)?parseFloat(e):e;"stroke-width"==t?this.attr("stroke",parseFloat(e)>0?this._stroke:null):"stroke"==t&&(this._stroke=e),"fill"!=t&&"stroke"!=t||(w.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),e instanceof w.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new w.Number(e):w.Color.isColor(e)?e=new w.Color(e):Array.isArray(e)&&(e=new w.Array(e)),"leading"==t?this.leading&&this.leading(e):"string"==typeof i?this.node.setAttributeNS(i,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!=t&&"x"!=t||this.rebuild(t,e)}return this}}),w.extend(w.Element,{transform:function(t,e){var i,n,r=this;if("object"!=typeof t)return i=new w.Matrix(r).extract(),"string"==typeof t?i[t]:i;if(i=new w.Matrix(r),e=!!e||!!t.relative,null!=t.a)i=e?i.multiply(new w.Matrix(t)):new w.Matrix(t);else if(null!=t.rotation)p(t,r),i=e?i.rotate(t.rotation,t.cx,t.cy):i.rotate(t.rotation-i.extract().rotation,t.cx,t.cy);else if(null!=t.scale||null!=t.scaleX||null!=t.scaleY){if(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,!e){var s=i.extract();t.scaleX=1*t.scaleX/s.scaleX,t.scaleY=1*t.scaleY/s.scaleY}i=i.scale(t.scaleX,t.scaleY,t.cx,t.cy)}else if(null!=t.skew||null!=t.skewX||null!=t.skewY){if(p(t,r),t.skewX=null!=t.skew?t.skew:null!=t.skewX?t.skewX:0,t.skewY=null!=t.skew?t.skew:null!=t.skewY?t.skewY:0,!e){var s=i.extract();i=i.multiply((new w.Matrix).skew(s.skewX,s.skewY,t.cx,t.cy).inverse())}i=i.skew(t.skewX,t.skewY,t.cx,t.cy)}else t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new w.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(e?i=i.translate(t.x,t.y):(null!=t.x&&(i.e=t.x),null!=t.y&&(i.f=t.y)));return this.attr("transform",i)}}),w.extend(w.FX,{transform:function(t,e){var i,n,r=this.target();return"object"!=typeof t?(i=new w.Matrix(r).extract(),"string"==typeof t?i[t]:i):(e=!!e||!!t.relative,null!=t.a?i=new w.Matrix(t):null!=t.rotation?(p(t,r),i=new w.Rotate(t.rotation,t.cx,t.cy)):null!=t.scale||null!=t.scaleX||null!=t.scaleY?(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,i=new w.Scale(t.scaleX,t.scaleY,t.cx,t.cy)):null!=t.skewX||null!=t.skewY?(p(t,r),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,i=new w.Skew(t.skewX,t.skewY,t.cx,t.cy)):t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new w.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(i=new w.Translate(t.x,t.y)),i?(i.relative=e,this.last().transforms.push(i),this._callStart()):this)}}),w.extend(w.Element,{untransform:function(){return this.attr("transform",null)},matrixify:function(){return(this.attr("transform")||"").split(w.regex.transforms).slice(0,-1).map(function(t){var e=t.trim().split("(");return[e[0],e[1].split(w.regex.delimiter).map(function(t){return parseFloat(t)})]}).reduce(function(t,e){return"matrix"==e[0]?t.multiply(f(e[1])):t[e[0]].apply(t,e[1])},new w.Matrix)},toParent:function(t){if(this==t)return this;var e=this.screenCTM(),i=t.screenCTM().inverse();return this.addTo(t).untransform().transform(i.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),w.Transformation=w.invent({create:function(t,e){if(arguments.length>1&&"boolean"!=typeof e)return this.constructor.call(this,[].slice.call(arguments));if(Array.isArray(t))for(var i=0,n=this.arguments.length;i=0},index:function(t){return[].slice.call(this.node.childNodes).indexOf(t.node)},get:function(t){return w.adopt(this.node.childNodes[t])},first:function(){return this.get(0)},last:function(){return this.get(this.node.childNodes.length-1)},each:function(t,e){var i,n,r=this.children();for(i=0,n=r.length;in/r?this.height/r:this.width/n,this.x=e,this.y=i,this.width=n,this.height=r)}else t="string"==typeof t?t.match(c).map(function(t){return parseFloat(t)}):Array.isArray(t)?t:"object"==typeof t?[t.x,t.y,t.width,t.height]:4==arguments.length?[].slice.call(arguments):h,this.x=t[0],this.y=t[1],this.width=t[2],this.height=t[3]},extend:{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height},morph:function(t,e,i,n){return this.destination=new w.ViewBox(t,e,i,n),this},at:function(t){return this.destination?new w.ViewBox([this.x+(this.destination.x-this.x)*t,this.y+(this.destination.y-this.y)*t,this.width+(this.destination.width-this.width)*t,this.height+(this.destination.height-this.height)*t]):this}},parent:w.Container,construct:{viewbox:function(t,e,i,n){return 0==arguments.length?new w.ViewBox(this):this.attr("viewBox",new w.ViewBox(t,e,i,n))}}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){w.Element.prototype[t]=function(e){return w.on(this.node,t,e),this}}),w.listeners=[],w.handlerMap=[],w.listenerId=0,w.on=function(t,e,i,n,r){var s=i.bind(n||t.instance||t),o=(w.handlerMap.indexOf(t)+1||w.handlerMap.push(t))-1,a=e.split(".")[0],h=e.split(".")[1]||"*";w.listeners[o]=w.listeners[o]||{},w.listeners[o][a]=w.listeners[o][a]||{},w.listeners[o][a][h]=w.listeners[o][a][h]||{},i._svgjsListenerId||(i._svgjsListenerId=++w.listenerId),w.listeners[o][a][h][i._svgjsListenerId]=s,t.addEventListener(a,s,r||!1)},w.off=function(t,e,i){var n=w.handlerMap.indexOf(t),r=e&&e.split(".")[0],s=e&&e.split(".")[1],o="";if(-1!=n)if(i){if("function"==typeof i&&(i=i._svgjsListenerId),!i)return;w.listeners[n][r]&&w.listeners[n][r][s||"*"]&&(t.removeEventListener(r,w.listeners[n][r][s||"*"][i],!1),delete w.listeners[n][r][s||"*"][i])}else if(s&&r){if(w.listeners[n][r]&&w.listeners[n][r][s]){for(i in w.listeners[n][r][s])w.off(t,[r,s].join("."),i);delete w.listeners[n][r][s]}}else if(s)for(e in w.listeners[n])for(o in w.listeners[n][e])s===o&&w.off(t,[e,s].join("."));else if(r){if(w.listeners[n][r]){for(o in w.listeners[n][r])w.off(t,[r,o].join("."));delete w.listeners[n][r]}}else{for(e in w.listeners[n])w.off(t,e);delete w.listeners[n],delete w.handlerMap[n]}},w.extend(w.Element,{on:function(t,e,i,n){return w.on(this.node,t,e,i,n),this},off:function(t,e){return w.off(this.node,t,e),this},fire:function(e,i){return e instanceof t.Event?this.node.dispatchEvent(e):this.node.dispatchEvent(e=new w.CustomEvent(e,{detail:i,cancelable:!0})),this._event=e,this},event:function(){return this._event}}),w.Defs=w.invent({create:"defs",inherit:w.Container}),w.G=w.invent({create:"g",inherit:w.Container,extend:{x:function(t){return null==t?this.transform("x"):this.transform({x:t-this.x()},!0)},y:function(t){return null==t?this.transform("y"):this.transform({y:t-this.y()},!0)},cx:function(t){return null==t?this.gbox().cx:this.x(t-this.gbox().width/2)},cy:function(t){return null==t?this.gbox().cy:this.y(t-this.gbox().height/2)},gbox:function(){var t=this.bbox(),e=this.transform();return t.x+=e.x,t.x2+=e.x,t.cx+=e.x,t.y+=e.y,t.y2+=e.y,t.cy+=e.y,t}},construct:{group:function(){return this.put(new w.G)}}}),w.Doc=w.invent({create:function(t){t&&(t="string"==typeof t?e.getElementById(t):t,"svg"==t.nodeName?this.constructor.call(this,t):(this.constructor.call(this,w.create("svg")),t.appendChild(this.node),this.size("100%","100%")),this.namespace().defs())},inherit:w.Container,extend:{namespace:function(){return this.attr({xmlns:w.ns,version:"1.1"}).attr("xmlns:xlink",w.xlink,w.xmlns).attr("xmlns:svgjs",w.svgjs,w.xmlns)},defs:function(){if(!this._defs){var t;(t=this.node.getElementsByTagName("defs")[0])?this._defs=w.adopt(t):this._defs=new w.Defs,this.node.appendChild(this._defs.node)}return this._defs},parent:function(){return this.node.parentNode&&"#document"!=this.node.parentNode.nodeName?this.node.parentNode:null},spof:function(){var t=this.node.getScreenCTM();return t&&this.style("left",-t.e%1+"px").style("top",-t.f%1+"px"),this},remove:function(){return this.parent()&&this.parent().removeChild(this.node),this},clear:function(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return delete this._defs,w.parser.draw.parentNode||this.node.appendChild(w.parser.draw),this},clone:function(t){this.writeDataToDom();var e=this.node,i=x(e.cloneNode(!0));return t?(t.node||t).appendChild(i.node):e.parentNode.insertBefore(i.node,e.nextSibling),i}}}),w.extend(w.Element,{siblings:function(){return this.parent().children()},position:function(){return this.parent().index(this)},next:function(){return this.siblings()[this.position()+1]},previous:function(){return this.siblings()[this.position()-1]},forward:function(){var t=this.position()+1,e=this.parent();return e.removeElement(this).add(this,t),e instanceof w.Doc&&e.node.appendChild(e.defs().node),this},backward:function(){var t=this.position();return t>0&&this.parent().removeElement(this).add(this,t-1),this},front:function(){var t=this.parent();return t.node.appendChild(this.node),t instanceof w.Doc&&t.node.appendChild(t.defs().node),this},back:function(){return this.position()>0&&this.parent().removeElement(this).add(this,0),this},before:function(t){t.remove();var e=this.position();return this.parent().add(t,e),this},after:function(t){t.remove();var e=this.position();return this.parent().add(t,e+1),this}}),w.Mask=w.invent({create:function(){this.constructor.call(this,w.create("mask")),this.targets=[]},inherit:w.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unmask();return this.targets=[],w.Element.prototype.remove.call(this),this}},construct:{mask:function(){return this.defs().put(new w.Mask)}}}),w.extend(w.Element,{maskWith:function(t){return this.masker=t instanceof w.Mask?t:this.parent().mask().add(t),this.masker.targets.push(this),this.attr("mask",'url("#'+this.masker.attr("id")+'")')},unmask:function(){return delete this.masker,this.attr("mask",null)}}),w.ClipPath=w.invent({create:function(){this.constructor.call(this,w.create("clipPath")),this.targets=[]},inherit:w.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unclip();return this.targets=[],this.parent().removeElement(this),this}},construct:{clip:function(){return this.defs().put(new w.ClipPath)}}}),w.extend(w.Element,{clipWith:function(t){return this.clipper=t instanceof w.ClipPath?t:this.parent().clip().add(t),this.clipper.targets.push(this),this.attr("clip-path",'url("#'+this.clipper.attr("id")+'")')},unclip:function(){return delete this.clipper,this.attr("clip-path",null)}}),w.Gradient=w.invent({create:function(t){this.constructor.call(this,w.create(t+"Gradient")),this.type=t},inherit:w.Container,extend:{at:function(t,e,i){return this.put(new w.Stop).update(t,e,i)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},fill:function(){return"url(#"+this.id()+")"},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="gradientTransform"),w.Container.prototype.attr.call(this,t,e,i)}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),w.extend(w.Gradient,w.FX,{from:function(t,e){return"radial"==(this._target||this).type?this.attr({fx:new w.Number(t),fy:new w.Number(e)}):this.attr({x1:new w.Number(t),y1:new w.Number(e)})},to:function(t,e){return"radial"==(this._target||this).type?this.attr({cx:new w.Number(t),cy:new w.Number(e)}):this.attr({x2:new w.Number(t),y2:new w.Number(e)})}}),w.extend(w.Defs,{gradient:function(t,e){return this.put(new w.Gradient(t)).update(e)}}),w.Stop=w.invent({create:"stop",inherit:w.Element,extend:{update:function(t){return("number"==typeof t||t instanceof w.Number)&&(t={offset:arguments[0],color:arguments[1],opacity:arguments[2]}),null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new w.Number(t.offset)),this}}}),w.Pattern=w.invent({create:"pattern",inherit:w.Container,extend:{fill:function(){return"url(#"+this.id()+")"},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="patternTransform"),w.Container.prototype.attr.call(this,t,e,i)}},construct:{pattern:function(t,e,i){return this.defs().pattern(t,e,i)}}}),w.extend(w.Defs,{pattern:function(t,e,i){return this.put(new w.Pattern).update(i).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),w.Shape=w.invent({create:function(t){this.constructor.call(this,t)},inherit:w.Element}),w.Bare=w.invent({create:function(t,e){if(this.constructor.call(this,w.create(t)),e)for(var i in e.prototype)"function"==typeof e.prototype[i]&&(this[i]=e.prototype[i])},inherit:w.Element,extend:{words:function(t){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this.node.appendChild(e.createTextNode(t)),this}}}),w.extend(w.Parent,{element:function(t,e){return this.put(new w.Bare(t,e))}}),w.Symbol=w.invent({create:"symbol",inherit:w.Container,construct:{symbol:function(){return this.put(new w.Symbol)}}}),w.Use=w.invent({create:"use",inherit:w.Shape,extend:{element:function(t,e){return this.attr("href",(e||"")+"#"+t,w.xlink)}},construct:{use:function(t,e){return this.put(new w.Use).element(t,e)}}}),w.Rect=w.invent({create:"rect",inherit:w.Shape,construct:{rect:function(t,e){return this.put(new w.Rect).size(t,e)}}}),w.Circle=w.invent({create:"circle",inherit:w.Shape,construct:{circle:function(t){return this.put(new w.Circle).rx(new w.Number(t).divide(2)).move(0,0)}}}),w.extend(w.Circle,w.FX,{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),w.Ellipse=w.invent({create:"ellipse",inherit:w.Shape,construct:{ellipse:function(t,e){return this.put(new w.Ellipse).size(t,e).move(0,0)}}}),w.extend(w.Ellipse,w.Rect,w.FX,{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),w.extend(w.Circle,w.Ellipse,{x:function(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())},y:function(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())},cx:function(t){return null==t?this.attr("cx"):this.attr("cx",t)},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",t)},width:function(t){return null==t?2*this.rx():this.rx(new w.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new w.Number(t).divide(2))},size:function(t,e){var i=l(this,t,e);return this.rx(new w.Number(i.width).divide(2)).ry(new w.Number(i.height).divide(2))}}),w.Line=w.invent({create:"line",inherit:w.Shape,extend:{array:function(){return new w.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,i,n){return null==t?this.array():(t=void 0!==e?{x1:t,y1:e,x2:i,y2:n}:new w.PointArray(t).toLine(),this.attr(t))},move:function(t,e){return this.attr(this.array().move(t,e).toLine())},size:function(t,e){var i=l(this,t,e);return this.attr(this.array().size(i.width,i.height).toLine())}},construct:{line:function(t,e,i,n){return w.Line.prototype.plot.apply(this.put(new w.Line),null!=t?[t,e,i,n]:[0,0,0,0])}}}),w.Polyline=w.invent({create:"polyline",inherit:w.Shape,construct:{polyline:function(t){return this.put(new w.Polyline).plot(t||new w.PointArray)}}}),w.Polygon=w.invent({create:"polygon",inherit:w.Shape,construct:{polygon:function(t){return this.put(new w.Polygon).plot(t||new w.PointArray)}}}),w.extend(w.Polyline,w.Polygon,{array:function(){return this._array||(this._array=new w.PointArray(this.attr("points")))},plot:function(t){return null==t?this.array():this.clear().attr("points","string"==typeof t?t:this._array=new w.PointArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("points",this.array().move(t,e))},size:function(t,e){var i=l(this,t,e);return this.attr("points",this.array().size(i.width,i.height))}}),w.extend(w.Line,w.Polyline,w.Polygon,{morphArray:w.PointArray,x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},width:function(t){var e=this.bbox();return null==t?e.width:this.size(t,e.height)},height:function(t){var e=this.bbox();return null==t?e.height:this.size(e.width,t)}}),w.Path=w.invent({create:"path",inherit:w.Shape,extend:{morphArray:w.PathArray,array:function(){return this._array||(this._array=new w.PathArray(this.attr("d")))},plot:function(t){return null==t?this.array():this.clear().attr("d","string"==typeof t?t:this._array=new w.PathArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("d",this.array().move(t,e))},x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},size:function(t,e){var i=l(this,t,e);return this.attr("d",this.array().size(i.width,i.height))},width:function(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)},height:function(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}},construct:{path:function(t){return this.put(new w.Path).plot(t||new w.PathArray)}}}),w.Image=w.invent({create:"image",inherit:w.Shape,extend:{load:function(e){if(!e)return this;var i=this,n=new t.Image;return w.on(n,"load",function(){w.off(n);var t=i.parent(w.Pattern);null!==t&&(0==i.width()&&0==i.height()&&i.size(n.width,n.height),t&&0==t.width()&&0==t.height()&&t.size(i.width(),i.height()),"function"==typeof i._loaded&&i._loaded.call(i,{width:n.width,height:n.height,ratio:n.width/n.height,url:e}))}),w.on(n,"error",function(t){w.off(n),"function"==typeof i._error&&i._error.call(i,t)}),this.attr("href",n.src=this.src=e,w.xlink)},loaded:function(t){return this._loaded=t,this},error:function(t){return this._error=t,this}},construct:{image:function(t,e,i){return this.put(new w.Image).load(t).size(e||0,i||e||0)}}}),w.Text=w.invent({create:function(){this.constructor.call(this,w.create("text")),this.dom.leading=new w.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",w.defaults.attrs["font-family"])},inherit:w.Shape,extend:{x:function(t){return null==t?this.attr("x"):this.attr("x",t)},y:function(t){var e=this.attr("y"),i="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-i:e:this.attr("y","number"==typeof t.valueOf()?t+i:t)},cx:function(t){return null==t?this.bbox().cx:this.x(t-this.bbox().width/2)},cy:function(t){return null==t?this.bbox().cy:this.y(t-this.bbox().height/2)},text:function(t){if(void 0===t){for(var t="",e=this.node.childNodes,i=0,n=e.length;i=0;e--)null!=i[M[t][e]]&&this.attr(M.prefix(t,M[t][e]),i[M[t][e]]);return this},w.extend(w.Element,w.FX,i)}),w.extend(w.Element,w.FX,{rotate:function(t,e,i){return this.transform({rotation:t,cx:e,cy:i})},skew:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({skew:t,cx:e,cy:i}):this.transform({skewX:t,skewY:e,cx:i,cy:n})},scale:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({scale:t,cx:e,cy:i}):this.transform({scaleX:t,scaleY:e,cx:i,cy:n})},translate:function(t,e){return this.transform({x:t,y:e})},flip:function(t,e){return e="number"==typeof t?t:e,this.transform({flip:t||"both",offset:e})},matrix:function(t){return this.attr("transform",new w.Matrix(6==arguments.length?[].slice.call(arguments):t))},opacity:function(t){return this.attr("opacity",t)},dx:function(t){return this.x(new w.Number(t).plus(this instanceof w.FX?0:this.x()),!0)},dy:function(t){return this.y(new w.Number(t).plus(this instanceof w.FX?0:this.y()),!0)},dmove:function(t,e){return this.dx(t).dy(e)}}),w.extend(w.Rect,w.Ellipse,w.Circle,w.Gradient,w.FX,{radius:function(t,e){var i=(this._target||this).type;return"radial"==i||"circle"==i?this.attr("r",new w.Number(t)):this.rx(t).ry(null==e?t:e)}}),w.extend(w.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),w.extend(w.Parent,w.Text,w.Tspan,w.FX,{font:function(t,e){if("object"==typeof t)for(e in t)this.font(e,t[e]);return"leading"==t?this.leading(e):"anchor"==t?this.attr("text-anchor",e):"size"==t||"family"==t||"weight"==t||"stretch"==t||"variant"==t||"style"==t?this.attr("font-"+t,e):this.attr(t,e)}}),w.Set=w.invent({create:function(t){Array.isArray(t)?this.members=t:this.clear()},extend:{add:function(){var t,e,i=[].slice.call(arguments);for(t=0,e=i.length;t-1&&this.members.splice(e,1),this},each:function(t){for(var e=0,i=this.members.length;e=0},index:function(t){return this.members.indexOf(t)},get:function(t){return this.members[t]},first:function(){return this.get(0)},last:function(){return this.get(this.members.length-1)},valueOf:function(){return this.members},bbox:function(){if(0==this.members.length)return new w.RBox;var t=this.members[0].rbox(this.members[0].doc());return this.each(function(){t=t.merge(this.rbox(this.doc()))}),t}},construct:{set:function(t){return new w.Set(t)}}}),w.FX.Set=w.invent({create:function(t){this.set=t}}),w.Set.inherit=function(){ +var t,e=[];for(var t in w.Shape.prototype)"function"==typeof w.Shape.prototype[t]&&"function"!=typeof w.Set.prototype[t]&&e.push(t);e.forEach(function(t){w.Set.prototype[t]=function(){for(var e=0,i=this.members.length;e=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),w.get=function(t){var i=e.getElementById(v(t)||t);return w.adopt(i)},w.select=function(t,i){return new w.Set(w.utils.map((i||e).querySelectorAll(t),function(t){return w.adopt(t)}))},w.extend(w.Parent,{select:function(t){return w.select(t,this.node)}});var P="abcdef".split("");if("function"!=typeof t.CustomEvent){var k=function(t,i){i=i||{bubbles:!1,cancelable:!1,detail:void 0};var n=e.createEvent("CustomEvent");return n.initCustomEvent(t,i.bubbles,i.cancelable,i.detail),n};k.prototype=t.Event.prototype,w.CustomEvent=k}else w.CustomEvent=t.CustomEvent;return function(e){for(var i=0,n=["moz","webkit"],r=0;r. +# + +import os, subprocess, shlex +from inginious import feedback +from inginious import input + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("insertion_sort.py") + + p = subprocess.Popen(shlex.split("python3 insertion_sort.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("Quelque chose s'est mal passé. Veuillez vérifier que l'agencement de vos blocs est correct.") + exit(0) + if make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Félicitations ! La liste est bien triée.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback("Mauvaise réponse. " + make_output) diff --git a/Tri_PDS/4_selection_sort/student/insertion_sort.py b/Tri_PDS/4_selection_sort/student/insertion_sort.py new file mode 100644 index 0000000..c1a4ddf --- /dev/null +++ b/Tri_PDS/4_selection_sort/student/insertion_sort.py @@ -0,0 +1,30 @@ +#!/bin/python3 + +# +# Copyright (c) 2018 Ilias Boutchichi +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +def student_code(): + @@algo@@ + +if __name__ == "__main__": + try: + student_code() + except: + print("Unexpected error:", sys.exc_info()[0]) + correct_list = sorted(B) + if correct_list == B: + print('True', end='', flush=True) + else: + print('Vous obtenez la liste suivante: ' + str(B) + ' au lieu de: ' + str(correct_list)) diff --git a/Tri_PDS/4_selection_sort/task.yaml b/Tri_PDS/4_selection_sort/task.yaml new file mode 100644 index 0000000..fdd60d9 --- /dev/null +++ b/Tri_PDS/4_selection_sort/task.yaml @@ -0,0 +1,100 @@ +accessible: true +author: Celine Deknop +context: |- + Maintenant, utilise l'algorithme *le_plus_léger* (il est déjà fait pour toi) pour trier la liste par sélection. + + Tu peux cliquer sur le bouton vers à gauche pour voir une animation qui correspond à ce que fait le code : l'élément "le plus léger jusqu'ici" est en rouge, et celui qu'on compare, en bleu. + + Une fois que tu as l'élement le plus léger, place-le dans une nouvelle liste et recommence. +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '10' +name: 3. Tri par sélection +network_grading: false +problems: + algo: + options: + css: true + zoom: + scaleSpeed: 1.2 + maxScale: 3.0 + minScale: 0.3 + controls: true + startScale: 1.0 + wheel: false + grid: + snap: true + colour: '#ccc' + length: 3 + spacing: 20 + maxBlocks: '100' + scrollbars: true + toolboxPosition: start + media: plugins/blockly/static/blockly/media/ + visual: + position: left + files: + - svg_min.js + - insertion_sort.js + workspace: |- + + + + + + + + + + + + + + + + + + + + + + + + + + + + toolbox: |- + + + + + + + + + + + + + + + + + + type: blockly + name: '' + blocks_files: + - customblocks.js + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/Tri_PDS/5_QCM2/task.yaml b/Tri_PDS/5_QCM2/task.yaml new file mode 100644 index 0000000..15bf31d --- /dev/null +++ b/Tri_PDS/5_QCM2/task.yaml @@ -0,0 +1,63 @@ +accessible: true +author: '' +context: Maintenant, peux-tu compter les comparaisons nécéssaires pour trier en entier + les listes suivantes ? +environment: mcq +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '30' +name: 4. Questions - tri par sélection +network_grading: false +problems: + qcm: + header: Combien faut-il faire de comparaisons pour trier la liste [1,2,3,4] + ? + name: '' + choices: + - text: '4' + feedback: N'oublie pas que l'ordinateur ne sait voir que deux chiffres + à la fois ! + - text: '5' + - text: '6' + valid: true + feedback: Correct ! Peux-tu trouver une formule pour le calculer facilement + ? + - text: '7' + feedback: On ne peut pas faire 7 comparaisons différentes si on met les + éléments déjà triés à part ! + limit: 0 + type: multiple_choice + qcm2: + choices: + - text: '4' + - text: '5' + - text: '6' + valid: true + - text: '7' + header: Combien faut-il faire de comparaisons pour trier la liste [4,3,2,1] + ? + limit: 0 + name: '' + type: multiple_choice + qcm3: + choices: + - text: '4' + - text: '5' + - valid: true + text: '6' + - text: '7' + name: '' + header: Combien faut-il faire de comparaisons pour trier la liste [2,1,3,1] + ? + type: multiple_choice + limit: 0 +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/Tri_PDS/6_insertion_sort/public/customblocks.js b/Tri_PDS/6_insertion_sort/public/customblocks.js new file mode 100644 index 0000000..96688f5 --- /dev/null +++ b/Tri_PDS/6_insertion_sort/public/customblocks.js @@ -0,0 +1,225 @@ +/* +# Copyright (c) 2018 Ilias Boutchichi +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +*/ + +/** +* @author Ilias Boutchichi +*/ + +'use strict'; + +var newlist = function() { + var liste = []; + for(var i = 0; i<10; i++){ + liste.push(Math.floor((Math.random() * 100) + 1)); + } + return liste; +}; + +var Lst = {}; +Lst.liste = newlist(); + +/******************************************************************************** + ** JavaScript Block Definitions ** + ********************************************************************************/ + +Blockly.Blocks['new_list'] = { + init: function() { + this.appendDummyInput() + .appendField("créer une nouvelle liste 'A'"); + this.setNextStatement(true, null); + this.setColour(290); + this.setTooltip("A = []"); + this.setHelpUrl(""); + } +}; + +Blockly.Blocks['for_each_list'] = { + init: function() { + this.appendDummyInput() + .appendField("Pour chaque indice i de 1 à (longueur de A - 1)"); + this.appendStatementInput("CONTENT") + .setCheck(null); + this.setPreviousStatement(true, null); + this.setColour(120); + this.setTooltip("En Python, for i in range(1, len(A)):"); + this.setHelpUrl(""); + } +}; + +Blockly.Blocks['assign_tmp'] = { + init: function() { + this.appendDummyInput() + .appendField("x = iᵉᵐᵉ élément de A"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(330); + this.setTooltip("x = A[i]"); + this.setHelpUrl(""); + } +}; + +Blockly.Blocks['for_each_greater'] = { + init: function() { + this.appendDummyInput() + .appendField("tant que indice j ≥ 0 et jᵉᵐᵉ élément de A > x"); + this.appendStatementInput("CONTENT") + .setCheck(null); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(120); + this.setTooltip("En python, while j >= 0 and A[j] > x"); + this.setHelpUrl(""); + } +}; + +Blockly.Blocks['assign_index_j'] = { + init: function() { + this.appendDummyInput() + .appendField("indice j = (indice i - 1)"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(330); + this.setTooltip("j = i - 1"); + this.setHelpUrl(""); + } +}; + +Blockly.Blocks['decrease_j'] = { + init: function() { + this.appendDummyInput() + .appendField("décrémenter indice j de 1"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(330); + this.setTooltip("j -= 1"); + this.setHelpUrl(""); + } +}; + +Blockly.Blocks['assign_greater_elem'] = { + init: function() { + this.appendDummyInput() + .appendField("décale le jᵉᵐᵉ élément de A vers le bas"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(290); + this.setTooltip("A[j+1] = A[j]"); + this.setHelpUrl(""); + } +}; + +Blockly.Blocks['put_x'] = { + init: function() { + this.appendDummyInput() + .appendField("place x à la (j+1)ᵉᵐᵉ place de la liste A"); + this.setPreviousStatement(true, null); + this.setColour(290); + this.setTooltip("A[j+1] = x"); + this.setHelpUrl(""); + } +}; + +/******************************************************************************** + ** Python Generator ** + ********************************************************************************/ + +Blockly.Python['new_list'] = function(block) { + var code = 'global A, B\nA = [' + Lst.liste + ']\nB = list(A)\n'; + return code; +}; + +Blockly.Python['for_each_list'] = function(block) { + var statements_content = Blockly.Python.statementToCode(block, 'CONTENT'); + var code = 'for i in range(1,len(A)):\n' + statements_content; + return code; +}; + +Blockly.Python['assign_tmp'] = function(block) { + var code = 'tmp = A[i]\n'; + return code; +}; + +Blockly.Python['for_each_greater'] = function(block) { + var statements_content = Blockly.Python.statementToCode(block, 'CONTENT'); + var code = 'while j>=0 and A[j] > tmp:\n' + statements_content; + return code; +}; + +Blockly.Python['assign_index_j'] = function(block) { + var code = 'j = i-1\n'; + return code; +}; + +Blockly.Python['decrease_j'] = function(block) { + var code = 'j -= 1\n'; + return code; +}; + +Blockly.Python['assign_greater_elem'] = function(block) { + var code = 'A[j+1] = A[j]\n'; + return code; +}; + +Blockly.Python['put_x'] = function(block) { + var code = 'A[j+1] = tmp\n'; + return code; +}; + +/******************************************************************************** + ** JavaScript Generator ** + ********************************************************************************/ + +Blockly.JavaScript['new_list'] = function(block) { + var code = 'A = [' + Lst.liste + '];\nvar n = A.length;\n'; + return code; +}; + +Blockly.JavaScript['for_each_list'] = function(block) { + var statements_content = Blockly.JavaScript.statementToCode(block, 'CONTENT'); + var code = 'for(var i=1; i. +*/ + +/** +* @author Ilias Boutchichi +*/ + +'use strict'; //more secure + +var TEXT = 0, RECT = 1, INDEX = 2, VALUE = 3, RECT_HEIGHT = 70; + +var Insertion = {}; + +Insertion.animations = []; +Insertion.initList = []; +Insertion.case = []; +Insertion.animid = 0; +Insertion.tmp = null; +Insertion.blue = null; +window.stepSpeed = 500; + +/** + * Interpreter for custom functions in blocks generators + */ +var initInterpreterApi = function(interpreter, scope) { + interpreter.setProperty(scope, 'shift_tmp', + interpreter.createNativeFunction(function(index) { + Insertion.tmp = Insertion.case[Number(index)]; + Insertion.animations.push(Insertion.changeColor('red', Insertion.tmp[RECT])); + Insertion.animations.push(Insertion.dmove(250, 0, Insertion.tmp)); + })); + + interpreter.setProperty(scope, 'shift', + interpreter.createNativeFunction(function(index) { + Insertion.animations.push(Insertion.dmove(0, RECT_HEIGHT, Insertion.case[Number(index)])); + Insertion.case[Number(index)+1] = Insertion.case[Number(index)]; + Insertion.case[Number(index)+1][INDEX] = Number(index)+1; + })); + + interpreter.setProperty(scope, 'put', + interpreter.createNativeFunction(function(dy, index) { + Insertion.animations.push(Insertion.dmove(0, -Number(dy), Insertion.tmp)); + Insertion.animations.push(Insertion.dmove(-250, 0, Insertion.tmp)); + Insertion.animations.push(Insertion.changeColor('black', Insertion.tmp[RECT])); + Insertion.case[Number(index)] = Insertion.tmp; + Insertion.case[Number(index)][INDEX] = Number(index); + })); + + Insertion.reset(); +}; + +/** + * Function called after the evaluation of each block for animations + */ +var animate = function() { + Insertion.animate(); +}; + +/** + * Initialisation of Blockly, svg, and display of the random list + */ +Insertion.init = function() { + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" + || Blockly.getMainWorkspace() === null) { + console.warn("Insertion.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Insertion.init, 20); + return; + } + var svg = document.getElementById('blocklySvgZone'); + svg.setAttribute('style', ''); + svg.setAttribute('viewBox', '0, 0, 400, 800'); + svg.setAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml'); + + Insertion.list = Lst.liste; + + var svg = SVG('blocklySvgZone').size('100%', '100%'); + Insertion.list.forEach(function(item, index, array) { + var text = svg.text('' + item).font({ fill: 'black', family: 'Inconsolata', size:40}) + .move(5, index*RECT_HEIGHT); + var rect = svg.rect(100,RECT_HEIGHT).fill('none').stroke({width:4,color:'black'}) + .move(5,RECT_HEIGHT*index); + Insertion.case.push([text,rect,index,item]); + }); + Insertion.initList = Insertion.case.slice(); +}; + +/** + * Reset function called when clicking on the 'restart' button + */ +Insertion.reset = function() { + for(var x=0; x < Insertion.animations.length; x++){ + clearTimeout(Insertion.animations[x]); + } + Insertion.animations = []; + Insertion.case = Insertion.initList.slice(); + + Insertion.case.forEach(function(item, index, array) { + item[TEXT].stroke('black').move(5,index*RECT_HEIGHT); + item[RECT].stroke('black').move(5,index*RECT_HEIGHT); + }); +}; + +var Maze = {}; +Maze.reset = Insertion.reset; + +/** + * Function that changes the color of a rectangle + * @param {string} color the color value, either common value + * or hexadecimal value + * @param {rectangle} rect the rectangle that needs to change color + */ +Insertion.changeColor = function(color, rect) { + return function() { + rect.animate(window.stepSpeed, '<>').stroke(color); + }; +}; + +Insertion.changeBackgroundColor = function(color, rect) { + return function() { + if(color == 'none'){ + rect.fill(color); + rect.animate(window.stepSpeed, '<>').opacity(1); + }else{ + rect.animate(window.stepSpeed, '<>').fill(color).opacity(0.4); + } + }; +}; + +/** + * Function that move a case (text + rectangle) by adding (x,y) to + * its coordinates + * @param {number} x value to add to the abscissa of the case 'elem' + * @param {number} y value to add to the ordinate of the case 'elem' + * @param {case} elem element representing the case that needs to move + */ +Insertion.dmove = function(x,y,elem) { + return function() { + elem[TEXT].animate(window.stepSpeed, '<>').dmove(x,y); + elem[RECT].animate(window.stepSpeed, '<>').dmove(x,y); + }; +}; + +/** + * My animation function + */ +Insertion.animate = function() { + while(Insertion.animations.length) { + window.setTimeout(Insertion.animations.shift(), + Insertion.animid++*window.stepSpeed); + } + Insertion.animid = 0; +}; + +/** + * Throws a warning if no visual interface is found on the web page, + * otherwise load the visuals + */ +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Insertion.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Tri_PDS/6_insertion_sort/public/svg_min.js b/Tri_PDS/6_insertion_sort/public/svg_min.js new file mode 100644 index 0000000..b451608 --- /dev/null +++ b/Tri_PDS/6_insertion_sort/public/svg_min.js @@ -0,0 +1,3 @@ +/*! svg.js v2.6.5 MIT*/;!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t,t.document)}):"object"==typeof exports?module.exports=t.document?e(t,t.document):function(t){return e(t,t.document)}:t.SVG=e(t,t.document)}("undefined"!=typeof window?window:this,function(t,e){function i(t,e,i,n){return i+n.replace(w.regex.dots," .")}function n(t){for(var e=t.slice(0),i=e.length;i--;)Array.isArray(e[i])&&(e[i]=n(e[i]));return e}function r(t,e){return t instanceof e}function s(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}function o(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function a(t){return t.charAt(0).toUpperCase()+t.slice(1)}function h(t){return 4==t.length?["#",t.substring(1,2),t.substring(1,2),t.substring(2,3),t.substring(2,3),t.substring(3,4),t.substring(3,4)].join(""):t}function u(t){var e=t.toString(16);return 1==e.length?"0"+e:e}function l(t,e,i){if(null==e||null==i){var n=t.bbox();null==e?e=n.width/n.height*i:null==i&&(i=n.height/n.width*e)}return{width:e,height:i}}function c(t,e,i){return{x:e*t.a+i*t.c+0,y:e*t.b+i*t.d+0}}function f(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function d(t){return t instanceof w.Matrix||(t=new w.Matrix(t)),t}function p(t,e){t.cx=null==t.cx?e.bbox().cx:t.cx,t.cy=null==t.cy?e.bbox().cy:t.cy}function m(t){for(var e=0,i=t.length,n="";e=0;i--)e.childNodes[i]instanceof t.SVGElement&&x(e.childNodes[i]);return w.adopt(e).id(w.eid(e.nodeName))}function y(t){return null==t.x&&(t.x=0,t.y=0,t.width=0,t.height=0),t.w=t.width,t.h=t.height,t.x2=t.x+t.width,t.y2=t.y+t.height,t.cx=t.x+t.width/2,t.cy=t.y+t.height/2,t}function v(t){var e=(t||"").toString().match(w.regex.reference);if(e)return e[1]}function g(t){return Math.abs(t)>1e-37?t:0}var w=this.SVG=function(t){if(w.supported)return t=new w.Doc(t),w.parser.draw||w.prepare(),t};if(w.ns="http://www.w3.org/2000/svg",w.xmlns="http://www.w3.org/2000/xmlns/",w.xlink="http://www.w3.org/1999/xlink",w.svgjs="http://svgjs.com/svgjs",w.supported=function(){return!!e.createElementNS&&!!e.createElementNS(w.ns,"svg").createSVGRect}(),!w.supported)return!1;w.did=1e3,w.eid=function(t){return"Svgjs"+a(t)+w.did++},w.create=function(t){var i=e.createElementNS(this.ns,t);return i.setAttribute("id",this.eid(t)),i},w.extend=function(){var t,e,i,n;for(t=[].slice.call(arguments),e=t.pop(),n=t.length-1;n>=0;n--)if(t[n])for(i in e)t[n].prototype[i]=e[i];w.Set&&w.Set.inherit&&w.Set.inherit()},w.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,w.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&w.extend(e,t.extend),t.construct&&w.extend(t.parent||w.Container,t.construct),e},w.adopt=function(e){if(!e)return null;if(e.instance)return e.instance;var i;return i="svg"==e.nodeName?e.parentNode instanceof t.SVGElement?new w.Nested:new w.Doc:"linearGradient"==e.nodeName?new w.Gradient("linear"):"radialGradient"==e.nodeName?new w.Gradient("radial"):w[a(e.nodeName)]?new(w[a(e.nodeName)]):new w.Element(e),i.type=e.nodeName,i.node=e,e.instance=i,i instanceof w.Doc&&i.namespace().defs(),i.setData(JSON.parse(e.getAttribute("svgjs:data"))||{}),i},w.prepare=function(){var t=e.getElementsByTagName("body")[0],i=(t?new w.Doc(t):w.adopt(e.documentElement).nested()).size(2,0);w.parser={body:t||e.documentElement,draw:i.style("opacity:0;position:absolute;left:-100%;top:-100%;overflow:hidden").node,poly:i.polyline().node,path:i.path().node,native:w.create("svg")}},w.parser={native:w.create("svg")},e.addEventListener("DOMContentLoaded",function(){w.parser.draw||w.prepare()},!1),w.regex={numberAndUnit:/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,transforms:/\)\s*,?\s*/,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,isPercent:/^-?[\d\.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,delimiter:/[\s,]+/,hyphen:/([^e])\-/gi,pathLetters:/[MLHVCSQTAZ]/gi,isPathLetter:/[MLHVCSQTAZ]/i,numbersWithDots:/((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi,dots:/\./g},w.utils={map:function(t,e){var i,n=t.length,r=[];for(i=0;i1?1:t,new w.Color({r:~~(this.r+(this.destination.r-this.r)*t),g:~~(this.g+(this.destination.g-this.g)*t),b:~~(this.b+(this.destination.b-this.b)*t)})):this}}),w.Color.test=function(t){return t+="",w.regex.isHex.test(t)||w.regex.isRgb.test(t)},w.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},w.Color.isColor=function(t){return w.Color.isRgb(t)||w.Color.test(t)},w.Array=function(t,e){t=(t||[]).valueOf(),0==t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},w.extend(w.Array,{morph:function(t){if(this.destination=this.parse(t),this.value.length!=this.destination.length){for(var e=this.value[this.value.length-1],i=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(i);for(;this.value.length=0;n--)this.value[n]=[this.value[n][0]+t,this.value[n][1]+e];return this},size:function(t,e){var i,n=this.bbox();for(i=this.value.length-1;i>=0;i--)n.width&&(this.value[i][0]=(this.value[i][0]-n.x)*t/n.width+n.x),n.height&&(this.value[i][1]=(this.value[i][1]-n.y)*e/n.height+n.y);return this},bbox:function(){return w.parser.poly.setAttribute("points",this.toString()),w.parser.poly.getBBox()}});for(var b={M:function(t,e,i){return e.x=i.x=t[0],e.y=i.y=t[1],["M",e.x,e.y]},L:function(t,e){return e.x=t[0],e.y=t[1],["L",t[0],t[1]]},H:function(t,e){return e.x=t[0],["H",t[0]]},V:function(t,e){return e.y=t[0],["V",t[0]]},C:function(t,e){return e.x=t[4],e.y=t[5],["C",t[0],t[1],t[2],t[3],t[4],t[5]]},S:function(t,e){return e.x=t[2],e.y=t[3],["S",t[0],t[1],t[2],t[3]]},Q:function(t,e){return e.x=t[2],e.y=t[3],["Q",t[0],t[1],t[2],t[3]]},T:function(t,e){return e.x=t[0],e.y=t[1],["T",t[0],t[1]]},Z:function(t,e,i){return e.x=i.x,e.y=i.y,["Z"]},A:function(t,e){return e.x=t[5],e.y=t[6],["A",t[0],t[1],t[2],t[3],t[4],t[5],t[6]]}},C="mlhvqtcsaz".split(""),N=0,A=C.length;N=0;r--)n=this.value[r][0],"M"==n||"L"==n||"T"==n?(this.value[r][1]+=t,this.value[r][2]+=e):"H"==n?this.value[r][1]+=t:"V"==n?this.value[r][1]+=e:"C"==n||"S"==n||"Q"==n?(this.value[r][1]+=t,this.value[r][2]+=e,this.value[r][3]+=t,this.value[r][4]+=e,"C"==n&&(this.value[r][5]+=t,this.value[r][6]+=e)):"A"==n&&(this.value[r][6]+=t,this.value[r][7]+=e);return this},size:function(t,e){var i,n,r=this.bbox();for(i=this.value.length-1;i>=0;i--)n=this.value[i][0],"M"==n||"L"==n||"T"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y):"H"==n?this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x:"V"==n?this.value[i][1]=(this.value[i][1]-r.y)*e/r.height+r.y:"C"==n||"S"==n||"Q"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y,this.value[i][3]=(this.value[i][3]-r.x)*t/r.width+r.x,this.value[i][4]=(this.value[i][4]-r.y)*e/r.height+r.y,"C"==n&&(this.value[i][5]=(this.value[i][5]-r.x)*t/r.width+r.x,this.value[i][6]=(this.value[i][6]-r.y)*e/r.height+r.y)):"A"==n&&(this.value[i][1]=this.value[i][1]*t/r.width,this.value[i][2]=this.value[i][2]*e/r.height,this.value[i][6]=(this.value[i][6]-r.x)*t/r.width+r.x,this.value[i][7]=(this.value[i][7]-r.y)*e/r.height+r.y);return this},equalCommands:function(t){var e,i,n;for(t=new w.PathArray(t),n=this.value.length===t.value.length,e=0,i=this.value.length;n&&ea);return n},bbox:function(){return w.parser.path.setAttribute("d",this.toString()),w.parser.path.getBBox()}}),w.Number=w.invent({create:function(t,e){this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:t<0?-3.4e38:3.4e38:"string"==typeof t?(e=t.match(w.regex.numberAndUnit))&&(this.value=parseFloat(e[1]),"%"==e[5]?this.value/=100:"s"==e[5]&&(this.value*=1e3),this.unit=e[5]):t instanceof w.Number&&(this.value=t.valueOf(),this.unit=t.unit)},extend:{toString:function(){return("%"==this.unit?~~(1e8*this.value)/1e6:"s"==this.unit?this.value/1e3:this.value)+this.unit},toJSON:function(){return this.toString()},valueOf:function(){return this.value},plus:function(t){return t=new w.Number(t),new w.Number(this+t,this.unit||t.unit)},minus:function(t){return t=new w.Number(t),new w.Number(this-t,this.unit||t.unit)},times:function(t){return t=new w.Number(t),new w.Number(this*t,this.unit||t.unit)},divide:function(t){return t=new w.Number(t),new w.Number(this/t,this.unit||t.unit)},to:function(t){var e=new w.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new w.Number(t),t.relative&&(this.destination.value+=this.value),this},at:function(t){return this.destination?new w.Number(this.destination).minus(this).times(t).plus(this):this}}}),w.Element=w.invent({create:function(t){this._stroke=w.defaults.attrs.stroke,this._event=null,this.dom={},(this.node=t)&&(this.type=t.nodeName,this.node.instance=this,this._stroke=t.getAttribute("stroke")||this._stroke)},extend:{x:function(t){return this.attr("x",t)},y:function(t){return this.attr("y",t)},cx:function(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)},cy:function(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},width:function(t){return this.attr("width",t)},height:function(t){return this.attr("height",t)},size:function(t,e){var i=l(this,t,e);return this.width(new w.Number(i.width)).height(new w.Number(i.height))},clone:function(t){this.writeDataToDom();var e=x(this.node.cloneNode(!0));return t?t.add(e):this.after(e),e},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(t){return this.after(t).remove(),t},addTo:function(t){return t.put(this)},putIn:function(t){return t.add(this)},id:function(t){return this.attr("id",t)},inside:function(t,e){var i=this.bbox();return t>i.x&&e>i.y&&t/,"").replace(/<\/svg>$/,"");i.innerHTML=""+t.replace(/\n/,"").replace(/<([\w:-]+)([^<]+?)\/>/g,"<$1$2>")+"";for(var n=0,r=i.firstChild.childNodes.length;n":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return 1-Math.cos(t*Math.PI/2)}},w.morph=function(t){return function(e,i){return new w.MorphObj(e,i).at(t)}},w.Situation=w.invent({create:function(t){this.init=!1,this.reversed=!1,this.reversing=!1,this.duration=new w.Number(t.duration).valueOf(),this.delay=new w.Number(t.delay).valueOf(),this.start=+new Date+this.delay,this.finish=this.start+this.duration,this.ease=t.ease,this.loop=0,this.loops=!1,this.animations={},this.attrs={},this.styles={},this.transforms=[],this.once={}}}),w.FX=w.invent({create:function(t){this._target=t,this.situations=[],this.active=!1,this.situation=null,this.paused=!1,this.lastPos=0,this.pos=0,this.absPos=0,this._speed=1},extend:{animate:function(t,e,i){"object"==typeof t&&(e=t.ease,i=t.delay,t=t.duration);var n=new w.Situation({duration:t||1e3,delay:i||0,ease:w.easing[e||"-"]||e});return this.queue(n),this},delay:function(t){var e=new w.Situation({duration:t,delay:0,ease:w.easing["-"]});return this.queue(e)},target:function(t){return t&&t instanceof w.Element?(this._target=t,this):this._target},timeToAbsPos:function(t){return(t-this.situation.start)/(this.situation.duration/this._speed)},absPosToTime:function(t){return this.situation.duration/this._speed*t+this.situation.start},startAnimFrame:function(){this.stopAnimFrame(),this.animationFrame=t.requestAnimationFrame(function(){this.step()}.bind(this))},stopAnimFrame:function(){t.cancelAnimationFrame(this.animationFrame)},start:function(){return!this.active&&this.situation&&(this.active=!0,this.startCurrent()),this},startCurrent:function(){return this.situation.start=+new Date+this.situation.delay/this._speed,this.situation.finish=this.situation.start+this.situation.duration/this._speed,this.initAnimations().step()},queue:function(t){return("function"==typeof t||t instanceof w.Situation)&&this.situations.push(t),this.situation||(this.situation=this.situations.shift()),this},dequeue:function(){return this.stop(),this.situation=this.situations.shift(),this.situation&&(this.situation instanceof w.Situation?this.start():this.situation.call(this)),this},initAnimations:function(){var t,e,i,n=this.situation;if(n.init)return this;for(t in n.animations)for(i=this.target()[t](),Array.isArray(i)||(i=[i]),Array.isArray(n.animations[t])||(n.animations[t]=[n.animations[t]]),e=i.length;e--;)n.animations[t][e]instanceof w.Number&&(i[e]=new w.Number(i[e])),n.animations[t][e]=i[e].morph(n.animations[t][e]);for(t in n.attrs)n.attrs[t]=new w.MorphObj(this.target().attr(t),n.attrs[t]);for(t in n.styles)n.styles[t]=new w.MorphObj(this.target().style(t),n.styles[t]);return n.initialTransformation=this.target().matrixify(),n.init=!0,this},clearQueue:function(){return this.situations=[],this},clearCurrent:function(){return this.situation=null,this},stop:function(t,e){var i=this.active;return this.active=!1,e&&this.clearQueue(),t&&this.situation&&(!i&&this.startCurrent(),this.atEnd()),this.stopAnimFrame(),this.clearCurrent()},reset:function(){if(this.situation){var t=this.situation;this.stop(),this.situation=t,this.atStart()}return this},finish:function(){for(this.stop(!0,!1);this.dequeue().situation&&this.stop(!0,!1););return this.clearQueue().clearCurrent(),this},atStart:function(){return this.at(0,!0)},atEnd:function(){return!0===this.situation.loops&&(this.situation.loops=this.situation.loop+1),"number"==typeof this.situation.loops?this.at(this.situation.loops,!0):this.at(1,!0)},at:function(t,e){var i=this.situation.duration/this._speed;return this.absPos=t,e||(this.situation.reversed&&(this.absPos=1-this.absPos),this.absPos+=this.situation.loop),this.situation.start=+new Date-this.absPos*i,this.situation.finish=this.situation.start+i,this.step(!0)},speed:function(t){return 0===t?this.pause():t?(this._speed=t,this.at(this.absPos,!0)):this._speed},loop:function(t,e){var i=this.last();return i.loops=null==t||t,i.loop=0,e&&(i.reversing=!0),this},pause:function(){return this.paused=!0,this.stopAnimFrame(),this},play:function(){return this.paused?(this.paused=!1,this.at(this.absPos,!0)):this},reverse:function(t){var e=this.last();return e.reversed=void 0===t?!e.reversed:t,this},progress:function(t){return t?this.situation.ease(this.pos):this.pos},after:function(t){var e=this.last(),i=function i(n){n.detail.situation==e&&(t.call(this,e),this.off("finished.fx",i))};return this.target().on("finished.fx",i),this._callStart()},during:function(t){var e=this.last(),i=function(i){i.detail.situation==e&&t.call(this,i.detail.pos,w.morph(i.detail.pos),i.detail.eased,e)};return this.target().off("during.fx",i).on("during.fx",i),this.after(function(){this.off("during.fx",i)}),this._callStart()},afterAll:function(t){var e=function e(i){t.call(this),this.off("allfinished.fx",e)};return this.target().off("allfinished.fx",e).on("allfinished.fx",e),this._callStart()},duringAll:function(t){var e=function(e){t.call(this,e.detail.pos,w.morph(e.detail.pos),e.detail.eased,e.detail.situation)};return this.target().off("during.fx",e).on("during.fx",e),this.afterAll(function(){this.off("during.fx",e)}),this._callStart()},last:function(){return this.situations.length?this.situations[this.situations.length-1]:this.situation},add:function(t,e,i){return this.last()[i||"animations"][t]=e,this._callStart()},step:function(t){if(t||(this.absPos=this.timeToAbsPos(+new Date)),!1!==this.situation.loops){var e,i,n;e=Math.max(this.absPos,0),i=Math.floor(e),!0===this.situation.loops||ithis.lastPos&&s<=r&&(this.situation.once[s].call(this.target(),this.pos,r),delete this.situation.once[s]);return this.active&&this.target().fire("during",{pos:this.pos,eased:r,fx:this,situation:this.situation}),this.situation?(this.eachAt(),1==this.pos&&!this.situation.reversed||this.situation.reversed&&0==this.pos?(this.stopAnimFrame(),this.target().fire("finished",{fx:this,situation:this.situation}),this.situations.length||(this.target().fire("allfinished"),this.situations.length||(this.target().off(".fx"),this.active=!1)),this.active?this.dequeue():this.clearCurrent()):!this.paused&&this.active&&this.startAnimFrame(),this.lastPos=r,this):this},eachAt:function(){var t,e,i,n=this,r=this.target(),s=this.situation;for(t in s.animations)i=[].concat(s.animations[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r[t].apply(r,i);for(t in s.attrs)i=[t].concat(s.attrs[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.attr.apply(r,i);for(t in s.styles)i=[t].concat(s.styles[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.style.apply(r,i);if(s.transforms.length){for(i=s.initialTransformation,t=0,e=s.transforms.length;t=0;--e)this[P[e]]=null!=t[P[e]]?t[P[e]]:i[P[e]]},extend:{extract:function(){var t=c(this,0,1),e=c(this,1,0),i=180/Math.PI*Math.atan2(t.y,t.x)-90;return{x:this.e,y:this.f,transformedX:(this.e*Math.cos(i*Math.PI/180)+this.f*Math.sin(i*Math.PI/180))/Math.sqrt(this.a*this.a+this.b*this.b),transformedY:(this.f*Math.cos(i*Math.PI/180)+this.e*Math.sin(-i*Math.PI/180))/Math.sqrt(this.c*this.c+this.d*this.d),skewX:-i,skewY:180/Math.PI*Math.atan2(e.y,e.x),scaleX:Math.sqrt(this.a*this.a+this.b*this.b),scaleY:Math.sqrt(this.c*this.c+this.d*this.d),rotation:i,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f,matrix:new w.Matrix(this)}},clone:function(){return new w.Matrix(this)},morph:function(t){return this.destination=new w.Matrix(t),this},at:function(t){return this.destination?new w.Matrix({a:this.a+(this.destination.a-this.a)*t,b:this.b+(this.destination.b-this.b)*t,c:this.c+(this.destination.c-this.c)*t,d:this.d+(this.destination.d-this.d)*t,e:this.e+(this.destination.e-this.e)*t,f:this.f+(this.destination.f-this.f)*t}):this},multiply:function(t){return new w.Matrix(this.native().multiply(d(t).native()))},inverse:function(){return new w.Matrix(this.native().inverse())},translate:function(t,e){return new w.Matrix(this.native().translate(t||0,e||0))},scale:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),this.around(i,n,new w.Matrix(t,0,0,e,0,0))},rotate:function(t,e,i){return t=w.utils.radians(t),this.around(e,i,new w.Matrix(Math.cos(t),Math.sin(t),-Math.sin(t),Math.cos(t),0,0))},flip:function(t,e){return"x"==t?this.scale(-1,1,e,0):"y"==t?this.scale(1,-1,0,e):this.scale(-1,-1,t,null!=e?e:t)},skew:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),t=w.utils.radians(t),e=w.utils.radians(e), +this.around(i,n,new w.Matrix(1,Math.tan(e),Math.tan(t),1,0,0))},skewX:function(t,e,i){return this.skew(t,0,e,i)},skewY:function(t,e,i){return this.skew(0,t,e,i)},around:function(t,e,i){return this.multiply(new w.Matrix(1,0,0,1,t||0,e||0)).multiply(i).multiply(new w.Matrix(1,0,0,1,-t||0,-e||0))},native:function(){for(var t=w.parser.native.createSVGMatrix(),e=P.length-1;e>=0;e--)t[P[e]]=this[P[e]];return t},toString:function(){return"matrix("+g(this.a)+","+g(this.b)+","+g(this.c)+","+g(this.d)+","+g(this.e)+","+g(this.f)+")"}},parent:w.Element,construct:{ctm:function(){return new w.Matrix(this.node.getCTM())},screenCTM:function(){if(this instanceof w.Nested){var t=this.rect(1,1),e=t.node.getScreenCTM();return t.remove(),new w.Matrix(e)}return new w.Matrix(this.node.getScreenCTM())}}}),w.Point=w.invent({create:function(t,e){var i,n={x:0,y:0};i=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:null!=t?{x:t,y:null!=e?e:t}:n,this.x=i.x,this.y=i.y},extend:{clone:function(){return new w.Point(this)},morph:function(t,e){return this.destination=new w.Point(t,e),this},at:function(t){return this.destination?new w.Point({x:this.x+(this.destination.x-this.x)*t,y:this.y+(this.destination.y-this.y)*t}):this},native:function(){var t=w.parser.native.createSVGPoint();return t.x=this.x,t.y=this.y,t},transform:function(t){return new w.Point(this.native().matrixTransform(t.native()))}}}),w.extend(w.Element,{point:function(t,e){return new w.Point(t,e).transform(this.screenCTM().inverse())}}),w.extend(w.Element,{attr:function(t,e,i){if(null==t){for(t={},e=this.node.attributes,i=e.length-1;i>=0;i--)t[e[i].nodeName]=w.regex.isNumber.test(e[i].nodeValue)?parseFloat(e[i].nodeValue):e[i].nodeValue;return t}if("object"==typeof t)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return e=this.node.getAttribute(t),null==e?w.defaults.attrs[t]:w.regex.isNumber.test(e)?parseFloat(e):e;"stroke-width"==t?this.attr("stroke",parseFloat(e)>0?this._stroke:null):"stroke"==t&&(this._stroke=e),"fill"!=t&&"stroke"!=t||(w.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),e instanceof w.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new w.Number(e):w.Color.isColor(e)?e=new w.Color(e):Array.isArray(e)&&(e=new w.Array(e)),"leading"==t?this.leading&&this.leading(e):"string"==typeof i?this.node.setAttributeNS(i,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!=t&&"x"!=t||this.rebuild(t,e)}return this}}),w.extend(w.Element,{transform:function(t,e){var i,n,r=this;if("object"!=typeof t)return i=new w.Matrix(r).extract(),"string"==typeof t?i[t]:i;if(i=new w.Matrix(r),e=!!e||!!t.relative,null!=t.a)i=e?i.multiply(new w.Matrix(t)):new w.Matrix(t);else if(null!=t.rotation)p(t,r),i=e?i.rotate(t.rotation,t.cx,t.cy):i.rotate(t.rotation-i.extract().rotation,t.cx,t.cy);else if(null!=t.scale||null!=t.scaleX||null!=t.scaleY){if(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,!e){var s=i.extract();t.scaleX=1*t.scaleX/s.scaleX,t.scaleY=1*t.scaleY/s.scaleY}i=i.scale(t.scaleX,t.scaleY,t.cx,t.cy)}else if(null!=t.skew||null!=t.skewX||null!=t.skewY){if(p(t,r),t.skewX=null!=t.skew?t.skew:null!=t.skewX?t.skewX:0,t.skewY=null!=t.skew?t.skew:null!=t.skewY?t.skewY:0,!e){var s=i.extract();i=i.multiply((new w.Matrix).skew(s.skewX,s.skewY,t.cx,t.cy).inverse())}i=i.skew(t.skewX,t.skewY,t.cx,t.cy)}else t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new w.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(e?i=i.translate(t.x,t.y):(null!=t.x&&(i.e=t.x),null!=t.y&&(i.f=t.y)));return this.attr("transform",i)}}),w.extend(w.FX,{transform:function(t,e){var i,n,r=this.target();return"object"!=typeof t?(i=new w.Matrix(r).extract(),"string"==typeof t?i[t]:i):(e=!!e||!!t.relative,null!=t.a?i=new w.Matrix(t):null!=t.rotation?(p(t,r),i=new w.Rotate(t.rotation,t.cx,t.cy)):null!=t.scale||null!=t.scaleX||null!=t.scaleY?(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,i=new w.Scale(t.scaleX,t.scaleY,t.cx,t.cy)):null!=t.skewX||null!=t.skewY?(p(t,r),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,i=new w.Skew(t.skewX,t.skewY,t.cx,t.cy)):t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new w.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(i=new w.Translate(t.x,t.y)),i?(i.relative=e,this.last().transforms.push(i),this._callStart()):this)}}),w.extend(w.Element,{untransform:function(){return this.attr("transform",null)},matrixify:function(){return(this.attr("transform")||"").split(w.regex.transforms).slice(0,-1).map(function(t){var e=t.trim().split("(");return[e[0],e[1].split(w.regex.delimiter).map(function(t){return parseFloat(t)})]}).reduce(function(t,e){return"matrix"==e[0]?t.multiply(f(e[1])):t[e[0]].apply(t,e[1])},new w.Matrix)},toParent:function(t){if(this==t)return this;var e=this.screenCTM(),i=t.screenCTM().inverse();return this.addTo(t).untransform().transform(i.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),w.Transformation=w.invent({create:function(t,e){if(arguments.length>1&&"boolean"!=typeof e)return this.constructor.call(this,[].slice.call(arguments));if(Array.isArray(t))for(var i=0,n=this.arguments.length;i=0},index:function(t){return[].slice.call(this.node.childNodes).indexOf(t.node)},get:function(t){return w.adopt(this.node.childNodes[t])},first:function(){return this.get(0)},last:function(){return this.get(this.node.childNodes.length-1)},each:function(t,e){var i,n,r=this.children();for(i=0,n=r.length;in/r?this.height/r:this.width/n,this.x=e,this.y=i,this.width=n,this.height=r)}else t="string"==typeof t?t.match(c).map(function(t){return parseFloat(t)}):Array.isArray(t)?t:"object"==typeof t?[t.x,t.y,t.width,t.height]:4==arguments.length?[].slice.call(arguments):h,this.x=t[0],this.y=t[1],this.width=t[2],this.height=t[3]},extend:{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height},morph:function(t,e,i,n){return this.destination=new w.ViewBox(t,e,i,n),this},at:function(t){return this.destination?new w.ViewBox([this.x+(this.destination.x-this.x)*t,this.y+(this.destination.y-this.y)*t,this.width+(this.destination.width-this.width)*t,this.height+(this.destination.height-this.height)*t]):this}},parent:w.Container,construct:{viewbox:function(t,e,i,n){return 0==arguments.length?new w.ViewBox(this):this.attr("viewBox",new w.ViewBox(t,e,i,n))}}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){w.Element.prototype[t]=function(e){return w.on(this.node,t,e),this}}),w.listeners=[],w.handlerMap=[],w.listenerId=0,w.on=function(t,e,i,n,r){var s=i.bind(n||t.instance||t),o=(w.handlerMap.indexOf(t)+1||w.handlerMap.push(t))-1,a=e.split(".")[0],h=e.split(".")[1]||"*";w.listeners[o]=w.listeners[o]||{},w.listeners[o][a]=w.listeners[o][a]||{},w.listeners[o][a][h]=w.listeners[o][a][h]||{},i._svgjsListenerId||(i._svgjsListenerId=++w.listenerId),w.listeners[o][a][h][i._svgjsListenerId]=s,t.addEventListener(a,s,r||!1)},w.off=function(t,e,i){var n=w.handlerMap.indexOf(t),r=e&&e.split(".")[0],s=e&&e.split(".")[1],o="";if(-1!=n)if(i){if("function"==typeof i&&(i=i._svgjsListenerId),!i)return;w.listeners[n][r]&&w.listeners[n][r][s||"*"]&&(t.removeEventListener(r,w.listeners[n][r][s||"*"][i],!1),delete w.listeners[n][r][s||"*"][i])}else if(s&&r){if(w.listeners[n][r]&&w.listeners[n][r][s]){for(i in w.listeners[n][r][s])w.off(t,[r,s].join("."),i);delete w.listeners[n][r][s]}}else if(s)for(e in w.listeners[n])for(o in w.listeners[n][e])s===o&&w.off(t,[e,s].join("."));else if(r){if(w.listeners[n][r]){for(o in w.listeners[n][r])w.off(t,[r,o].join("."));delete w.listeners[n][r]}}else{for(e in w.listeners[n])w.off(t,e);delete w.listeners[n],delete w.handlerMap[n]}},w.extend(w.Element,{on:function(t,e,i,n){return w.on(this.node,t,e,i,n),this},off:function(t,e){return w.off(this.node,t,e),this},fire:function(e,i){return e instanceof t.Event?this.node.dispatchEvent(e):this.node.dispatchEvent(e=new w.CustomEvent(e,{detail:i,cancelable:!0})),this._event=e,this},event:function(){return this._event}}),w.Defs=w.invent({create:"defs",inherit:w.Container}),w.G=w.invent({create:"g",inherit:w.Container,extend:{x:function(t){return null==t?this.transform("x"):this.transform({x:t-this.x()},!0)},y:function(t){return null==t?this.transform("y"):this.transform({y:t-this.y()},!0)},cx:function(t){return null==t?this.gbox().cx:this.x(t-this.gbox().width/2)},cy:function(t){return null==t?this.gbox().cy:this.y(t-this.gbox().height/2)},gbox:function(){var t=this.bbox(),e=this.transform();return t.x+=e.x,t.x2+=e.x,t.cx+=e.x,t.y+=e.y,t.y2+=e.y,t.cy+=e.y,t}},construct:{group:function(){return this.put(new w.G)}}}),w.Doc=w.invent({create:function(t){t&&(t="string"==typeof t?e.getElementById(t):t,"svg"==t.nodeName?this.constructor.call(this,t):(this.constructor.call(this,w.create("svg")),t.appendChild(this.node),this.size("100%","100%")),this.namespace().defs())},inherit:w.Container,extend:{namespace:function(){return this.attr({xmlns:w.ns,version:"1.1"}).attr("xmlns:xlink",w.xlink,w.xmlns).attr("xmlns:svgjs",w.svgjs,w.xmlns)},defs:function(){if(!this._defs){var t;(t=this.node.getElementsByTagName("defs")[0])?this._defs=w.adopt(t):this._defs=new w.Defs,this.node.appendChild(this._defs.node)}return this._defs},parent:function(){return this.node.parentNode&&"#document"!=this.node.parentNode.nodeName?this.node.parentNode:null},spof:function(){var t=this.node.getScreenCTM();return t&&this.style("left",-t.e%1+"px").style("top",-t.f%1+"px"),this},remove:function(){return this.parent()&&this.parent().removeChild(this.node),this},clear:function(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return delete this._defs,w.parser.draw.parentNode||this.node.appendChild(w.parser.draw),this},clone:function(t){this.writeDataToDom();var e=this.node,i=x(e.cloneNode(!0));return t?(t.node||t).appendChild(i.node):e.parentNode.insertBefore(i.node,e.nextSibling),i}}}),w.extend(w.Element,{siblings:function(){return this.parent().children()},position:function(){return this.parent().index(this)},next:function(){return this.siblings()[this.position()+1]},previous:function(){return this.siblings()[this.position()-1]},forward:function(){var t=this.position()+1,e=this.parent();return e.removeElement(this).add(this,t),e instanceof w.Doc&&e.node.appendChild(e.defs().node),this},backward:function(){var t=this.position();return t>0&&this.parent().removeElement(this).add(this,t-1),this},front:function(){var t=this.parent();return t.node.appendChild(this.node),t instanceof w.Doc&&t.node.appendChild(t.defs().node),this},back:function(){return this.position()>0&&this.parent().removeElement(this).add(this,0),this},before:function(t){t.remove();var e=this.position();return this.parent().add(t,e),this},after:function(t){t.remove();var e=this.position();return this.parent().add(t,e+1),this}}),w.Mask=w.invent({create:function(){this.constructor.call(this,w.create("mask")),this.targets=[]},inherit:w.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unmask();return this.targets=[],w.Element.prototype.remove.call(this),this}},construct:{mask:function(){return this.defs().put(new w.Mask)}}}),w.extend(w.Element,{maskWith:function(t){return this.masker=t instanceof w.Mask?t:this.parent().mask().add(t),this.masker.targets.push(this),this.attr("mask",'url("#'+this.masker.attr("id")+'")')},unmask:function(){return delete this.masker,this.attr("mask",null)}}),w.ClipPath=w.invent({create:function(){this.constructor.call(this,w.create("clipPath")),this.targets=[]},inherit:w.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unclip();return this.targets=[],this.parent().removeElement(this),this}},construct:{clip:function(){return this.defs().put(new w.ClipPath)}}}),w.extend(w.Element,{clipWith:function(t){return this.clipper=t instanceof w.ClipPath?t:this.parent().clip().add(t),this.clipper.targets.push(this),this.attr("clip-path",'url("#'+this.clipper.attr("id")+'")')},unclip:function(){return delete this.clipper,this.attr("clip-path",null)}}),w.Gradient=w.invent({create:function(t){this.constructor.call(this,w.create(t+"Gradient")),this.type=t},inherit:w.Container,extend:{at:function(t,e,i){return this.put(new w.Stop).update(t,e,i)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},fill:function(){return"url(#"+this.id()+")"},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="gradientTransform"),w.Container.prototype.attr.call(this,t,e,i)}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),w.extend(w.Gradient,w.FX,{from:function(t,e){return"radial"==(this._target||this).type?this.attr({fx:new w.Number(t),fy:new w.Number(e)}):this.attr({x1:new w.Number(t),y1:new w.Number(e)})},to:function(t,e){return"radial"==(this._target||this).type?this.attr({cx:new w.Number(t),cy:new w.Number(e)}):this.attr({x2:new w.Number(t),y2:new w.Number(e)})}}),w.extend(w.Defs,{gradient:function(t,e){return this.put(new w.Gradient(t)).update(e)}}),w.Stop=w.invent({create:"stop",inherit:w.Element,extend:{update:function(t){return("number"==typeof t||t instanceof w.Number)&&(t={offset:arguments[0],color:arguments[1],opacity:arguments[2]}),null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new w.Number(t.offset)),this}}}),w.Pattern=w.invent({create:"pattern",inherit:w.Container,extend:{fill:function(){return"url(#"+this.id()+")"},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="patternTransform"),w.Container.prototype.attr.call(this,t,e,i)}},construct:{pattern:function(t,e,i){return this.defs().pattern(t,e,i)}}}),w.extend(w.Defs,{pattern:function(t,e,i){return this.put(new w.Pattern).update(i).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),w.Shape=w.invent({create:function(t){this.constructor.call(this,t)},inherit:w.Element}),w.Bare=w.invent({create:function(t,e){if(this.constructor.call(this,w.create(t)),e)for(var i in e.prototype)"function"==typeof e.prototype[i]&&(this[i]=e.prototype[i])},inherit:w.Element,extend:{words:function(t){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this.node.appendChild(e.createTextNode(t)),this}}}),w.extend(w.Parent,{element:function(t,e){return this.put(new w.Bare(t,e))}}),w.Symbol=w.invent({create:"symbol",inherit:w.Container,construct:{symbol:function(){return this.put(new w.Symbol)}}}),w.Use=w.invent({create:"use",inherit:w.Shape,extend:{element:function(t,e){return this.attr("href",(e||"")+"#"+t,w.xlink)}},construct:{use:function(t,e){return this.put(new w.Use).element(t,e)}}}),w.Rect=w.invent({create:"rect",inherit:w.Shape,construct:{rect:function(t,e){return this.put(new w.Rect).size(t,e)}}}),w.Circle=w.invent({create:"circle",inherit:w.Shape,construct:{circle:function(t){return this.put(new w.Circle).rx(new w.Number(t).divide(2)).move(0,0)}}}),w.extend(w.Circle,w.FX,{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),w.Ellipse=w.invent({create:"ellipse",inherit:w.Shape,construct:{ellipse:function(t,e){return this.put(new w.Ellipse).size(t,e).move(0,0)}}}),w.extend(w.Ellipse,w.Rect,w.FX,{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),w.extend(w.Circle,w.Ellipse,{x:function(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())},y:function(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())},cx:function(t){return null==t?this.attr("cx"):this.attr("cx",t)},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",t)},width:function(t){return null==t?2*this.rx():this.rx(new w.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new w.Number(t).divide(2))},size:function(t,e){var i=l(this,t,e);return this.rx(new w.Number(i.width).divide(2)).ry(new w.Number(i.height).divide(2))}}),w.Line=w.invent({create:"line",inherit:w.Shape,extend:{array:function(){return new w.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,i,n){return null==t?this.array():(t=void 0!==e?{x1:t,y1:e,x2:i,y2:n}:new w.PointArray(t).toLine(),this.attr(t))},move:function(t,e){return this.attr(this.array().move(t,e).toLine())},size:function(t,e){var i=l(this,t,e);return this.attr(this.array().size(i.width,i.height).toLine())}},construct:{line:function(t,e,i,n){return w.Line.prototype.plot.apply(this.put(new w.Line),null!=t?[t,e,i,n]:[0,0,0,0])}}}),w.Polyline=w.invent({create:"polyline",inherit:w.Shape,construct:{polyline:function(t){return this.put(new w.Polyline).plot(t||new w.PointArray)}}}),w.Polygon=w.invent({create:"polygon",inherit:w.Shape,construct:{polygon:function(t){return this.put(new w.Polygon).plot(t||new w.PointArray)}}}),w.extend(w.Polyline,w.Polygon,{array:function(){return this._array||(this._array=new w.PointArray(this.attr("points")))},plot:function(t){return null==t?this.array():this.clear().attr("points","string"==typeof t?t:this._array=new w.PointArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("points",this.array().move(t,e))},size:function(t,e){var i=l(this,t,e);return this.attr("points",this.array().size(i.width,i.height))}}),w.extend(w.Line,w.Polyline,w.Polygon,{morphArray:w.PointArray,x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},width:function(t){var e=this.bbox();return null==t?e.width:this.size(t,e.height)},height:function(t){var e=this.bbox();return null==t?e.height:this.size(e.width,t)}}),w.Path=w.invent({create:"path",inherit:w.Shape,extend:{morphArray:w.PathArray,array:function(){return this._array||(this._array=new w.PathArray(this.attr("d")))},plot:function(t){return null==t?this.array():this.clear().attr("d","string"==typeof t?t:this._array=new w.PathArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("d",this.array().move(t,e))},x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},size:function(t,e){var i=l(this,t,e);return this.attr("d",this.array().size(i.width,i.height))},width:function(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)},height:function(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}},construct:{path:function(t){return this.put(new w.Path).plot(t||new w.PathArray)}}}),w.Image=w.invent({create:"image",inherit:w.Shape,extend:{load:function(e){if(!e)return this;var i=this,n=new t.Image;return w.on(n,"load",function(){w.off(n);var t=i.parent(w.Pattern);null!==t&&(0==i.width()&&0==i.height()&&i.size(n.width,n.height),t&&0==t.width()&&0==t.height()&&t.size(i.width(),i.height()),"function"==typeof i._loaded&&i._loaded.call(i,{width:n.width,height:n.height,ratio:n.width/n.height,url:e}))}),w.on(n,"error",function(t){w.off(n),"function"==typeof i._error&&i._error.call(i,t)}),this.attr("href",n.src=this.src=e,w.xlink)},loaded:function(t){return this._loaded=t,this},error:function(t){return this._error=t,this}},construct:{image:function(t,e,i){return this.put(new w.Image).load(t).size(e||0,i||e||0)}}}),w.Text=w.invent({create:function(){this.constructor.call(this,w.create("text")),this.dom.leading=new w.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",w.defaults.attrs["font-family"])},inherit:w.Shape,extend:{x:function(t){return null==t?this.attr("x"):this.attr("x",t)},y:function(t){var e=this.attr("y"),i="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-i:e:this.attr("y","number"==typeof t.valueOf()?t+i:t)},cx:function(t){return null==t?this.bbox().cx:this.x(t-this.bbox().width/2)},cy:function(t){return null==t?this.bbox().cy:this.y(t-this.bbox().height/2)},text:function(t){if(void 0===t){for(var t="",e=this.node.childNodes,i=0,n=e.length;i=0;e--)null!=i[M[t][e]]&&this.attr(M.prefix(t,M[t][e]),i[M[t][e]]);return this},w.extend(w.Element,w.FX,i)}),w.extend(w.Element,w.FX,{rotate:function(t,e,i){return this.transform({rotation:t,cx:e,cy:i})},skew:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({skew:t,cx:e,cy:i}):this.transform({skewX:t,skewY:e,cx:i,cy:n})},scale:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({scale:t,cx:e,cy:i}):this.transform({scaleX:t,scaleY:e,cx:i,cy:n})},translate:function(t,e){return this.transform({x:t,y:e})},flip:function(t,e){return e="number"==typeof t?t:e,this.transform({flip:t||"both",offset:e})},matrix:function(t){return this.attr("transform",new w.Matrix(6==arguments.length?[].slice.call(arguments):t))},opacity:function(t){return this.attr("opacity",t)},dx:function(t){return this.x(new w.Number(t).plus(this instanceof w.FX?0:this.x()),!0)},dy:function(t){return this.y(new w.Number(t).plus(this instanceof w.FX?0:this.y()),!0)},dmove:function(t,e){return this.dx(t).dy(e)}}),w.extend(w.Rect,w.Ellipse,w.Circle,w.Gradient,w.FX,{radius:function(t,e){var i=(this._target||this).type;return"radial"==i||"circle"==i?this.attr("r",new w.Number(t)):this.rx(t).ry(null==e?t:e)}}),w.extend(w.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),w.extend(w.Parent,w.Text,w.Tspan,w.FX,{font:function(t,e){if("object"==typeof t)for(e in t)this.font(e,t[e]);return"leading"==t?this.leading(e):"anchor"==t?this.attr("text-anchor",e):"size"==t||"family"==t||"weight"==t||"stretch"==t||"variant"==t||"style"==t?this.attr("font-"+t,e):this.attr(t,e)}}),w.Set=w.invent({create:function(t){Array.isArray(t)?this.members=t:this.clear()},extend:{add:function(){var t,e,i=[].slice.call(arguments);for(t=0,e=i.length;t-1&&this.members.splice(e,1),this},each:function(t){for(var e=0,i=this.members.length;e=0},index:function(t){return this.members.indexOf(t)},get:function(t){return this.members[t]},first:function(){return this.get(0)},last:function(){return this.get(this.members.length-1)},valueOf:function(){return this.members},bbox:function(){if(0==this.members.length)return new w.RBox;var t=this.members[0].rbox(this.members[0].doc());return this.each(function(){t=t.merge(this.rbox(this.doc()))}),t}},construct:{set:function(t){return new w.Set(t)}}}),w.FX.Set=w.invent({create:function(t){this.set=t}}),w.Set.inherit=function(){ +var t,e=[];for(var t in w.Shape.prototype)"function"==typeof w.Shape.prototype[t]&&"function"!=typeof w.Set.prototype[t]&&e.push(t);e.forEach(function(t){w.Set.prototype[t]=function(){for(var e=0,i=this.members.length;e=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),w.get=function(t){var i=e.getElementById(v(t)||t);return w.adopt(i)},w.select=function(t,i){return new w.Set(w.utils.map((i||e).querySelectorAll(t),function(t){return w.adopt(t)}))},w.extend(w.Parent,{select:function(t){return w.select(t,this.node)}});var P="abcdef".split("");if("function"!=typeof t.CustomEvent){var k=function(t,i){i=i||{bubbles:!1,cancelable:!1,detail:void 0};var n=e.createEvent("CustomEvent");return n.initCustomEvent(t,i.bubbles,i.cancelable,i.detail),n};k.prototype=t.Event.prototype,w.CustomEvent=k}else w.CustomEvent=t.CustomEvent;return function(e){for(var i=0,n=["moz","webkit"],r=0;r. +# + +import os, subprocess, shlex +from inginious import feedback +from inginious import input + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("insertion_sort.py") + + p = subprocess.Popen(shlex.split("python3 insertion_sort.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("Quelque chose s'est mal passée. Veuillez vérifier que l'agencement de vos blocs est correct.") + exit(0) + if make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Félicitations! La liste est bien triée.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback("Mauvaise réponse. " + make_output) diff --git a/Tri_PDS/6_insertion_sort/student/insertion_sort.py b/Tri_PDS/6_insertion_sort/student/insertion_sort.py new file mode 100644 index 0000000..09da623 --- /dev/null +++ b/Tri_PDS/6_insertion_sort/student/insertion_sort.py @@ -0,0 +1,33 @@ +#!/bin/python3 + +# +# Copyright (c) 2018 Ilias Boutchichi +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +A = [] +B = [] + +def student_code(): + @@algo@@ + +if __name__ == "__main__": + try: + student_code() + except: + print("Unexpected error:", sys.exc_info()[0]) + correct_list = sorted(B) + if correct_list == A: + print('True', end='', flush=True) + else: + print('Vous obtenez la liste suivante: ' + str(A) + ' au lieu de: ' + str(correct_list)) diff --git a/Tri_PDS/6_insertion_sort/task.yaml b/Tri_PDS/6_insertion_sort/task.yaml new file mode 100644 index 0000000..c93f846 --- /dev/null +++ b/Tri_PDS/6_insertion_sort/task.yaml @@ -0,0 +1,74 @@ +accessible: true +author: Ilias Boutchichi +context: |- + Le tri par insertion est celui que tu fais naturellement si tu trie un paquet de carte : tu prends les cartes une à une et les mets à leur place. + + Regarde l'animation et compte les comparaisons. Est-ce mieux ou moins bien que le tri par sélection ? Est-ce que le nombre de comparaison est toujours le même ? +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '10' +name: 7. Tri par insertion +network_grading: false +problems: + algo: + options: + css: true + zoom: + scaleSpeed: 1.2 + maxScale: 3.0 + minScale: 0.3 + controls: true + startScale: 1.0 + wheel: false + grid: + snap: true + colour: '#ccc' + length: 3 + spacing: 20 + maxBlocks: '8' + scrollbars: true + toolboxPosition: start + media: plugins/blockly/static/blockly/media/ + visual: + position: left + files: + - svg_min.js + - insertion_sort.js + workspace: |- + + + + + toolbox: |- + + + + + + + + + + + + + + + + + type: blockly + name: '' + blocks_files: + - customblocks.js + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/Tri_PDS/6_insertion_sort1/public/customblocks.js b/Tri_PDS/6_insertion_sort1/public/customblocks.js new file mode 100644 index 0000000..c43877f --- /dev/null +++ b/Tri_PDS/6_insertion_sort1/public/customblocks.js @@ -0,0 +1,222 @@ +/* +# Copyright (c) 2018 Ilias Boutchichi +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +*/ + +/** +* @author Ilias Boutchichi +*/ + +'use strict'; + +var newlist = function() { + var liste = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; + return liste; +}; + +var Lst = {}; +Lst.liste = newlist(); + +/******************************************************************************** + ** JavaScript Block Definitions ** + ********************************************************************************/ + +Blockly.Blocks['new_list'] = { + init: function() { + this.appendDummyInput() + .appendField("créer une nouvelle liste 'A'"); + this.setNextStatement(true, null); + this.setColour(290); + this.setTooltip("A = []"); + this.setHelpUrl(""); + } +}; + +Blockly.Blocks['for_each_list'] = { + init: function() { + this.appendDummyInput() + .appendField("Pour chaque indice i de 1 à (longueur de A - 1)"); + this.appendStatementInput("CONTENT") + .setCheck(null); + this.setPreviousStatement(true, null); + this.setColour(120); + this.setTooltip("En Python, for i in range(1, len(A)):"); + this.setHelpUrl(""); + } +}; + +Blockly.Blocks['assign_tmp'] = { + init: function() { + this.appendDummyInput() + .appendField("x = iᵉᵐᵉ élément de A"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(330); + this.setTooltip("x = A[i]"); + this.setHelpUrl(""); + } +}; + +Blockly.Blocks['for_each_greater'] = { + init: function() { + this.appendDummyInput() + .appendField("tant que indice j ≥ 0 et jᵉᵐᵉ élément de A > x"); + this.appendStatementInput("CONTENT") + .setCheck(null); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(120); + this.setTooltip("En python, while j >= 0 and A[j] > x"); + this.setHelpUrl(""); + } +}; + +Blockly.Blocks['assign_index_j'] = { + init: function() { + this.appendDummyInput() + .appendField("indice j = (indice i - 1)"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(330); + this.setTooltip("j = i - 1"); + this.setHelpUrl(""); + } +}; + +Blockly.Blocks['decrease_j'] = { + init: function() { + this.appendDummyInput() + .appendField("décrémenter indice j de 1"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(330); + this.setTooltip("j -= 1"); + this.setHelpUrl(""); + } +}; + +Blockly.Blocks['assign_greater_elem'] = { + init: function() { + this.appendDummyInput() + .appendField("décale le jᵉᵐᵉ élément de A vers le bas"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(290); + this.setTooltip("A[j+1] = A[j]"); + this.setHelpUrl(""); + } +}; + +Blockly.Blocks['put_x'] = { + init: function() { + this.appendDummyInput() + .appendField("place x à la (j+1)ᵉᵐᵉ place de la liste A"); + this.setPreviousStatement(true, null); + this.setColour(290); + this.setTooltip("A[j+1] = x"); + this.setHelpUrl(""); + } +}; + +/******************************************************************************** + ** Python Generator ** + ********************************************************************************/ + +Blockly.Python['new_list'] = function(block) { + var code = 'global A, B\nA = [' + Lst.liste + ']\nB = list(A)\n'; + return code; +}; + +Blockly.Python['for_each_list'] = function(block) { + var statements_content = Blockly.Python.statementToCode(block, 'CONTENT'); + var code = 'for i in range(1,len(A)):\n' + statements_content; + return code; +}; + +Blockly.Python['assign_tmp'] = function(block) { + var code = 'tmp = A[i]\n'; + return code; +}; + +Blockly.Python['for_each_greater'] = function(block) { + var statements_content = Blockly.Python.statementToCode(block, 'CONTENT'); + var code = 'while j>=0 and A[j] > tmp:\n' + statements_content; + return code; +}; + +Blockly.Python['assign_index_j'] = function(block) { + var code = 'j = i-1\n'; + return code; +}; + +Blockly.Python['decrease_j'] = function(block) { + var code = 'j -= 1\n'; + return code; +}; + +Blockly.Python['assign_greater_elem'] = function(block) { + var code = 'A[j+1] = A[j]\n'; + return code; +}; + +Blockly.Python['put_x'] = function(block) { + var code = 'A[j+1] = tmp\n'; + return code; +}; + +/******************************************************************************** + ** JavaScript Generator ** + ********************************************************************************/ + +Blockly.JavaScript['new_list'] = function(block) { + var code = 'A = [' + Lst.liste + '];\nvar n = A.length;\n'; + return code; +}; + +Blockly.JavaScript['for_each_list'] = function(block) { + var statements_content = Blockly.JavaScript.statementToCode(block, 'CONTENT'); + var code = 'for(var i=1; i. +*/ + +/** +* @author Ilias Boutchichi +*/ + +'use strict'; //more secure + +var TEXT = 0, RECT = 1, INDEX = 2, VALUE = 3, RECT_HEIGHT = 70; + +var Insertion = {}; + +Insertion.animations = []; +Insertion.initList = []; +Insertion.case = []; +Insertion.animid = 0; +Insertion.tmp = null; +Insertion.blue = null; +window.stepSpeed = 500; + +/** + * Interpreter for custom functions in blocks generators + */ +var initInterpreterApi = function(interpreter, scope) { + interpreter.setProperty(scope, 'shift_tmp', + interpreter.createNativeFunction(function(index) { + Insertion.tmp = Insertion.case[Number(index)]; + Insertion.animations.push(Insertion.changeColor('red', Insertion.tmp[RECT])); + Insertion.animations.push(Insertion.dmove(250, 0, Insertion.tmp)); + })); + + interpreter.setProperty(scope, 'shift', + interpreter.createNativeFunction(function(index) { + Insertion.animations.push(Insertion.dmove(0, RECT_HEIGHT, Insertion.case[Number(index)])); + Insertion.case[Number(index)+1] = Insertion.case[Number(index)]; + Insertion.case[Number(index)+1][INDEX] = Number(index)+1; + })); + + interpreter.setProperty(scope, 'put', + interpreter.createNativeFunction(function(dy, index) { + Insertion.animations.push(Insertion.dmove(0, -Number(dy), Insertion.tmp)); + Insertion.animations.push(Insertion.dmove(-250, 0, Insertion.tmp)); + Insertion.animations.push(Insertion.changeColor('black', Insertion.tmp[RECT])); + Insertion.case[Number(index)] = Insertion.tmp; + Insertion.case[Number(index)][INDEX] = Number(index); + })); + + Insertion.reset(); +}; + +/** + * Function called after the evaluation of each block for animations + */ +var animate = function() { + Insertion.animate(); +}; + +/** + * Initialisation of Blockly, svg, and display of the random list + */ +Insertion.init = function() { + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" + || Blockly.getMainWorkspace() === null) { + console.warn("Insertion.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Insertion.init, 20); + return; + } + var svg = document.getElementById('blocklySvgZone'); + svg.setAttribute('style', ''); + svg.setAttribute('viewBox', '0, 0, 400, 800'); + svg.setAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml'); + + Insertion.list = Lst.liste; + + var svg = SVG('blocklySvgZone').size('100%', '100%'); + Insertion.list.forEach(function(item, index, array) { + var text = svg.text('' + item).font({ fill: 'black', family: 'Inconsolata', size:40}) + .move(5, index*RECT_HEIGHT); + var rect = svg.rect(100,RECT_HEIGHT).fill('none').stroke({width:4,color:'black'}) + .move(5,RECT_HEIGHT*index); + Insertion.case.push([text,rect,index,item]); + }); + Insertion.initList = Insertion.case.slice(); +}; + +/** + * Reset function called when clicking on the 'restart' button + */ +Insertion.reset = function() { + for(var x=0; x < Insertion.animations.length; x++){ + clearTimeout(Insertion.animations[x]); + } + Insertion.animations = []; + Insertion.case = Insertion.initList.slice(); + + Insertion.case.forEach(function(item, index, array) { + item[TEXT].stroke('black').move(5,index*RECT_HEIGHT); + item[RECT].stroke('black').move(5,index*RECT_HEIGHT); + }); +}; + +var Maze = {}; +Maze.reset = Insertion.reset; + +/** + * Function that changes the color of a rectangle + * @param {string} color the color value, either common value + * or hexadecimal value + * @param {rectangle} rect the rectangle that needs to change color + */ +Insertion.changeColor = function(color, rect) { + return function() { + rect.animate(window.stepSpeed, '<>').stroke(color); + }; +}; + +Insertion.changeBackgroundColor = function(color, rect) { + return function() { + if(color == 'none'){ + rect.fill(color); + rect.animate(window.stepSpeed, '<>').opacity(1); + }else{ + rect.animate(window.stepSpeed, '<>').fill(color).opacity(0.4); + } + }; +}; + +/** + * Function that move a case (text + rectangle) by adding (x,y) to + * its coordinates + * @param {number} x value to add to the abscissa of the case 'elem' + * @param {number} y value to add to the ordinate of the case 'elem' + * @param {case} elem element representing the case that needs to move + */ +Insertion.dmove = function(x,y,elem) { + return function() { + elem[TEXT].animate(window.stepSpeed, '<>').dmove(x,y); + elem[RECT].animate(window.stepSpeed, '<>').dmove(x,y); + }; +}; + +/** + * My animation function + */ +Insertion.animate = function() { + while(Insertion.animations.length) { + window.setTimeout(Insertion.animations.shift(), + Insertion.animid++*window.stepSpeed); + } + Insertion.animid = 0; +}; + +/** + * Throws a warning if no visual interface is found on the web page, + * otherwise load the visuals + */ +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Insertion.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Tri_PDS/6_insertion_sort1/public/svg_min.js b/Tri_PDS/6_insertion_sort1/public/svg_min.js new file mode 100644 index 0000000..b451608 --- /dev/null +++ b/Tri_PDS/6_insertion_sort1/public/svg_min.js @@ -0,0 +1,3 @@ +/*! svg.js v2.6.5 MIT*/;!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t,t.document)}):"object"==typeof exports?module.exports=t.document?e(t,t.document):function(t){return e(t,t.document)}:t.SVG=e(t,t.document)}("undefined"!=typeof window?window:this,function(t,e){function i(t,e,i,n){return i+n.replace(w.regex.dots," .")}function n(t){for(var e=t.slice(0),i=e.length;i--;)Array.isArray(e[i])&&(e[i]=n(e[i]));return e}function r(t,e){return t instanceof e}function s(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}function o(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function a(t){return t.charAt(0).toUpperCase()+t.slice(1)}function h(t){return 4==t.length?["#",t.substring(1,2),t.substring(1,2),t.substring(2,3),t.substring(2,3),t.substring(3,4),t.substring(3,4)].join(""):t}function u(t){var e=t.toString(16);return 1==e.length?"0"+e:e}function l(t,e,i){if(null==e||null==i){var n=t.bbox();null==e?e=n.width/n.height*i:null==i&&(i=n.height/n.width*e)}return{width:e,height:i}}function c(t,e,i){return{x:e*t.a+i*t.c+0,y:e*t.b+i*t.d+0}}function f(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function d(t){return t instanceof w.Matrix||(t=new w.Matrix(t)),t}function p(t,e){t.cx=null==t.cx?e.bbox().cx:t.cx,t.cy=null==t.cy?e.bbox().cy:t.cy}function m(t){for(var e=0,i=t.length,n="";e=0;i--)e.childNodes[i]instanceof t.SVGElement&&x(e.childNodes[i]);return w.adopt(e).id(w.eid(e.nodeName))}function y(t){return null==t.x&&(t.x=0,t.y=0,t.width=0,t.height=0),t.w=t.width,t.h=t.height,t.x2=t.x+t.width,t.y2=t.y+t.height,t.cx=t.x+t.width/2,t.cy=t.y+t.height/2,t}function v(t){var e=(t||"").toString().match(w.regex.reference);if(e)return e[1]}function g(t){return Math.abs(t)>1e-37?t:0}var w=this.SVG=function(t){if(w.supported)return t=new w.Doc(t),w.parser.draw||w.prepare(),t};if(w.ns="http://www.w3.org/2000/svg",w.xmlns="http://www.w3.org/2000/xmlns/",w.xlink="http://www.w3.org/1999/xlink",w.svgjs="http://svgjs.com/svgjs",w.supported=function(){return!!e.createElementNS&&!!e.createElementNS(w.ns,"svg").createSVGRect}(),!w.supported)return!1;w.did=1e3,w.eid=function(t){return"Svgjs"+a(t)+w.did++},w.create=function(t){var i=e.createElementNS(this.ns,t);return i.setAttribute("id",this.eid(t)),i},w.extend=function(){var t,e,i,n;for(t=[].slice.call(arguments),e=t.pop(),n=t.length-1;n>=0;n--)if(t[n])for(i in e)t[n].prototype[i]=e[i];w.Set&&w.Set.inherit&&w.Set.inherit()},w.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,w.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&w.extend(e,t.extend),t.construct&&w.extend(t.parent||w.Container,t.construct),e},w.adopt=function(e){if(!e)return null;if(e.instance)return e.instance;var i;return i="svg"==e.nodeName?e.parentNode instanceof t.SVGElement?new w.Nested:new w.Doc:"linearGradient"==e.nodeName?new w.Gradient("linear"):"radialGradient"==e.nodeName?new w.Gradient("radial"):w[a(e.nodeName)]?new(w[a(e.nodeName)]):new w.Element(e),i.type=e.nodeName,i.node=e,e.instance=i,i instanceof w.Doc&&i.namespace().defs(),i.setData(JSON.parse(e.getAttribute("svgjs:data"))||{}),i},w.prepare=function(){var t=e.getElementsByTagName("body")[0],i=(t?new w.Doc(t):w.adopt(e.documentElement).nested()).size(2,0);w.parser={body:t||e.documentElement,draw:i.style("opacity:0;position:absolute;left:-100%;top:-100%;overflow:hidden").node,poly:i.polyline().node,path:i.path().node,native:w.create("svg")}},w.parser={native:w.create("svg")},e.addEventListener("DOMContentLoaded",function(){w.parser.draw||w.prepare()},!1),w.regex={numberAndUnit:/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,transforms:/\)\s*,?\s*/,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,isPercent:/^-?[\d\.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,delimiter:/[\s,]+/,hyphen:/([^e])\-/gi,pathLetters:/[MLHVCSQTAZ]/gi,isPathLetter:/[MLHVCSQTAZ]/i,numbersWithDots:/((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi,dots:/\./g},w.utils={map:function(t,e){var i,n=t.length,r=[];for(i=0;i1?1:t,new w.Color({r:~~(this.r+(this.destination.r-this.r)*t),g:~~(this.g+(this.destination.g-this.g)*t),b:~~(this.b+(this.destination.b-this.b)*t)})):this}}),w.Color.test=function(t){return t+="",w.regex.isHex.test(t)||w.regex.isRgb.test(t)},w.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},w.Color.isColor=function(t){return w.Color.isRgb(t)||w.Color.test(t)},w.Array=function(t,e){t=(t||[]).valueOf(),0==t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},w.extend(w.Array,{morph:function(t){if(this.destination=this.parse(t),this.value.length!=this.destination.length){for(var e=this.value[this.value.length-1],i=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(i);for(;this.value.length=0;n--)this.value[n]=[this.value[n][0]+t,this.value[n][1]+e];return this},size:function(t,e){var i,n=this.bbox();for(i=this.value.length-1;i>=0;i--)n.width&&(this.value[i][0]=(this.value[i][0]-n.x)*t/n.width+n.x),n.height&&(this.value[i][1]=(this.value[i][1]-n.y)*e/n.height+n.y);return this},bbox:function(){return w.parser.poly.setAttribute("points",this.toString()),w.parser.poly.getBBox()}});for(var b={M:function(t,e,i){return e.x=i.x=t[0],e.y=i.y=t[1],["M",e.x,e.y]},L:function(t,e){return e.x=t[0],e.y=t[1],["L",t[0],t[1]]},H:function(t,e){return e.x=t[0],["H",t[0]]},V:function(t,e){return e.y=t[0],["V",t[0]]},C:function(t,e){return e.x=t[4],e.y=t[5],["C",t[0],t[1],t[2],t[3],t[4],t[5]]},S:function(t,e){return e.x=t[2],e.y=t[3],["S",t[0],t[1],t[2],t[3]]},Q:function(t,e){return e.x=t[2],e.y=t[3],["Q",t[0],t[1],t[2],t[3]]},T:function(t,e){return e.x=t[0],e.y=t[1],["T",t[0],t[1]]},Z:function(t,e,i){return e.x=i.x,e.y=i.y,["Z"]},A:function(t,e){return e.x=t[5],e.y=t[6],["A",t[0],t[1],t[2],t[3],t[4],t[5],t[6]]}},C="mlhvqtcsaz".split(""),N=0,A=C.length;N=0;r--)n=this.value[r][0],"M"==n||"L"==n||"T"==n?(this.value[r][1]+=t,this.value[r][2]+=e):"H"==n?this.value[r][1]+=t:"V"==n?this.value[r][1]+=e:"C"==n||"S"==n||"Q"==n?(this.value[r][1]+=t,this.value[r][2]+=e,this.value[r][3]+=t,this.value[r][4]+=e,"C"==n&&(this.value[r][5]+=t,this.value[r][6]+=e)):"A"==n&&(this.value[r][6]+=t,this.value[r][7]+=e);return this},size:function(t,e){var i,n,r=this.bbox();for(i=this.value.length-1;i>=0;i--)n=this.value[i][0],"M"==n||"L"==n||"T"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y):"H"==n?this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x:"V"==n?this.value[i][1]=(this.value[i][1]-r.y)*e/r.height+r.y:"C"==n||"S"==n||"Q"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y,this.value[i][3]=(this.value[i][3]-r.x)*t/r.width+r.x,this.value[i][4]=(this.value[i][4]-r.y)*e/r.height+r.y,"C"==n&&(this.value[i][5]=(this.value[i][5]-r.x)*t/r.width+r.x,this.value[i][6]=(this.value[i][6]-r.y)*e/r.height+r.y)):"A"==n&&(this.value[i][1]=this.value[i][1]*t/r.width,this.value[i][2]=this.value[i][2]*e/r.height,this.value[i][6]=(this.value[i][6]-r.x)*t/r.width+r.x,this.value[i][7]=(this.value[i][7]-r.y)*e/r.height+r.y);return this},equalCommands:function(t){var e,i,n;for(t=new w.PathArray(t),n=this.value.length===t.value.length,e=0,i=this.value.length;n&&ea);return n},bbox:function(){return w.parser.path.setAttribute("d",this.toString()),w.parser.path.getBBox()}}),w.Number=w.invent({create:function(t,e){this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:t<0?-3.4e38:3.4e38:"string"==typeof t?(e=t.match(w.regex.numberAndUnit))&&(this.value=parseFloat(e[1]),"%"==e[5]?this.value/=100:"s"==e[5]&&(this.value*=1e3),this.unit=e[5]):t instanceof w.Number&&(this.value=t.valueOf(),this.unit=t.unit)},extend:{toString:function(){return("%"==this.unit?~~(1e8*this.value)/1e6:"s"==this.unit?this.value/1e3:this.value)+this.unit},toJSON:function(){return this.toString()},valueOf:function(){return this.value},plus:function(t){return t=new w.Number(t),new w.Number(this+t,this.unit||t.unit)},minus:function(t){return t=new w.Number(t),new w.Number(this-t,this.unit||t.unit)},times:function(t){return t=new w.Number(t),new w.Number(this*t,this.unit||t.unit)},divide:function(t){return t=new w.Number(t),new w.Number(this/t,this.unit||t.unit)},to:function(t){var e=new w.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new w.Number(t),t.relative&&(this.destination.value+=this.value),this},at:function(t){return this.destination?new w.Number(this.destination).minus(this).times(t).plus(this):this}}}),w.Element=w.invent({create:function(t){this._stroke=w.defaults.attrs.stroke,this._event=null,this.dom={},(this.node=t)&&(this.type=t.nodeName,this.node.instance=this,this._stroke=t.getAttribute("stroke")||this._stroke)},extend:{x:function(t){return this.attr("x",t)},y:function(t){return this.attr("y",t)},cx:function(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)},cy:function(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},width:function(t){return this.attr("width",t)},height:function(t){return this.attr("height",t)},size:function(t,e){var i=l(this,t,e);return this.width(new w.Number(i.width)).height(new w.Number(i.height))},clone:function(t){this.writeDataToDom();var e=x(this.node.cloneNode(!0));return t?t.add(e):this.after(e),e},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(t){return this.after(t).remove(),t},addTo:function(t){return t.put(this)},putIn:function(t){return t.add(this)},id:function(t){return this.attr("id",t)},inside:function(t,e){var i=this.bbox();return t>i.x&&e>i.y&&t/,"").replace(/<\/svg>$/,"");i.innerHTML=""+t.replace(/\n/,"").replace(/<([\w:-]+)([^<]+?)\/>/g,"<$1$2>")+"";for(var n=0,r=i.firstChild.childNodes.length;n":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return 1-Math.cos(t*Math.PI/2)}},w.morph=function(t){return function(e,i){return new w.MorphObj(e,i).at(t)}},w.Situation=w.invent({create:function(t){this.init=!1,this.reversed=!1,this.reversing=!1,this.duration=new w.Number(t.duration).valueOf(),this.delay=new w.Number(t.delay).valueOf(),this.start=+new Date+this.delay,this.finish=this.start+this.duration,this.ease=t.ease,this.loop=0,this.loops=!1,this.animations={},this.attrs={},this.styles={},this.transforms=[],this.once={}}}),w.FX=w.invent({create:function(t){this._target=t,this.situations=[],this.active=!1,this.situation=null,this.paused=!1,this.lastPos=0,this.pos=0,this.absPos=0,this._speed=1},extend:{animate:function(t,e,i){"object"==typeof t&&(e=t.ease,i=t.delay,t=t.duration);var n=new w.Situation({duration:t||1e3,delay:i||0,ease:w.easing[e||"-"]||e});return this.queue(n),this},delay:function(t){var e=new w.Situation({duration:t,delay:0,ease:w.easing["-"]});return this.queue(e)},target:function(t){return t&&t instanceof w.Element?(this._target=t,this):this._target},timeToAbsPos:function(t){return(t-this.situation.start)/(this.situation.duration/this._speed)},absPosToTime:function(t){return this.situation.duration/this._speed*t+this.situation.start},startAnimFrame:function(){this.stopAnimFrame(),this.animationFrame=t.requestAnimationFrame(function(){this.step()}.bind(this))},stopAnimFrame:function(){t.cancelAnimationFrame(this.animationFrame)},start:function(){return!this.active&&this.situation&&(this.active=!0,this.startCurrent()),this},startCurrent:function(){return this.situation.start=+new Date+this.situation.delay/this._speed,this.situation.finish=this.situation.start+this.situation.duration/this._speed,this.initAnimations().step()},queue:function(t){return("function"==typeof t||t instanceof w.Situation)&&this.situations.push(t),this.situation||(this.situation=this.situations.shift()),this},dequeue:function(){return this.stop(),this.situation=this.situations.shift(),this.situation&&(this.situation instanceof w.Situation?this.start():this.situation.call(this)),this},initAnimations:function(){var t,e,i,n=this.situation;if(n.init)return this;for(t in n.animations)for(i=this.target()[t](),Array.isArray(i)||(i=[i]),Array.isArray(n.animations[t])||(n.animations[t]=[n.animations[t]]),e=i.length;e--;)n.animations[t][e]instanceof w.Number&&(i[e]=new w.Number(i[e])),n.animations[t][e]=i[e].morph(n.animations[t][e]);for(t in n.attrs)n.attrs[t]=new w.MorphObj(this.target().attr(t),n.attrs[t]);for(t in n.styles)n.styles[t]=new w.MorphObj(this.target().style(t),n.styles[t]);return n.initialTransformation=this.target().matrixify(),n.init=!0,this},clearQueue:function(){return this.situations=[],this},clearCurrent:function(){return this.situation=null,this},stop:function(t,e){var i=this.active;return this.active=!1,e&&this.clearQueue(),t&&this.situation&&(!i&&this.startCurrent(),this.atEnd()),this.stopAnimFrame(),this.clearCurrent()},reset:function(){if(this.situation){var t=this.situation;this.stop(),this.situation=t,this.atStart()}return this},finish:function(){for(this.stop(!0,!1);this.dequeue().situation&&this.stop(!0,!1););return this.clearQueue().clearCurrent(),this},atStart:function(){return this.at(0,!0)},atEnd:function(){return!0===this.situation.loops&&(this.situation.loops=this.situation.loop+1),"number"==typeof this.situation.loops?this.at(this.situation.loops,!0):this.at(1,!0)},at:function(t,e){var i=this.situation.duration/this._speed;return this.absPos=t,e||(this.situation.reversed&&(this.absPos=1-this.absPos),this.absPos+=this.situation.loop),this.situation.start=+new Date-this.absPos*i,this.situation.finish=this.situation.start+i,this.step(!0)},speed:function(t){return 0===t?this.pause():t?(this._speed=t,this.at(this.absPos,!0)):this._speed},loop:function(t,e){var i=this.last();return i.loops=null==t||t,i.loop=0,e&&(i.reversing=!0),this},pause:function(){return this.paused=!0,this.stopAnimFrame(),this},play:function(){return this.paused?(this.paused=!1,this.at(this.absPos,!0)):this},reverse:function(t){var e=this.last();return e.reversed=void 0===t?!e.reversed:t,this},progress:function(t){return t?this.situation.ease(this.pos):this.pos},after:function(t){var e=this.last(),i=function i(n){n.detail.situation==e&&(t.call(this,e),this.off("finished.fx",i))};return this.target().on("finished.fx",i),this._callStart()},during:function(t){var e=this.last(),i=function(i){i.detail.situation==e&&t.call(this,i.detail.pos,w.morph(i.detail.pos),i.detail.eased,e)};return this.target().off("during.fx",i).on("during.fx",i),this.after(function(){this.off("during.fx",i)}),this._callStart()},afterAll:function(t){var e=function e(i){t.call(this),this.off("allfinished.fx",e)};return this.target().off("allfinished.fx",e).on("allfinished.fx",e),this._callStart()},duringAll:function(t){var e=function(e){t.call(this,e.detail.pos,w.morph(e.detail.pos),e.detail.eased,e.detail.situation)};return this.target().off("during.fx",e).on("during.fx",e),this.afterAll(function(){this.off("during.fx",e)}),this._callStart()},last:function(){return this.situations.length?this.situations[this.situations.length-1]:this.situation},add:function(t,e,i){return this.last()[i||"animations"][t]=e,this._callStart()},step:function(t){if(t||(this.absPos=this.timeToAbsPos(+new Date)),!1!==this.situation.loops){var e,i,n;e=Math.max(this.absPos,0),i=Math.floor(e),!0===this.situation.loops||ithis.lastPos&&s<=r&&(this.situation.once[s].call(this.target(),this.pos,r),delete this.situation.once[s]);return this.active&&this.target().fire("during",{pos:this.pos,eased:r,fx:this,situation:this.situation}),this.situation?(this.eachAt(),1==this.pos&&!this.situation.reversed||this.situation.reversed&&0==this.pos?(this.stopAnimFrame(),this.target().fire("finished",{fx:this,situation:this.situation}),this.situations.length||(this.target().fire("allfinished"),this.situations.length||(this.target().off(".fx"),this.active=!1)),this.active?this.dequeue():this.clearCurrent()):!this.paused&&this.active&&this.startAnimFrame(),this.lastPos=r,this):this},eachAt:function(){var t,e,i,n=this,r=this.target(),s=this.situation;for(t in s.animations)i=[].concat(s.animations[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r[t].apply(r,i);for(t in s.attrs)i=[t].concat(s.attrs[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.attr.apply(r,i);for(t in s.styles)i=[t].concat(s.styles[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.style.apply(r,i);if(s.transforms.length){for(i=s.initialTransformation,t=0,e=s.transforms.length;t=0;--e)this[P[e]]=null!=t[P[e]]?t[P[e]]:i[P[e]]},extend:{extract:function(){var t=c(this,0,1),e=c(this,1,0),i=180/Math.PI*Math.atan2(t.y,t.x)-90;return{x:this.e,y:this.f,transformedX:(this.e*Math.cos(i*Math.PI/180)+this.f*Math.sin(i*Math.PI/180))/Math.sqrt(this.a*this.a+this.b*this.b),transformedY:(this.f*Math.cos(i*Math.PI/180)+this.e*Math.sin(-i*Math.PI/180))/Math.sqrt(this.c*this.c+this.d*this.d),skewX:-i,skewY:180/Math.PI*Math.atan2(e.y,e.x),scaleX:Math.sqrt(this.a*this.a+this.b*this.b),scaleY:Math.sqrt(this.c*this.c+this.d*this.d),rotation:i,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f,matrix:new w.Matrix(this)}},clone:function(){return new w.Matrix(this)},morph:function(t){return this.destination=new w.Matrix(t),this},at:function(t){return this.destination?new w.Matrix({a:this.a+(this.destination.a-this.a)*t,b:this.b+(this.destination.b-this.b)*t,c:this.c+(this.destination.c-this.c)*t,d:this.d+(this.destination.d-this.d)*t,e:this.e+(this.destination.e-this.e)*t,f:this.f+(this.destination.f-this.f)*t}):this},multiply:function(t){return new w.Matrix(this.native().multiply(d(t).native()))},inverse:function(){return new w.Matrix(this.native().inverse())},translate:function(t,e){return new w.Matrix(this.native().translate(t||0,e||0))},scale:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),this.around(i,n,new w.Matrix(t,0,0,e,0,0))},rotate:function(t,e,i){return t=w.utils.radians(t),this.around(e,i,new w.Matrix(Math.cos(t),Math.sin(t),-Math.sin(t),Math.cos(t),0,0))},flip:function(t,e){return"x"==t?this.scale(-1,1,e,0):"y"==t?this.scale(1,-1,0,e):this.scale(-1,-1,t,null!=e?e:t)},skew:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),t=w.utils.radians(t),e=w.utils.radians(e), +this.around(i,n,new w.Matrix(1,Math.tan(e),Math.tan(t),1,0,0))},skewX:function(t,e,i){return this.skew(t,0,e,i)},skewY:function(t,e,i){return this.skew(0,t,e,i)},around:function(t,e,i){return this.multiply(new w.Matrix(1,0,0,1,t||0,e||0)).multiply(i).multiply(new w.Matrix(1,0,0,1,-t||0,-e||0))},native:function(){for(var t=w.parser.native.createSVGMatrix(),e=P.length-1;e>=0;e--)t[P[e]]=this[P[e]];return t},toString:function(){return"matrix("+g(this.a)+","+g(this.b)+","+g(this.c)+","+g(this.d)+","+g(this.e)+","+g(this.f)+")"}},parent:w.Element,construct:{ctm:function(){return new w.Matrix(this.node.getCTM())},screenCTM:function(){if(this instanceof w.Nested){var t=this.rect(1,1),e=t.node.getScreenCTM();return t.remove(),new w.Matrix(e)}return new w.Matrix(this.node.getScreenCTM())}}}),w.Point=w.invent({create:function(t,e){var i,n={x:0,y:0};i=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:null!=t?{x:t,y:null!=e?e:t}:n,this.x=i.x,this.y=i.y},extend:{clone:function(){return new w.Point(this)},morph:function(t,e){return this.destination=new w.Point(t,e),this},at:function(t){return this.destination?new w.Point({x:this.x+(this.destination.x-this.x)*t,y:this.y+(this.destination.y-this.y)*t}):this},native:function(){var t=w.parser.native.createSVGPoint();return t.x=this.x,t.y=this.y,t},transform:function(t){return new w.Point(this.native().matrixTransform(t.native()))}}}),w.extend(w.Element,{point:function(t,e){return new w.Point(t,e).transform(this.screenCTM().inverse())}}),w.extend(w.Element,{attr:function(t,e,i){if(null==t){for(t={},e=this.node.attributes,i=e.length-1;i>=0;i--)t[e[i].nodeName]=w.regex.isNumber.test(e[i].nodeValue)?parseFloat(e[i].nodeValue):e[i].nodeValue;return t}if("object"==typeof t)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return e=this.node.getAttribute(t),null==e?w.defaults.attrs[t]:w.regex.isNumber.test(e)?parseFloat(e):e;"stroke-width"==t?this.attr("stroke",parseFloat(e)>0?this._stroke:null):"stroke"==t&&(this._stroke=e),"fill"!=t&&"stroke"!=t||(w.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),e instanceof w.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new w.Number(e):w.Color.isColor(e)?e=new w.Color(e):Array.isArray(e)&&(e=new w.Array(e)),"leading"==t?this.leading&&this.leading(e):"string"==typeof i?this.node.setAttributeNS(i,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!=t&&"x"!=t||this.rebuild(t,e)}return this}}),w.extend(w.Element,{transform:function(t,e){var i,n,r=this;if("object"!=typeof t)return i=new w.Matrix(r).extract(),"string"==typeof t?i[t]:i;if(i=new w.Matrix(r),e=!!e||!!t.relative,null!=t.a)i=e?i.multiply(new w.Matrix(t)):new w.Matrix(t);else if(null!=t.rotation)p(t,r),i=e?i.rotate(t.rotation,t.cx,t.cy):i.rotate(t.rotation-i.extract().rotation,t.cx,t.cy);else if(null!=t.scale||null!=t.scaleX||null!=t.scaleY){if(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,!e){var s=i.extract();t.scaleX=1*t.scaleX/s.scaleX,t.scaleY=1*t.scaleY/s.scaleY}i=i.scale(t.scaleX,t.scaleY,t.cx,t.cy)}else if(null!=t.skew||null!=t.skewX||null!=t.skewY){if(p(t,r),t.skewX=null!=t.skew?t.skew:null!=t.skewX?t.skewX:0,t.skewY=null!=t.skew?t.skew:null!=t.skewY?t.skewY:0,!e){var s=i.extract();i=i.multiply((new w.Matrix).skew(s.skewX,s.skewY,t.cx,t.cy).inverse())}i=i.skew(t.skewX,t.skewY,t.cx,t.cy)}else t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new w.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(e?i=i.translate(t.x,t.y):(null!=t.x&&(i.e=t.x),null!=t.y&&(i.f=t.y)));return this.attr("transform",i)}}),w.extend(w.FX,{transform:function(t,e){var i,n,r=this.target();return"object"!=typeof t?(i=new w.Matrix(r).extract(),"string"==typeof t?i[t]:i):(e=!!e||!!t.relative,null!=t.a?i=new w.Matrix(t):null!=t.rotation?(p(t,r),i=new w.Rotate(t.rotation,t.cx,t.cy)):null!=t.scale||null!=t.scaleX||null!=t.scaleY?(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,i=new w.Scale(t.scaleX,t.scaleY,t.cx,t.cy)):null!=t.skewX||null!=t.skewY?(p(t,r),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,i=new w.Skew(t.skewX,t.skewY,t.cx,t.cy)):t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new w.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(i=new w.Translate(t.x,t.y)),i?(i.relative=e,this.last().transforms.push(i),this._callStart()):this)}}),w.extend(w.Element,{untransform:function(){return this.attr("transform",null)},matrixify:function(){return(this.attr("transform")||"").split(w.regex.transforms).slice(0,-1).map(function(t){var e=t.trim().split("(");return[e[0],e[1].split(w.regex.delimiter).map(function(t){return parseFloat(t)})]}).reduce(function(t,e){return"matrix"==e[0]?t.multiply(f(e[1])):t[e[0]].apply(t,e[1])},new w.Matrix)},toParent:function(t){if(this==t)return this;var e=this.screenCTM(),i=t.screenCTM().inverse();return this.addTo(t).untransform().transform(i.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),w.Transformation=w.invent({create:function(t,e){if(arguments.length>1&&"boolean"!=typeof e)return this.constructor.call(this,[].slice.call(arguments));if(Array.isArray(t))for(var i=0,n=this.arguments.length;i=0},index:function(t){return[].slice.call(this.node.childNodes).indexOf(t.node)},get:function(t){return w.adopt(this.node.childNodes[t])},first:function(){return this.get(0)},last:function(){return this.get(this.node.childNodes.length-1)},each:function(t,e){var i,n,r=this.children();for(i=0,n=r.length;in/r?this.height/r:this.width/n,this.x=e,this.y=i,this.width=n,this.height=r)}else t="string"==typeof t?t.match(c).map(function(t){return parseFloat(t)}):Array.isArray(t)?t:"object"==typeof t?[t.x,t.y,t.width,t.height]:4==arguments.length?[].slice.call(arguments):h,this.x=t[0],this.y=t[1],this.width=t[2],this.height=t[3]},extend:{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height},morph:function(t,e,i,n){return this.destination=new w.ViewBox(t,e,i,n),this},at:function(t){return this.destination?new w.ViewBox([this.x+(this.destination.x-this.x)*t,this.y+(this.destination.y-this.y)*t,this.width+(this.destination.width-this.width)*t,this.height+(this.destination.height-this.height)*t]):this}},parent:w.Container,construct:{viewbox:function(t,e,i,n){return 0==arguments.length?new w.ViewBox(this):this.attr("viewBox",new w.ViewBox(t,e,i,n))}}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){w.Element.prototype[t]=function(e){return w.on(this.node,t,e),this}}),w.listeners=[],w.handlerMap=[],w.listenerId=0,w.on=function(t,e,i,n,r){var s=i.bind(n||t.instance||t),o=(w.handlerMap.indexOf(t)+1||w.handlerMap.push(t))-1,a=e.split(".")[0],h=e.split(".")[1]||"*";w.listeners[o]=w.listeners[o]||{},w.listeners[o][a]=w.listeners[o][a]||{},w.listeners[o][a][h]=w.listeners[o][a][h]||{},i._svgjsListenerId||(i._svgjsListenerId=++w.listenerId),w.listeners[o][a][h][i._svgjsListenerId]=s,t.addEventListener(a,s,r||!1)},w.off=function(t,e,i){var n=w.handlerMap.indexOf(t),r=e&&e.split(".")[0],s=e&&e.split(".")[1],o="";if(-1!=n)if(i){if("function"==typeof i&&(i=i._svgjsListenerId),!i)return;w.listeners[n][r]&&w.listeners[n][r][s||"*"]&&(t.removeEventListener(r,w.listeners[n][r][s||"*"][i],!1),delete w.listeners[n][r][s||"*"][i])}else if(s&&r){if(w.listeners[n][r]&&w.listeners[n][r][s]){for(i in w.listeners[n][r][s])w.off(t,[r,s].join("."),i);delete w.listeners[n][r][s]}}else if(s)for(e in w.listeners[n])for(o in w.listeners[n][e])s===o&&w.off(t,[e,s].join("."));else if(r){if(w.listeners[n][r]){for(o in w.listeners[n][r])w.off(t,[r,o].join("."));delete w.listeners[n][r]}}else{for(e in w.listeners[n])w.off(t,e);delete w.listeners[n],delete w.handlerMap[n]}},w.extend(w.Element,{on:function(t,e,i,n){return w.on(this.node,t,e,i,n),this},off:function(t,e){return w.off(this.node,t,e),this},fire:function(e,i){return e instanceof t.Event?this.node.dispatchEvent(e):this.node.dispatchEvent(e=new w.CustomEvent(e,{detail:i,cancelable:!0})),this._event=e,this},event:function(){return this._event}}),w.Defs=w.invent({create:"defs",inherit:w.Container}),w.G=w.invent({create:"g",inherit:w.Container,extend:{x:function(t){return null==t?this.transform("x"):this.transform({x:t-this.x()},!0)},y:function(t){return null==t?this.transform("y"):this.transform({y:t-this.y()},!0)},cx:function(t){return null==t?this.gbox().cx:this.x(t-this.gbox().width/2)},cy:function(t){return null==t?this.gbox().cy:this.y(t-this.gbox().height/2)},gbox:function(){var t=this.bbox(),e=this.transform();return t.x+=e.x,t.x2+=e.x,t.cx+=e.x,t.y+=e.y,t.y2+=e.y,t.cy+=e.y,t}},construct:{group:function(){return this.put(new w.G)}}}),w.Doc=w.invent({create:function(t){t&&(t="string"==typeof t?e.getElementById(t):t,"svg"==t.nodeName?this.constructor.call(this,t):(this.constructor.call(this,w.create("svg")),t.appendChild(this.node),this.size("100%","100%")),this.namespace().defs())},inherit:w.Container,extend:{namespace:function(){return this.attr({xmlns:w.ns,version:"1.1"}).attr("xmlns:xlink",w.xlink,w.xmlns).attr("xmlns:svgjs",w.svgjs,w.xmlns)},defs:function(){if(!this._defs){var t;(t=this.node.getElementsByTagName("defs")[0])?this._defs=w.adopt(t):this._defs=new w.Defs,this.node.appendChild(this._defs.node)}return this._defs},parent:function(){return this.node.parentNode&&"#document"!=this.node.parentNode.nodeName?this.node.parentNode:null},spof:function(){var t=this.node.getScreenCTM();return t&&this.style("left",-t.e%1+"px").style("top",-t.f%1+"px"),this},remove:function(){return this.parent()&&this.parent().removeChild(this.node),this},clear:function(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return delete this._defs,w.parser.draw.parentNode||this.node.appendChild(w.parser.draw),this},clone:function(t){this.writeDataToDom();var e=this.node,i=x(e.cloneNode(!0));return t?(t.node||t).appendChild(i.node):e.parentNode.insertBefore(i.node,e.nextSibling),i}}}),w.extend(w.Element,{siblings:function(){return this.parent().children()},position:function(){return this.parent().index(this)},next:function(){return this.siblings()[this.position()+1]},previous:function(){return this.siblings()[this.position()-1]},forward:function(){var t=this.position()+1,e=this.parent();return e.removeElement(this).add(this,t),e instanceof w.Doc&&e.node.appendChild(e.defs().node),this},backward:function(){var t=this.position();return t>0&&this.parent().removeElement(this).add(this,t-1),this},front:function(){var t=this.parent();return t.node.appendChild(this.node),t instanceof w.Doc&&t.node.appendChild(t.defs().node),this},back:function(){return this.position()>0&&this.parent().removeElement(this).add(this,0),this},before:function(t){t.remove();var e=this.position();return this.parent().add(t,e),this},after:function(t){t.remove();var e=this.position();return this.parent().add(t,e+1),this}}),w.Mask=w.invent({create:function(){this.constructor.call(this,w.create("mask")),this.targets=[]},inherit:w.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unmask();return this.targets=[],w.Element.prototype.remove.call(this),this}},construct:{mask:function(){return this.defs().put(new w.Mask)}}}),w.extend(w.Element,{maskWith:function(t){return this.masker=t instanceof w.Mask?t:this.parent().mask().add(t),this.masker.targets.push(this),this.attr("mask",'url("#'+this.masker.attr("id")+'")')},unmask:function(){return delete this.masker,this.attr("mask",null)}}),w.ClipPath=w.invent({create:function(){this.constructor.call(this,w.create("clipPath")),this.targets=[]},inherit:w.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unclip();return this.targets=[],this.parent().removeElement(this),this}},construct:{clip:function(){return this.defs().put(new w.ClipPath)}}}),w.extend(w.Element,{clipWith:function(t){return this.clipper=t instanceof w.ClipPath?t:this.parent().clip().add(t),this.clipper.targets.push(this),this.attr("clip-path",'url("#'+this.clipper.attr("id")+'")')},unclip:function(){return delete this.clipper,this.attr("clip-path",null)}}),w.Gradient=w.invent({create:function(t){this.constructor.call(this,w.create(t+"Gradient")),this.type=t},inherit:w.Container,extend:{at:function(t,e,i){return this.put(new w.Stop).update(t,e,i)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},fill:function(){return"url(#"+this.id()+")"},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="gradientTransform"),w.Container.prototype.attr.call(this,t,e,i)}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),w.extend(w.Gradient,w.FX,{from:function(t,e){return"radial"==(this._target||this).type?this.attr({fx:new w.Number(t),fy:new w.Number(e)}):this.attr({x1:new w.Number(t),y1:new w.Number(e)})},to:function(t,e){return"radial"==(this._target||this).type?this.attr({cx:new w.Number(t),cy:new w.Number(e)}):this.attr({x2:new w.Number(t),y2:new w.Number(e)})}}),w.extend(w.Defs,{gradient:function(t,e){return this.put(new w.Gradient(t)).update(e)}}),w.Stop=w.invent({create:"stop",inherit:w.Element,extend:{update:function(t){return("number"==typeof t||t instanceof w.Number)&&(t={offset:arguments[0],color:arguments[1],opacity:arguments[2]}),null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new w.Number(t.offset)),this}}}),w.Pattern=w.invent({create:"pattern",inherit:w.Container,extend:{fill:function(){return"url(#"+this.id()+")"},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="patternTransform"),w.Container.prototype.attr.call(this,t,e,i)}},construct:{pattern:function(t,e,i){return this.defs().pattern(t,e,i)}}}),w.extend(w.Defs,{pattern:function(t,e,i){return this.put(new w.Pattern).update(i).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),w.Shape=w.invent({create:function(t){this.constructor.call(this,t)},inherit:w.Element}),w.Bare=w.invent({create:function(t,e){if(this.constructor.call(this,w.create(t)),e)for(var i in e.prototype)"function"==typeof e.prototype[i]&&(this[i]=e.prototype[i])},inherit:w.Element,extend:{words:function(t){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this.node.appendChild(e.createTextNode(t)),this}}}),w.extend(w.Parent,{element:function(t,e){return this.put(new w.Bare(t,e))}}),w.Symbol=w.invent({create:"symbol",inherit:w.Container,construct:{symbol:function(){return this.put(new w.Symbol)}}}),w.Use=w.invent({create:"use",inherit:w.Shape,extend:{element:function(t,e){return this.attr("href",(e||"")+"#"+t,w.xlink)}},construct:{use:function(t,e){return this.put(new w.Use).element(t,e)}}}),w.Rect=w.invent({create:"rect",inherit:w.Shape,construct:{rect:function(t,e){return this.put(new w.Rect).size(t,e)}}}),w.Circle=w.invent({create:"circle",inherit:w.Shape,construct:{circle:function(t){return this.put(new w.Circle).rx(new w.Number(t).divide(2)).move(0,0)}}}),w.extend(w.Circle,w.FX,{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),w.Ellipse=w.invent({create:"ellipse",inherit:w.Shape,construct:{ellipse:function(t,e){return this.put(new w.Ellipse).size(t,e).move(0,0)}}}),w.extend(w.Ellipse,w.Rect,w.FX,{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),w.extend(w.Circle,w.Ellipse,{x:function(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())},y:function(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())},cx:function(t){return null==t?this.attr("cx"):this.attr("cx",t)},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",t)},width:function(t){return null==t?2*this.rx():this.rx(new w.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new w.Number(t).divide(2))},size:function(t,e){var i=l(this,t,e);return this.rx(new w.Number(i.width).divide(2)).ry(new w.Number(i.height).divide(2))}}),w.Line=w.invent({create:"line",inherit:w.Shape,extend:{array:function(){return new w.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,i,n){return null==t?this.array():(t=void 0!==e?{x1:t,y1:e,x2:i,y2:n}:new w.PointArray(t).toLine(),this.attr(t))},move:function(t,e){return this.attr(this.array().move(t,e).toLine())},size:function(t,e){var i=l(this,t,e);return this.attr(this.array().size(i.width,i.height).toLine())}},construct:{line:function(t,e,i,n){return w.Line.prototype.plot.apply(this.put(new w.Line),null!=t?[t,e,i,n]:[0,0,0,0])}}}),w.Polyline=w.invent({create:"polyline",inherit:w.Shape,construct:{polyline:function(t){return this.put(new w.Polyline).plot(t||new w.PointArray)}}}),w.Polygon=w.invent({create:"polygon",inherit:w.Shape,construct:{polygon:function(t){return this.put(new w.Polygon).plot(t||new w.PointArray)}}}),w.extend(w.Polyline,w.Polygon,{array:function(){return this._array||(this._array=new w.PointArray(this.attr("points")))},plot:function(t){return null==t?this.array():this.clear().attr("points","string"==typeof t?t:this._array=new w.PointArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("points",this.array().move(t,e))},size:function(t,e){var i=l(this,t,e);return this.attr("points",this.array().size(i.width,i.height))}}),w.extend(w.Line,w.Polyline,w.Polygon,{morphArray:w.PointArray,x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},width:function(t){var e=this.bbox();return null==t?e.width:this.size(t,e.height)},height:function(t){var e=this.bbox();return null==t?e.height:this.size(e.width,t)}}),w.Path=w.invent({create:"path",inherit:w.Shape,extend:{morphArray:w.PathArray,array:function(){return this._array||(this._array=new w.PathArray(this.attr("d")))},plot:function(t){return null==t?this.array():this.clear().attr("d","string"==typeof t?t:this._array=new w.PathArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("d",this.array().move(t,e))},x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},size:function(t,e){var i=l(this,t,e);return this.attr("d",this.array().size(i.width,i.height))},width:function(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)},height:function(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}},construct:{path:function(t){return this.put(new w.Path).plot(t||new w.PathArray)}}}),w.Image=w.invent({create:"image",inherit:w.Shape,extend:{load:function(e){if(!e)return this;var i=this,n=new t.Image;return w.on(n,"load",function(){w.off(n);var t=i.parent(w.Pattern);null!==t&&(0==i.width()&&0==i.height()&&i.size(n.width,n.height),t&&0==t.width()&&0==t.height()&&t.size(i.width(),i.height()),"function"==typeof i._loaded&&i._loaded.call(i,{width:n.width,height:n.height,ratio:n.width/n.height,url:e}))}),w.on(n,"error",function(t){w.off(n),"function"==typeof i._error&&i._error.call(i,t)}),this.attr("href",n.src=this.src=e,w.xlink)},loaded:function(t){return this._loaded=t,this},error:function(t){return this._error=t,this}},construct:{image:function(t,e,i){return this.put(new w.Image).load(t).size(e||0,i||e||0)}}}),w.Text=w.invent({create:function(){this.constructor.call(this,w.create("text")),this.dom.leading=new w.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",w.defaults.attrs["font-family"])},inherit:w.Shape,extend:{x:function(t){return null==t?this.attr("x"):this.attr("x",t)},y:function(t){var e=this.attr("y"),i="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-i:e:this.attr("y","number"==typeof t.valueOf()?t+i:t)},cx:function(t){return null==t?this.bbox().cx:this.x(t-this.bbox().width/2)},cy:function(t){return null==t?this.bbox().cy:this.y(t-this.bbox().height/2)},text:function(t){if(void 0===t){for(var t="",e=this.node.childNodes,i=0,n=e.length;i=0;e--)null!=i[M[t][e]]&&this.attr(M.prefix(t,M[t][e]),i[M[t][e]]);return this},w.extend(w.Element,w.FX,i)}),w.extend(w.Element,w.FX,{rotate:function(t,e,i){return this.transform({rotation:t,cx:e,cy:i})},skew:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({skew:t,cx:e,cy:i}):this.transform({skewX:t,skewY:e,cx:i,cy:n})},scale:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({scale:t,cx:e,cy:i}):this.transform({scaleX:t,scaleY:e,cx:i,cy:n})},translate:function(t,e){return this.transform({x:t,y:e})},flip:function(t,e){return e="number"==typeof t?t:e,this.transform({flip:t||"both",offset:e})},matrix:function(t){return this.attr("transform",new w.Matrix(6==arguments.length?[].slice.call(arguments):t))},opacity:function(t){return this.attr("opacity",t)},dx:function(t){return this.x(new w.Number(t).plus(this instanceof w.FX?0:this.x()),!0)},dy:function(t){return this.y(new w.Number(t).plus(this instanceof w.FX?0:this.y()),!0)},dmove:function(t,e){return this.dx(t).dy(e)}}),w.extend(w.Rect,w.Ellipse,w.Circle,w.Gradient,w.FX,{radius:function(t,e){var i=(this._target||this).type;return"radial"==i||"circle"==i?this.attr("r",new w.Number(t)):this.rx(t).ry(null==e?t:e)}}),w.extend(w.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),w.extend(w.Parent,w.Text,w.Tspan,w.FX,{font:function(t,e){if("object"==typeof t)for(e in t)this.font(e,t[e]);return"leading"==t?this.leading(e):"anchor"==t?this.attr("text-anchor",e):"size"==t||"family"==t||"weight"==t||"stretch"==t||"variant"==t||"style"==t?this.attr("font-"+t,e):this.attr(t,e)}}),w.Set=w.invent({create:function(t){Array.isArray(t)?this.members=t:this.clear()},extend:{add:function(){var t,e,i=[].slice.call(arguments);for(t=0,e=i.length;t-1&&this.members.splice(e,1),this},each:function(t){for(var e=0,i=this.members.length;e=0},index:function(t){return this.members.indexOf(t)},get:function(t){return this.members[t]},first:function(){return this.get(0)},last:function(){return this.get(this.members.length-1)},valueOf:function(){return this.members},bbox:function(){if(0==this.members.length)return new w.RBox;var t=this.members[0].rbox(this.members[0].doc());return this.each(function(){t=t.merge(this.rbox(this.doc()))}),t}},construct:{set:function(t){return new w.Set(t)}}}),w.FX.Set=w.invent({create:function(t){this.set=t}}),w.Set.inherit=function(){ +var t,e=[];for(var t in w.Shape.prototype)"function"==typeof w.Shape.prototype[t]&&"function"!=typeof w.Set.prototype[t]&&e.push(t);e.forEach(function(t){w.Set.prototype[t]=function(){for(var e=0,i=this.members.length;e=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),w.get=function(t){var i=e.getElementById(v(t)||t);return w.adopt(i)},w.select=function(t,i){return new w.Set(w.utils.map((i||e).querySelectorAll(t),function(t){return w.adopt(t)}))},w.extend(w.Parent,{select:function(t){return w.select(t,this.node)}});var P="abcdef".split("");if("function"!=typeof t.CustomEvent){var k=function(t,i){i=i||{bubbles:!1,cancelable:!1,detail:void 0};var n=e.createEvent("CustomEvent");return n.initCustomEvent(t,i.bubbles,i.cancelable,i.detail),n};k.prototype=t.Event.prototype,w.CustomEvent=k}else w.CustomEvent=t.CustomEvent;return function(e){for(var i=0,n=["moz","webkit"],r=0;r. +# + +import os, subprocess, shlex +from inginious import feedback +from inginious import input + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("insertion_sort.py") + + p = subprocess.Popen(shlex.split("python3 insertion_sort.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("Quelque chose s'est mal passée. Veuillez vérifier que l'agencement de vos blocs est correct.") + exit(0) + if make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Félicitations! La liste est bien triée.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback("Mauvaise réponse. " + make_output) diff --git a/Tri_PDS/6_insertion_sort1/student/insertion_sort.py b/Tri_PDS/6_insertion_sort1/student/insertion_sort.py new file mode 100644 index 0000000..09da623 --- /dev/null +++ b/Tri_PDS/6_insertion_sort1/student/insertion_sort.py @@ -0,0 +1,33 @@ +#!/bin/python3 + +# +# Copyright (c) 2018 Ilias Boutchichi +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +A = [] +B = [] + +def student_code(): + @@algo@@ + +if __name__ == "__main__": + try: + student_code() + except: + print("Unexpected error:", sys.exc_info()[0]) + correct_list = sorted(B) + if correct_list == A: + print('True', end='', flush=True) + else: + print('Vous obtenez la liste suivante: ' + str(A) + ' au lieu de: ' + str(correct_list)) diff --git a/Tri_PDS/6_insertion_sort1/task.yaml b/Tri_PDS/6_insertion_sort1/task.yaml new file mode 100644 index 0000000..31bfd52 --- /dev/null +++ b/Tri_PDS/6_insertion_sort1/task.yaml @@ -0,0 +1,99 @@ +accessible: true +author: Ilias Boutchichi +context: Ici, tu peux voir le pire cas du tri par insertion. Combien fait-il de comparaisons + ? Est-ce similaire au tri par sélection ? +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + time: '10' + output: '2' +name: 7.5 Tri par insertion - pire cas +network_grading: false +problems: + algo: + options: + grid: + colour: '#ccc' + length: 3 + spacing: 20 + snap: true + zoom: + minScale: 0.3 + scaleSpeed: 1.2 + controls: true + maxScale: 3.0 + startScale: 1.0 + wheel: false + maxBlocks: '8' + visual: + position: left + css: true + toolboxPosition: start + media: plugins/blockly/static/blockly/media/ + scrollbars: true + name: '' + header: '' + workspace: |- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + type: blockly + blocks_files: + - customblocks.js + toolbox: |- + + + + + + + + + + + + + + + + + files: + - svg_min.js + - insertion_sort.js +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/Tri_PDS/7_quick_sort/public/customblocks.js b/Tri_PDS/7_quick_sort/public/customblocks.js new file mode 100644 index 0000000..f1d47e3 --- /dev/null +++ b/Tri_PDS/7_quick_sort/public/customblocks.js @@ -0,0 +1,312 @@ +/* +# Copyright (c) 2018 Ilias Boutchichi +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +*/ + +/** +* @author Ilias Boutchichi +*/ + +'use strict'; + +var newlist = function() { + var liste = []; + for(var i = 0; i<10; i++){ + liste.push(Math.floor((Math.random() * 100) + 1)); + } + return liste; +}; + +var Lst = {}; +Lst.liste = newlist(); + +Blockly.Blocks['custom_swap'] = { + init: function() { + this.jsonInit({ + "type": "custom_swap", + "message0": "echanger les cases %1 %2 et %3 %4", + "args0": [ + { + "type": "input_dummy" + }, + { + "type": "input_value", + "name": "pos1", + "check": "Number" + }, + { + "type": "input_dummy" + }, + { + "type": "input_value", + "name": "pos2", + "check": "Number" + } + ], + "inputsInline": true, + "previousStatement": null, + "nextStatement": null, + "colour": 330, + "tooltip": "Echanger deux cases du tableau", + "helpUrl": "" + }); + } +}; + +Blockly.Python['custom_swap'] = function(block) { + var value_pos1 = Blockly.Python.valueToCode(block, 'pos1', Blockly.Python.ORDER_ATOMIC); + var value_pos2 = Blockly.Python.valueToCode(block, 'pos2', Blockly.Python.ORDER_ATOMIC); + var code = 'temp=liste[int('+value_pos1+ '-1)] \nliste[int('+value_pos1+'-1)]=liste[int('+value_pos2+'-1)] \nliste[int('+value_pos2+'-1)] = temp \n'; + return code; +}; + +Blockly.JavaScript['custom_swap'] = function(block) { + var value_pos1 = Blockly.JavaScript.valueToCode(block, 'pos1', Blockly.JavaScript.ORDER_ATOMIC); + var value_pos2 = Blockly.JavaScript.valueToCode(block, 'pos2', Blockly.JavaScript.ORDER_ATOMIC); + var code = 'temp=liste['+value_pos1+ '] \nliste['+value_pos1+']=liste['+value_pos2+'] \nliste['+value_pos2+'] = temp \n swap('+value_pos1+', '+value_pos2+', premier, dernier, pivot);'; + return code; +}; + +Blockly.Blocks['create_pivot'] = { + init: function() { + this.appendValueInput("FIRST") + .setCheck("Number") + .appendField("choisir un pivot entre"); + this.appendValueInput("LAST") + .setCheck("Number") + .appendField("et"); + this.setInputsInline(true); + this.setOutput(true, "Number"); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['create_pivot'] = function(block) { + var value_first = Blockly.JavaScript.valueToCode(block, 'FIRST', Blockly.JavaScript.ORDER_ATOMIC); + var value_last = Blockly.JavaScript.valueToCode(block, 'LAST', Blockly.JavaScript.ORDER_ATOMIC); + var code = 'Math.round(Math.random() * ('+value_last+' - '+value_first+') + '+value_first+'); \n makePivot(pivot)'; + return [code, Blockly.JavaScript.ORDER_ATOMIC]; +}; + +Blockly.Python['create_pivot'] = function(block) { + var value_min = Blockly.Python.valueToCode(block, 'FIRST', Blockly.Python.ORDER_ATOMIC); + var value_max = Blockly.Python.valueToCode(block, 'LAST', Blockly.Python.ORDER_ATOMIC); + // from random import randint + var code = 'randint('+value_min+', '+value_max+')\n'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['b_lighter_pivot'] = { + init: function() { + this.appendDummyInput() + .appendField("l'élément est plus petit que le pivot"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['b_lighter_pivot'] = function(block) { + var code = 'liste[i] <= liste[dernier]'; + return [code, Blockly.JavaScript.ORDER_RELATIONAL]; +}; + +Blockly.Python['b_lighter_pivot'] = function(block) { + var code = 'liste[int(i)] < liste[int(dernier)]'; + return [code, Blockly.Python.ORDER_RELATIONAL]; +}; + +Blockly.Blocks['custom_for_elem'] = { + init: function() { + this.appendDummyInput() + .appendField("TANT QUE (il reste des bouteilles à comparer)"); + this.appendStatementInput("STAT") + .setCheck(null); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(105); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_for_elem'] = function(block) { + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'for(var i = 1; i < liste.length; i++){\n'+statements_stat+'}\n'; + return code; +}; + +Blockly.Python['custom_for_elem'] = function(block) { + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'for i in range(len(liste)):\n'+statements_stat; + return code; +}; + +Blockly.Blocks['custom_for_all'] = { + init: function() { + this.appendDummyInput() + .appendField("TANT QUE (il reste des bouteilles à trier)"); + this.appendStatementInput("STAT") + .setCheck(null); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(105); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['custom_for_all'] = function(block) { + var statements_stat = Blockly.JavaScript.statementToCode(block, 'STAT'); + var code = 'for(var j = 0; j < n; j++){\n'+statements_stat+'}\n'; + return code; +}; + +Blockly.Python['custom_for_all'] = function(block) { + var statements_stat = Blockly.Python.statementToCode(block, 'STAT'); + var code = 'for j in range(len(liste)):\n'+statements_stat; + return code; +}; + +Blockly.Blocks['create_plus_leger'] = { + init: function() { + this.appendDummyInput() + .appendField("Choisir une bouteille A"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['create_plus_leger'] = function(block) { + var code = 'var current = 0;\n currentComp(0);\n'; + return code; +}; + +Blockly.Python['create_plus_leger'] = function(block) { + var code = 'leger = 0\n'; + return code; +}; + +Blockly.Blocks['get_other'] = { + init: function() { + this.appendDummyInput() + .appendField("Prendre une bouteille B parmi les autres"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['get_other'] = function(block) { + var code = 'var other = i; otherComp(i)\n'; + return code; +}; + +Blockly.Python['get_other'] = function(block) { + var code = 'other = i\n'; + return code; +}; + +Blockly.Blocks['b_lighter_a'] = { + init: function() { + this.appendDummyInput() + .appendField("la bouteille B est plus légère que la A"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['b_lighter_a'] = function(block) { + var code = 'liste[other] < liste[current]'; + return [code, Blockly.JavaScript.ORDER_RELATIONAL]; +}; + +Blockly.Python['b_lighter_a'] = function(block) { + var code = 'liste[other] < liste[leger]'; + return [code, Blockly.Python.ORDER_RELATIONAL]; +}; + +Blockly.Blocks['b_is_a'] = { + init: function() { + this.appendDummyInput() + .appendField("la bouteille B devient la bouteille A"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['b_is_a'] = function(block) { + var code = 'current = i; currentComp(i);\n'; + return code; +}; + +Blockly.Python['b_is_a'] = function(block) { + var code = 'leger = i\n'; + return code; +}; + +Blockly.Blocks['store_a'] = { + init: function() { + this.appendDummyInput() + .appendField("placer la bouteille A dans la file déjà triée"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(230); + this.setTooltip(""); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['store_a'] = function(block) { + var code = 'move(current);\n liste.splice(current, 1);\n'; + return code; +}; + +Blockly.Python['store_a'] = function(block) { + var code = 'B.append(liste[leger]) \nliste.remove(liste[leger])'; + return code; +}; + +Blockly.Blocks['new_list'] = { + init: function() { + this.appendDummyInput() + .appendField("créer une nouvelle liste"); + this.setNextStatement(true, null); + this.setColour(290); + this.setTooltip("A = []"); + this.setHelpUrl(""); + } +}; + +Blockly.Python['new_list'] = function(block) { + var code = 'global liste, B\nliste = [' + Lst.liste + ']\nB = []\n'; + return code; +}; + +Blockly.JavaScript['new_list'] = function(block) { + var code = 'liste = [' + Lst.liste + '];\nvar n = liste.length;\n'; + return code; +}; \ No newline at end of file diff --git a/Tri_PDS/7_quick_sort/public/example.gif b/Tri_PDS/7_quick_sort/public/example.gif new file mode 100644 index 0000000..96c1b12 Binary files /dev/null and b/Tri_PDS/7_quick_sort/public/example.gif differ diff --git a/Tri_PDS/7_quick_sort/public/insertion_sort.js b/Tri_PDS/7_quick_sort/public/insertion_sort.js new file mode 100644 index 0000000..1040c7a --- /dev/null +++ b/Tri_PDS/7_quick_sort/public/insertion_sort.js @@ -0,0 +1,198 @@ +/* +* Copyright (c) 2018 Ilias Boutchichi +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see . +*/ + +/** +* @author Ilias Boutchichi, Celine Deknop +*/ + +'use strict'; //more secure + +var TEXT = 0, RECT = 1, INDEX = 2, VALUE = 3, RECT_HEIGHT = 60; + +var Insertion = {}; + +var currentBlue = -1; +var currentRed = -1; +var pivot = -1; +var currentlyClassed = 0; + +Insertion.animations = []; +Insertion.initList = []; +Insertion.case = []; +Insertion.animid = 0; +Insertion.tmp = null; +window.stepSpeed = 500; + +/** + * Interpreter for custom functions in blocks generators + */ +var initInterpreterApi = function(interpreter, scope) { + interpreter.setProperty(scope, 'makePivot', + interpreter.createNativeFunction(function(index) { + if(pivot != -1){ + Insertion.tmp = Insertion.case[pivot]; + Insertion.animations.push(Insertion.changeBackgroundColor('grey', Insertion.tmp[RECT])); + } + var tmp = Insertion.case[Number(index)]; + Insertion.animations.push(Insertion.changeBackgroundColor('yellow', tmp[RECT])); + pivot = Number(index); + })); + + interpreter.setProperty(scope, 'swap', + interpreter.createNativeFunction(function(index1, index2, p, d, pi, k) { + if(Number(index1) != Number(index2)){ + var tmp1 = Insertion.case[Number(index1)]; + var tmp2 = Insertion.case[Number(index2)]; + Insertion.animations.push(Insertion.dmove(250, 0, tmp1)); + Insertion.animations.push(Insertion.dmove(250, 0, tmp2)); + if(index1 < index2){ + Insertion.animations.push(Insertion.dmove(0, -RECT_HEIGHT*(index2-index1), tmp1)); + Insertion.animations.push(Insertion.dmove(0, RECT_HEIGHT*(index2-index1), tmp2)); + } + else{ + Insertion.animations.push(Insertion.dmove(0, RECT_HEIGHT*(index2-index1), tmp1)); + Insertion.animations.push(Insertion.dmove(0, -RECT_HEIGHT*(index2-index1), tmp2)); + } + if(Number(index1) == pivot){ + pivot = Number(index2); + }else if (Number(index2) == pivot){ + pivot = Number(index1); + } + Insertion.case[Number(index1)] = tmp2; + Insertion.case[Number(index2)] = tmp1; + Insertion.animations.push(Insertion.dmove(-250, 0, tmp1)); + Insertion.animations.push(Insertion.dmove(-250, 0, tmp2)); + } + })); + + Insertion.reset(); +}; + +/** + * Function called after the evaluation of each block for animations + */ +var animate = function() { + Insertion.animate(); +}; + +/** + * Initialisation of Blockly, svg, and display of the random list + */ +Insertion.init = function() { + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" + || Blockly.getMainWorkspace() === null) { + console.warn("Insertion.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Insertion.init, 20); + return; + } + var svg = document.getElementById('blocklySvgZone'); + svg.setAttribute('style', ''); + svg.setAttribute('viewBox', '0, 0, 400, 800'); + svg.setAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml'); + + Insertion.list = Lst.liste; + + var svg = SVG('blocklySvgZone').size('100%', '100%'); + Insertion.list.forEach(function(item, index, array) { + var text = svg.text('' + item).font({ fill: 'black', family: 'Inconsolata', size:40}) + .move(5, index*RECT_HEIGHT); + var rect = svg.rect(100,RECT_HEIGHT).fill('none').stroke({width:4,color:'black'}) + .move(5,RECT_HEIGHT*index); + Insertion.case.push([text,rect,index,item]); + }); + Insertion.initList = Insertion.case.slice(); +}; + +/** + * Reset function called when clicking on the 'restart' button + */ +Insertion.reset = function() { + for(var x=0; x < Insertion.animations.length; x++){ + clearTimeout(Insertion.animations[x]); + } + Insertion.animations = []; + pivot = -1; + for(var i = 0; i < Insertion.case.length; i++){ + Insertion.case[i][RECT].fill("none"); + Insertion.case[i][RECT].opacity(1); + } + Insertion.case = Insertion.initList.slice(); + + Insertion.case.forEach(function(item, index, array) { + item[TEXT].stroke('black').move(5,index*RECT_HEIGHT); + item[RECT].stroke('black').move(5,index*RECT_HEIGHT); + }); +}; + +var Maze = {}; +Maze.reset = Insertion.reset; //Trick 'cause plugin + +/** + * Function that changes the color of a rectangle + * @param {string} color the color value, either common value + * or hexadecimal value + * @param {rectangle} rect the rectangle that needs to change color + */ +Insertion.changeColor = function(color, rect) { + return function() { + rect.animate(window.stepSpeed, '<>').stroke(color); + }; +}; + +Insertion.changeBackgroundColor = function(color, rect) { + return function() { + if(color == 'none'){ + rect.fill(color); + rect.animate(window.stepSpeed, '<>').opacity(1); + }else{ + rect.animate(window.stepSpeed, '<>').fill(color).opacity(0.4); + } + }; +}; + +/** + * Function that move a case (text + rectangle) by adding (x,y) to + * its coordinates + * @param {number} x value to add to the abscissa of the case 'elem' + * @param {number} y value to add to the ordinate of the case 'elem' + * @param {case} elem element representing the case that needs to move + */ +Insertion.dmove = function(x,y,elem) { + return function() { + elem[TEXT].animate(window.stepSpeed, '<>').dmove(x,y); + elem[RECT].animate(window.stepSpeed, '<>').dmove(x,y); + }; +}; + +/** + * My animation function + */ +Insertion.animate = function() { + while(Insertion.animations.length) { + window.setTimeout(Insertion.animations.shift(), + Insertion.animid++*window.stepSpeed); + } + Insertion.animid = 0; +}; + +/** + * Throws a warning if no visual interface is found on the web page, + * otherwise load the visuals + */ +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Insertion.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/Tri_PDS/7_quick_sort/public/svg_min.js b/Tri_PDS/7_quick_sort/public/svg_min.js new file mode 100644 index 0000000..b451608 --- /dev/null +++ b/Tri_PDS/7_quick_sort/public/svg_min.js @@ -0,0 +1,3 @@ +/*! svg.js v2.6.5 MIT*/;!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t,t.document)}):"object"==typeof exports?module.exports=t.document?e(t,t.document):function(t){return e(t,t.document)}:t.SVG=e(t,t.document)}("undefined"!=typeof window?window:this,function(t,e){function i(t,e,i,n){return i+n.replace(w.regex.dots," .")}function n(t){for(var e=t.slice(0),i=e.length;i--;)Array.isArray(e[i])&&(e[i]=n(e[i]));return e}function r(t,e){return t instanceof e}function s(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}function o(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function a(t){return t.charAt(0).toUpperCase()+t.slice(1)}function h(t){return 4==t.length?["#",t.substring(1,2),t.substring(1,2),t.substring(2,3),t.substring(2,3),t.substring(3,4),t.substring(3,4)].join(""):t}function u(t){var e=t.toString(16);return 1==e.length?"0"+e:e}function l(t,e,i){if(null==e||null==i){var n=t.bbox();null==e?e=n.width/n.height*i:null==i&&(i=n.height/n.width*e)}return{width:e,height:i}}function c(t,e,i){return{x:e*t.a+i*t.c+0,y:e*t.b+i*t.d+0}}function f(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function d(t){return t instanceof w.Matrix||(t=new w.Matrix(t)),t}function p(t,e){t.cx=null==t.cx?e.bbox().cx:t.cx,t.cy=null==t.cy?e.bbox().cy:t.cy}function m(t){for(var e=0,i=t.length,n="";e=0;i--)e.childNodes[i]instanceof t.SVGElement&&x(e.childNodes[i]);return w.adopt(e).id(w.eid(e.nodeName))}function y(t){return null==t.x&&(t.x=0,t.y=0,t.width=0,t.height=0),t.w=t.width,t.h=t.height,t.x2=t.x+t.width,t.y2=t.y+t.height,t.cx=t.x+t.width/2,t.cy=t.y+t.height/2,t}function v(t){var e=(t||"").toString().match(w.regex.reference);if(e)return e[1]}function g(t){return Math.abs(t)>1e-37?t:0}var w=this.SVG=function(t){if(w.supported)return t=new w.Doc(t),w.parser.draw||w.prepare(),t};if(w.ns="http://www.w3.org/2000/svg",w.xmlns="http://www.w3.org/2000/xmlns/",w.xlink="http://www.w3.org/1999/xlink",w.svgjs="http://svgjs.com/svgjs",w.supported=function(){return!!e.createElementNS&&!!e.createElementNS(w.ns,"svg").createSVGRect}(),!w.supported)return!1;w.did=1e3,w.eid=function(t){return"Svgjs"+a(t)+w.did++},w.create=function(t){var i=e.createElementNS(this.ns,t);return i.setAttribute("id",this.eid(t)),i},w.extend=function(){var t,e,i,n;for(t=[].slice.call(arguments),e=t.pop(),n=t.length-1;n>=0;n--)if(t[n])for(i in e)t[n].prototype[i]=e[i];w.Set&&w.Set.inherit&&w.Set.inherit()},w.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,w.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&w.extend(e,t.extend),t.construct&&w.extend(t.parent||w.Container,t.construct),e},w.adopt=function(e){if(!e)return null;if(e.instance)return e.instance;var i;return i="svg"==e.nodeName?e.parentNode instanceof t.SVGElement?new w.Nested:new w.Doc:"linearGradient"==e.nodeName?new w.Gradient("linear"):"radialGradient"==e.nodeName?new w.Gradient("radial"):w[a(e.nodeName)]?new(w[a(e.nodeName)]):new w.Element(e),i.type=e.nodeName,i.node=e,e.instance=i,i instanceof w.Doc&&i.namespace().defs(),i.setData(JSON.parse(e.getAttribute("svgjs:data"))||{}),i},w.prepare=function(){var t=e.getElementsByTagName("body")[0],i=(t?new w.Doc(t):w.adopt(e.documentElement).nested()).size(2,0);w.parser={body:t||e.documentElement,draw:i.style("opacity:0;position:absolute;left:-100%;top:-100%;overflow:hidden").node,poly:i.polyline().node,path:i.path().node,native:w.create("svg")}},w.parser={native:w.create("svg")},e.addEventListener("DOMContentLoaded",function(){w.parser.draw||w.prepare()},!1),w.regex={numberAndUnit:/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,transforms:/\)\s*,?\s*/,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,isPercent:/^-?[\d\.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,delimiter:/[\s,]+/,hyphen:/([^e])\-/gi,pathLetters:/[MLHVCSQTAZ]/gi,isPathLetter:/[MLHVCSQTAZ]/i,numbersWithDots:/((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi,dots:/\./g},w.utils={map:function(t,e){var i,n=t.length,r=[];for(i=0;i1?1:t,new w.Color({r:~~(this.r+(this.destination.r-this.r)*t),g:~~(this.g+(this.destination.g-this.g)*t),b:~~(this.b+(this.destination.b-this.b)*t)})):this}}),w.Color.test=function(t){return t+="",w.regex.isHex.test(t)||w.regex.isRgb.test(t)},w.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},w.Color.isColor=function(t){return w.Color.isRgb(t)||w.Color.test(t)},w.Array=function(t,e){t=(t||[]).valueOf(),0==t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},w.extend(w.Array,{morph:function(t){if(this.destination=this.parse(t),this.value.length!=this.destination.length){for(var e=this.value[this.value.length-1],i=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(i);for(;this.value.length=0;n--)this.value[n]=[this.value[n][0]+t,this.value[n][1]+e];return this},size:function(t,e){var i,n=this.bbox();for(i=this.value.length-1;i>=0;i--)n.width&&(this.value[i][0]=(this.value[i][0]-n.x)*t/n.width+n.x),n.height&&(this.value[i][1]=(this.value[i][1]-n.y)*e/n.height+n.y);return this},bbox:function(){return w.parser.poly.setAttribute("points",this.toString()),w.parser.poly.getBBox()}});for(var b={M:function(t,e,i){return e.x=i.x=t[0],e.y=i.y=t[1],["M",e.x,e.y]},L:function(t,e){return e.x=t[0],e.y=t[1],["L",t[0],t[1]]},H:function(t,e){return e.x=t[0],["H",t[0]]},V:function(t,e){return e.y=t[0],["V",t[0]]},C:function(t,e){return e.x=t[4],e.y=t[5],["C",t[0],t[1],t[2],t[3],t[4],t[5]]},S:function(t,e){return e.x=t[2],e.y=t[3],["S",t[0],t[1],t[2],t[3]]},Q:function(t,e){return e.x=t[2],e.y=t[3],["Q",t[0],t[1],t[2],t[3]]},T:function(t,e){return e.x=t[0],e.y=t[1],["T",t[0],t[1]]},Z:function(t,e,i){return e.x=i.x,e.y=i.y,["Z"]},A:function(t,e){return e.x=t[5],e.y=t[6],["A",t[0],t[1],t[2],t[3],t[4],t[5],t[6]]}},C="mlhvqtcsaz".split(""),N=0,A=C.length;N=0;r--)n=this.value[r][0],"M"==n||"L"==n||"T"==n?(this.value[r][1]+=t,this.value[r][2]+=e):"H"==n?this.value[r][1]+=t:"V"==n?this.value[r][1]+=e:"C"==n||"S"==n||"Q"==n?(this.value[r][1]+=t,this.value[r][2]+=e,this.value[r][3]+=t,this.value[r][4]+=e,"C"==n&&(this.value[r][5]+=t,this.value[r][6]+=e)):"A"==n&&(this.value[r][6]+=t,this.value[r][7]+=e);return this},size:function(t,e){var i,n,r=this.bbox();for(i=this.value.length-1;i>=0;i--)n=this.value[i][0],"M"==n||"L"==n||"T"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y):"H"==n?this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x:"V"==n?this.value[i][1]=(this.value[i][1]-r.y)*e/r.height+r.y:"C"==n||"S"==n||"Q"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y,this.value[i][3]=(this.value[i][3]-r.x)*t/r.width+r.x,this.value[i][4]=(this.value[i][4]-r.y)*e/r.height+r.y,"C"==n&&(this.value[i][5]=(this.value[i][5]-r.x)*t/r.width+r.x,this.value[i][6]=(this.value[i][6]-r.y)*e/r.height+r.y)):"A"==n&&(this.value[i][1]=this.value[i][1]*t/r.width,this.value[i][2]=this.value[i][2]*e/r.height,this.value[i][6]=(this.value[i][6]-r.x)*t/r.width+r.x,this.value[i][7]=(this.value[i][7]-r.y)*e/r.height+r.y);return this},equalCommands:function(t){var e,i,n;for(t=new w.PathArray(t),n=this.value.length===t.value.length,e=0,i=this.value.length;n&&ea);return n},bbox:function(){return w.parser.path.setAttribute("d",this.toString()),w.parser.path.getBBox()}}),w.Number=w.invent({create:function(t,e){this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:t<0?-3.4e38:3.4e38:"string"==typeof t?(e=t.match(w.regex.numberAndUnit))&&(this.value=parseFloat(e[1]),"%"==e[5]?this.value/=100:"s"==e[5]&&(this.value*=1e3),this.unit=e[5]):t instanceof w.Number&&(this.value=t.valueOf(),this.unit=t.unit)},extend:{toString:function(){return("%"==this.unit?~~(1e8*this.value)/1e6:"s"==this.unit?this.value/1e3:this.value)+this.unit},toJSON:function(){return this.toString()},valueOf:function(){return this.value},plus:function(t){return t=new w.Number(t),new w.Number(this+t,this.unit||t.unit)},minus:function(t){return t=new w.Number(t),new w.Number(this-t,this.unit||t.unit)},times:function(t){return t=new w.Number(t),new w.Number(this*t,this.unit||t.unit)},divide:function(t){return t=new w.Number(t),new w.Number(this/t,this.unit||t.unit)},to:function(t){var e=new w.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new w.Number(t),t.relative&&(this.destination.value+=this.value),this},at:function(t){return this.destination?new w.Number(this.destination).minus(this).times(t).plus(this):this}}}),w.Element=w.invent({create:function(t){this._stroke=w.defaults.attrs.stroke,this._event=null,this.dom={},(this.node=t)&&(this.type=t.nodeName,this.node.instance=this,this._stroke=t.getAttribute("stroke")||this._stroke)},extend:{x:function(t){return this.attr("x",t)},y:function(t){return this.attr("y",t)},cx:function(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)},cy:function(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},width:function(t){return this.attr("width",t)},height:function(t){return this.attr("height",t)},size:function(t,e){var i=l(this,t,e);return this.width(new w.Number(i.width)).height(new w.Number(i.height))},clone:function(t){this.writeDataToDom();var e=x(this.node.cloneNode(!0));return t?t.add(e):this.after(e),e},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(t){return this.after(t).remove(),t},addTo:function(t){return t.put(this)},putIn:function(t){return t.add(this)},id:function(t){return this.attr("id",t)},inside:function(t,e){var i=this.bbox();return t>i.x&&e>i.y&&t/,"").replace(/<\/svg>$/,"");i.innerHTML=""+t.replace(/\n/,"").replace(/<([\w:-]+)([^<]+?)\/>/g,"<$1$2>")+"";for(var n=0,r=i.firstChild.childNodes.length;n":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return 1-Math.cos(t*Math.PI/2)}},w.morph=function(t){return function(e,i){return new w.MorphObj(e,i).at(t)}},w.Situation=w.invent({create:function(t){this.init=!1,this.reversed=!1,this.reversing=!1,this.duration=new w.Number(t.duration).valueOf(),this.delay=new w.Number(t.delay).valueOf(),this.start=+new Date+this.delay,this.finish=this.start+this.duration,this.ease=t.ease,this.loop=0,this.loops=!1,this.animations={},this.attrs={},this.styles={},this.transforms=[],this.once={}}}),w.FX=w.invent({create:function(t){this._target=t,this.situations=[],this.active=!1,this.situation=null,this.paused=!1,this.lastPos=0,this.pos=0,this.absPos=0,this._speed=1},extend:{animate:function(t,e,i){"object"==typeof t&&(e=t.ease,i=t.delay,t=t.duration);var n=new w.Situation({duration:t||1e3,delay:i||0,ease:w.easing[e||"-"]||e});return this.queue(n),this},delay:function(t){var e=new w.Situation({duration:t,delay:0,ease:w.easing["-"]});return this.queue(e)},target:function(t){return t&&t instanceof w.Element?(this._target=t,this):this._target},timeToAbsPos:function(t){return(t-this.situation.start)/(this.situation.duration/this._speed)},absPosToTime:function(t){return this.situation.duration/this._speed*t+this.situation.start},startAnimFrame:function(){this.stopAnimFrame(),this.animationFrame=t.requestAnimationFrame(function(){this.step()}.bind(this))},stopAnimFrame:function(){t.cancelAnimationFrame(this.animationFrame)},start:function(){return!this.active&&this.situation&&(this.active=!0,this.startCurrent()),this},startCurrent:function(){return this.situation.start=+new Date+this.situation.delay/this._speed,this.situation.finish=this.situation.start+this.situation.duration/this._speed,this.initAnimations().step()},queue:function(t){return("function"==typeof t||t instanceof w.Situation)&&this.situations.push(t),this.situation||(this.situation=this.situations.shift()),this},dequeue:function(){return this.stop(),this.situation=this.situations.shift(),this.situation&&(this.situation instanceof w.Situation?this.start():this.situation.call(this)),this},initAnimations:function(){var t,e,i,n=this.situation;if(n.init)return this;for(t in n.animations)for(i=this.target()[t](),Array.isArray(i)||(i=[i]),Array.isArray(n.animations[t])||(n.animations[t]=[n.animations[t]]),e=i.length;e--;)n.animations[t][e]instanceof w.Number&&(i[e]=new w.Number(i[e])),n.animations[t][e]=i[e].morph(n.animations[t][e]);for(t in n.attrs)n.attrs[t]=new w.MorphObj(this.target().attr(t),n.attrs[t]);for(t in n.styles)n.styles[t]=new w.MorphObj(this.target().style(t),n.styles[t]);return n.initialTransformation=this.target().matrixify(),n.init=!0,this},clearQueue:function(){return this.situations=[],this},clearCurrent:function(){return this.situation=null,this},stop:function(t,e){var i=this.active;return this.active=!1,e&&this.clearQueue(),t&&this.situation&&(!i&&this.startCurrent(),this.atEnd()),this.stopAnimFrame(),this.clearCurrent()},reset:function(){if(this.situation){var t=this.situation;this.stop(),this.situation=t,this.atStart()}return this},finish:function(){for(this.stop(!0,!1);this.dequeue().situation&&this.stop(!0,!1););return this.clearQueue().clearCurrent(),this},atStart:function(){return this.at(0,!0)},atEnd:function(){return!0===this.situation.loops&&(this.situation.loops=this.situation.loop+1),"number"==typeof this.situation.loops?this.at(this.situation.loops,!0):this.at(1,!0)},at:function(t,e){var i=this.situation.duration/this._speed;return this.absPos=t,e||(this.situation.reversed&&(this.absPos=1-this.absPos),this.absPos+=this.situation.loop),this.situation.start=+new Date-this.absPos*i,this.situation.finish=this.situation.start+i,this.step(!0)},speed:function(t){return 0===t?this.pause():t?(this._speed=t,this.at(this.absPos,!0)):this._speed},loop:function(t,e){var i=this.last();return i.loops=null==t||t,i.loop=0,e&&(i.reversing=!0),this},pause:function(){return this.paused=!0,this.stopAnimFrame(),this},play:function(){return this.paused?(this.paused=!1,this.at(this.absPos,!0)):this},reverse:function(t){var e=this.last();return e.reversed=void 0===t?!e.reversed:t,this},progress:function(t){return t?this.situation.ease(this.pos):this.pos},after:function(t){var e=this.last(),i=function i(n){n.detail.situation==e&&(t.call(this,e),this.off("finished.fx",i))};return this.target().on("finished.fx",i),this._callStart()},during:function(t){var e=this.last(),i=function(i){i.detail.situation==e&&t.call(this,i.detail.pos,w.morph(i.detail.pos),i.detail.eased,e)};return this.target().off("during.fx",i).on("during.fx",i),this.after(function(){this.off("during.fx",i)}),this._callStart()},afterAll:function(t){var e=function e(i){t.call(this),this.off("allfinished.fx",e)};return this.target().off("allfinished.fx",e).on("allfinished.fx",e),this._callStart()},duringAll:function(t){var e=function(e){t.call(this,e.detail.pos,w.morph(e.detail.pos),e.detail.eased,e.detail.situation)};return this.target().off("during.fx",e).on("during.fx",e),this.afterAll(function(){this.off("during.fx",e)}),this._callStart()},last:function(){return this.situations.length?this.situations[this.situations.length-1]:this.situation},add:function(t,e,i){return this.last()[i||"animations"][t]=e,this._callStart()},step:function(t){if(t||(this.absPos=this.timeToAbsPos(+new Date)),!1!==this.situation.loops){var e,i,n;e=Math.max(this.absPos,0),i=Math.floor(e),!0===this.situation.loops||ithis.lastPos&&s<=r&&(this.situation.once[s].call(this.target(),this.pos,r),delete this.situation.once[s]);return this.active&&this.target().fire("during",{pos:this.pos,eased:r,fx:this,situation:this.situation}),this.situation?(this.eachAt(),1==this.pos&&!this.situation.reversed||this.situation.reversed&&0==this.pos?(this.stopAnimFrame(),this.target().fire("finished",{fx:this,situation:this.situation}),this.situations.length||(this.target().fire("allfinished"),this.situations.length||(this.target().off(".fx"),this.active=!1)),this.active?this.dequeue():this.clearCurrent()):!this.paused&&this.active&&this.startAnimFrame(),this.lastPos=r,this):this},eachAt:function(){var t,e,i,n=this,r=this.target(),s=this.situation;for(t in s.animations)i=[].concat(s.animations[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r[t].apply(r,i);for(t in s.attrs)i=[t].concat(s.attrs[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.attr.apply(r,i);for(t in s.styles)i=[t].concat(s.styles[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.style.apply(r,i);if(s.transforms.length){for(i=s.initialTransformation,t=0,e=s.transforms.length;t=0;--e)this[P[e]]=null!=t[P[e]]?t[P[e]]:i[P[e]]},extend:{extract:function(){var t=c(this,0,1),e=c(this,1,0),i=180/Math.PI*Math.atan2(t.y,t.x)-90;return{x:this.e,y:this.f,transformedX:(this.e*Math.cos(i*Math.PI/180)+this.f*Math.sin(i*Math.PI/180))/Math.sqrt(this.a*this.a+this.b*this.b),transformedY:(this.f*Math.cos(i*Math.PI/180)+this.e*Math.sin(-i*Math.PI/180))/Math.sqrt(this.c*this.c+this.d*this.d),skewX:-i,skewY:180/Math.PI*Math.atan2(e.y,e.x),scaleX:Math.sqrt(this.a*this.a+this.b*this.b),scaleY:Math.sqrt(this.c*this.c+this.d*this.d),rotation:i,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f,matrix:new w.Matrix(this)}},clone:function(){return new w.Matrix(this)},morph:function(t){return this.destination=new w.Matrix(t),this},at:function(t){return this.destination?new w.Matrix({a:this.a+(this.destination.a-this.a)*t,b:this.b+(this.destination.b-this.b)*t,c:this.c+(this.destination.c-this.c)*t,d:this.d+(this.destination.d-this.d)*t,e:this.e+(this.destination.e-this.e)*t,f:this.f+(this.destination.f-this.f)*t}):this},multiply:function(t){return new w.Matrix(this.native().multiply(d(t).native()))},inverse:function(){return new w.Matrix(this.native().inverse())},translate:function(t,e){return new w.Matrix(this.native().translate(t||0,e||0))},scale:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),this.around(i,n,new w.Matrix(t,0,0,e,0,0))},rotate:function(t,e,i){return t=w.utils.radians(t),this.around(e,i,new w.Matrix(Math.cos(t),Math.sin(t),-Math.sin(t),Math.cos(t),0,0))},flip:function(t,e){return"x"==t?this.scale(-1,1,e,0):"y"==t?this.scale(1,-1,0,e):this.scale(-1,-1,t,null!=e?e:t)},skew:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),t=w.utils.radians(t),e=w.utils.radians(e), +this.around(i,n,new w.Matrix(1,Math.tan(e),Math.tan(t),1,0,0))},skewX:function(t,e,i){return this.skew(t,0,e,i)},skewY:function(t,e,i){return this.skew(0,t,e,i)},around:function(t,e,i){return this.multiply(new w.Matrix(1,0,0,1,t||0,e||0)).multiply(i).multiply(new w.Matrix(1,0,0,1,-t||0,-e||0))},native:function(){for(var t=w.parser.native.createSVGMatrix(),e=P.length-1;e>=0;e--)t[P[e]]=this[P[e]];return t},toString:function(){return"matrix("+g(this.a)+","+g(this.b)+","+g(this.c)+","+g(this.d)+","+g(this.e)+","+g(this.f)+")"}},parent:w.Element,construct:{ctm:function(){return new w.Matrix(this.node.getCTM())},screenCTM:function(){if(this instanceof w.Nested){var t=this.rect(1,1),e=t.node.getScreenCTM();return t.remove(),new w.Matrix(e)}return new w.Matrix(this.node.getScreenCTM())}}}),w.Point=w.invent({create:function(t,e){var i,n={x:0,y:0};i=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:null!=t?{x:t,y:null!=e?e:t}:n,this.x=i.x,this.y=i.y},extend:{clone:function(){return new w.Point(this)},morph:function(t,e){return this.destination=new w.Point(t,e),this},at:function(t){return this.destination?new w.Point({x:this.x+(this.destination.x-this.x)*t,y:this.y+(this.destination.y-this.y)*t}):this},native:function(){var t=w.parser.native.createSVGPoint();return t.x=this.x,t.y=this.y,t},transform:function(t){return new w.Point(this.native().matrixTransform(t.native()))}}}),w.extend(w.Element,{point:function(t,e){return new w.Point(t,e).transform(this.screenCTM().inverse())}}),w.extend(w.Element,{attr:function(t,e,i){if(null==t){for(t={},e=this.node.attributes,i=e.length-1;i>=0;i--)t[e[i].nodeName]=w.regex.isNumber.test(e[i].nodeValue)?parseFloat(e[i].nodeValue):e[i].nodeValue;return t}if("object"==typeof t)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return e=this.node.getAttribute(t),null==e?w.defaults.attrs[t]:w.regex.isNumber.test(e)?parseFloat(e):e;"stroke-width"==t?this.attr("stroke",parseFloat(e)>0?this._stroke:null):"stroke"==t&&(this._stroke=e),"fill"!=t&&"stroke"!=t||(w.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),e instanceof w.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new w.Number(e):w.Color.isColor(e)?e=new w.Color(e):Array.isArray(e)&&(e=new w.Array(e)),"leading"==t?this.leading&&this.leading(e):"string"==typeof i?this.node.setAttributeNS(i,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!=t&&"x"!=t||this.rebuild(t,e)}return this}}),w.extend(w.Element,{transform:function(t,e){var i,n,r=this;if("object"!=typeof t)return i=new w.Matrix(r).extract(),"string"==typeof t?i[t]:i;if(i=new w.Matrix(r),e=!!e||!!t.relative,null!=t.a)i=e?i.multiply(new w.Matrix(t)):new w.Matrix(t);else if(null!=t.rotation)p(t,r),i=e?i.rotate(t.rotation,t.cx,t.cy):i.rotate(t.rotation-i.extract().rotation,t.cx,t.cy);else if(null!=t.scale||null!=t.scaleX||null!=t.scaleY){if(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,!e){var s=i.extract();t.scaleX=1*t.scaleX/s.scaleX,t.scaleY=1*t.scaleY/s.scaleY}i=i.scale(t.scaleX,t.scaleY,t.cx,t.cy)}else if(null!=t.skew||null!=t.skewX||null!=t.skewY){if(p(t,r),t.skewX=null!=t.skew?t.skew:null!=t.skewX?t.skewX:0,t.skewY=null!=t.skew?t.skew:null!=t.skewY?t.skewY:0,!e){var s=i.extract();i=i.multiply((new w.Matrix).skew(s.skewX,s.skewY,t.cx,t.cy).inverse())}i=i.skew(t.skewX,t.skewY,t.cx,t.cy)}else t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new w.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(e?i=i.translate(t.x,t.y):(null!=t.x&&(i.e=t.x),null!=t.y&&(i.f=t.y)));return this.attr("transform",i)}}),w.extend(w.FX,{transform:function(t,e){var i,n,r=this.target();return"object"!=typeof t?(i=new w.Matrix(r).extract(),"string"==typeof t?i[t]:i):(e=!!e||!!t.relative,null!=t.a?i=new w.Matrix(t):null!=t.rotation?(p(t,r),i=new w.Rotate(t.rotation,t.cx,t.cy)):null!=t.scale||null!=t.scaleX||null!=t.scaleY?(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,i=new w.Scale(t.scaleX,t.scaleY,t.cx,t.cy)):null!=t.skewX||null!=t.skewY?(p(t,r),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,i=new w.Skew(t.skewX,t.skewY,t.cx,t.cy)):t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new w.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(i=new w.Translate(t.x,t.y)),i?(i.relative=e,this.last().transforms.push(i),this._callStart()):this)}}),w.extend(w.Element,{untransform:function(){return this.attr("transform",null)},matrixify:function(){return(this.attr("transform")||"").split(w.regex.transforms).slice(0,-1).map(function(t){var e=t.trim().split("(");return[e[0],e[1].split(w.regex.delimiter).map(function(t){return parseFloat(t)})]}).reduce(function(t,e){return"matrix"==e[0]?t.multiply(f(e[1])):t[e[0]].apply(t,e[1])},new w.Matrix)},toParent:function(t){if(this==t)return this;var e=this.screenCTM(),i=t.screenCTM().inverse();return this.addTo(t).untransform().transform(i.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),w.Transformation=w.invent({create:function(t,e){if(arguments.length>1&&"boolean"!=typeof e)return this.constructor.call(this,[].slice.call(arguments));if(Array.isArray(t))for(var i=0,n=this.arguments.length;i=0},index:function(t){return[].slice.call(this.node.childNodes).indexOf(t.node)},get:function(t){return w.adopt(this.node.childNodes[t])},first:function(){return this.get(0)},last:function(){return this.get(this.node.childNodes.length-1)},each:function(t,e){var i,n,r=this.children();for(i=0,n=r.length;in/r?this.height/r:this.width/n,this.x=e,this.y=i,this.width=n,this.height=r)}else t="string"==typeof t?t.match(c).map(function(t){return parseFloat(t)}):Array.isArray(t)?t:"object"==typeof t?[t.x,t.y,t.width,t.height]:4==arguments.length?[].slice.call(arguments):h,this.x=t[0],this.y=t[1],this.width=t[2],this.height=t[3]},extend:{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height},morph:function(t,e,i,n){return this.destination=new w.ViewBox(t,e,i,n),this},at:function(t){return this.destination?new w.ViewBox([this.x+(this.destination.x-this.x)*t,this.y+(this.destination.y-this.y)*t,this.width+(this.destination.width-this.width)*t,this.height+(this.destination.height-this.height)*t]):this}},parent:w.Container,construct:{viewbox:function(t,e,i,n){return 0==arguments.length?new w.ViewBox(this):this.attr("viewBox",new w.ViewBox(t,e,i,n))}}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){w.Element.prototype[t]=function(e){return w.on(this.node,t,e),this}}),w.listeners=[],w.handlerMap=[],w.listenerId=0,w.on=function(t,e,i,n,r){var s=i.bind(n||t.instance||t),o=(w.handlerMap.indexOf(t)+1||w.handlerMap.push(t))-1,a=e.split(".")[0],h=e.split(".")[1]||"*";w.listeners[o]=w.listeners[o]||{},w.listeners[o][a]=w.listeners[o][a]||{},w.listeners[o][a][h]=w.listeners[o][a][h]||{},i._svgjsListenerId||(i._svgjsListenerId=++w.listenerId),w.listeners[o][a][h][i._svgjsListenerId]=s,t.addEventListener(a,s,r||!1)},w.off=function(t,e,i){var n=w.handlerMap.indexOf(t),r=e&&e.split(".")[0],s=e&&e.split(".")[1],o="";if(-1!=n)if(i){if("function"==typeof i&&(i=i._svgjsListenerId),!i)return;w.listeners[n][r]&&w.listeners[n][r][s||"*"]&&(t.removeEventListener(r,w.listeners[n][r][s||"*"][i],!1),delete w.listeners[n][r][s||"*"][i])}else if(s&&r){if(w.listeners[n][r]&&w.listeners[n][r][s]){for(i in w.listeners[n][r][s])w.off(t,[r,s].join("."),i);delete w.listeners[n][r][s]}}else if(s)for(e in w.listeners[n])for(o in w.listeners[n][e])s===o&&w.off(t,[e,s].join("."));else if(r){if(w.listeners[n][r]){for(o in w.listeners[n][r])w.off(t,[r,o].join("."));delete w.listeners[n][r]}}else{for(e in w.listeners[n])w.off(t,e);delete w.listeners[n],delete w.handlerMap[n]}},w.extend(w.Element,{on:function(t,e,i,n){return w.on(this.node,t,e,i,n),this},off:function(t,e){return w.off(this.node,t,e),this},fire:function(e,i){return e instanceof t.Event?this.node.dispatchEvent(e):this.node.dispatchEvent(e=new w.CustomEvent(e,{detail:i,cancelable:!0})),this._event=e,this},event:function(){return this._event}}),w.Defs=w.invent({create:"defs",inherit:w.Container}),w.G=w.invent({create:"g",inherit:w.Container,extend:{x:function(t){return null==t?this.transform("x"):this.transform({x:t-this.x()},!0)},y:function(t){return null==t?this.transform("y"):this.transform({y:t-this.y()},!0)},cx:function(t){return null==t?this.gbox().cx:this.x(t-this.gbox().width/2)},cy:function(t){return null==t?this.gbox().cy:this.y(t-this.gbox().height/2)},gbox:function(){var t=this.bbox(),e=this.transform();return t.x+=e.x,t.x2+=e.x,t.cx+=e.x,t.y+=e.y,t.y2+=e.y,t.cy+=e.y,t}},construct:{group:function(){return this.put(new w.G)}}}),w.Doc=w.invent({create:function(t){t&&(t="string"==typeof t?e.getElementById(t):t,"svg"==t.nodeName?this.constructor.call(this,t):(this.constructor.call(this,w.create("svg")),t.appendChild(this.node),this.size("100%","100%")),this.namespace().defs())},inherit:w.Container,extend:{namespace:function(){return this.attr({xmlns:w.ns,version:"1.1"}).attr("xmlns:xlink",w.xlink,w.xmlns).attr("xmlns:svgjs",w.svgjs,w.xmlns)},defs:function(){if(!this._defs){var t;(t=this.node.getElementsByTagName("defs")[0])?this._defs=w.adopt(t):this._defs=new w.Defs,this.node.appendChild(this._defs.node)}return this._defs},parent:function(){return this.node.parentNode&&"#document"!=this.node.parentNode.nodeName?this.node.parentNode:null},spof:function(){var t=this.node.getScreenCTM();return t&&this.style("left",-t.e%1+"px").style("top",-t.f%1+"px"),this},remove:function(){return this.parent()&&this.parent().removeChild(this.node),this},clear:function(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return delete this._defs,w.parser.draw.parentNode||this.node.appendChild(w.parser.draw),this},clone:function(t){this.writeDataToDom();var e=this.node,i=x(e.cloneNode(!0));return t?(t.node||t).appendChild(i.node):e.parentNode.insertBefore(i.node,e.nextSibling),i}}}),w.extend(w.Element,{siblings:function(){return this.parent().children()},position:function(){return this.parent().index(this)},next:function(){return this.siblings()[this.position()+1]},previous:function(){return this.siblings()[this.position()-1]},forward:function(){var t=this.position()+1,e=this.parent();return e.removeElement(this).add(this,t),e instanceof w.Doc&&e.node.appendChild(e.defs().node),this},backward:function(){var t=this.position();return t>0&&this.parent().removeElement(this).add(this,t-1),this},front:function(){var t=this.parent();return t.node.appendChild(this.node),t instanceof w.Doc&&t.node.appendChild(t.defs().node),this},back:function(){return this.position()>0&&this.parent().removeElement(this).add(this,0),this},before:function(t){t.remove();var e=this.position();return this.parent().add(t,e),this},after:function(t){t.remove();var e=this.position();return this.parent().add(t,e+1),this}}),w.Mask=w.invent({create:function(){this.constructor.call(this,w.create("mask")),this.targets=[]},inherit:w.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unmask();return this.targets=[],w.Element.prototype.remove.call(this),this}},construct:{mask:function(){return this.defs().put(new w.Mask)}}}),w.extend(w.Element,{maskWith:function(t){return this.masker=t instanceof w.Mask?t:this.parent().mask().add(t),this.masker.targets.push(this),this.attr("mask",'url("#'+this.masker.attr("id")+'")')},unmask:function(){return delete this.masker,this.attr("mask",null)}}),w.ClipPath=w.invent({create:function(){this.constructor.call(this,w.create("clipPath")),this.targets=[]},inherit:w.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unclip();return this.targets=[],this.parent().removeElement(this),this}},construct:{clip:function(){return this.defs().put(new w.ClipPath)}}}),w.extend(w.Element,{clipWith:function(t){return this.clipper=t instanceof w.ClipPath?t:this.parent().clip().add(t),this.clipper.targets.push(this),this.attr("clip-path",'url("#'+this.clipper.attr("id")+'")')},unclip:function(){return delete this.clipper,this.attr("clip-path",null)}}),w.Gradient=w.invent({create:function(t){this.constructor.call(this,w.create(t+"Gradient")),this.type=t},inherit:w.Container,extend:{at:function(t,e,i){return this.put(new w.Stop).update(t,e,i)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},fill:function(){return"url(#"+this.id()+")"},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="gradientTransform"),w.Container.prototype.attr.call(this,t,e,i)}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),w.extend(w.Gradient,w.FX,{from:function(t,e){return"radial"==(this._target||this).type?this.attr({fx:new w.Number(t),fy:new w.Number(e)}):this.attr({x1:new w.Number(t),y1:new w.Number(e)})},to:function(t,e){return"radial"==(this._target||this).type?this.attr({cx:new w.Number(t),cy:new w.Number(e)}):this.attr({x2:new w.Number(t),y2:new w.Number(e)})}}),w.extend(w.Defs,{gradient:function(t,e){return this.put(new w.Gradient(t)).update(e)}}),w.Stop=w.invent({create:"stop",inherit:w.Element,extend:{update:function(t){return("number"==typeof t||t instanceof w.Number)&&(t={offset:arguments[0],color:arguments[1],opacity:arguments[2]}),null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new w.Number(t.offset)),this}}}),w.Pattern=w.invent({create:"pattern",inherit:w.Container,extend:{fill:function(){return"url(#"+this.id()+")"},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="patternTransform"),w.Container.prototype.attr.call(this,t,e,i)}},construct:{pattern:function(t,e,i){return this.defs().pattern(t,e,i)}}}),w.extend(w.Defs,{pattern:function(t,e,i){return this.put(new w.Pattern).update(i).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),w.Shape=w.invent({create:function(t){this.constructor.call(this,t)},inherit:w.Element}),w.Bare=w.invent({create:function(t,e){if(this.constructor.call(this,w.create(t)),e)for(var i in e.prototype)"function"==typeof e.prototype[i]&&(this[i]=e.prototype[i])},inherit:w.Element,extend:{words:function(t){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this.node.appendChild(e.createTextNode(t)),this}}}),w.extend(w.Parent,{element:function(t,e){return this.put(new w.Bare(t,e))}}),w.Symbol=w.invent({create:"symbol",inherit:w.Container,construct:{symbol:function(){return this.put(new w.Symbol)}}}),w.Use=w.invent({create:"use",inherit:w.Shape,extend:{element:function(t,e){return this.attr("href",(e||"")+"#"+t,w.xlink)}},construct:{use:function(t,e){return this.put(new w.Use).element(t,e)}}}),w.Rect=w.invent({create:"rect",inherit:w.Shape,construct:{rect:function(t,e){return this.put(new w.Rect).size(t,e)}}}),w.Circle=w.invent({create:"circle",inherit:w.Shape,construct:{circle:function(t){return this.put(new w.Circle).rx(new w.Number(t).divide(2)).move(0,0)}}}),w.extend(w.Circle,w.FX,{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),w.Ellipse=w.invent({create:"ellipse",inherit:w.Shape,construct:{ellipse:function(t,e){return this.put(new w.Ellipse).size(t,e).move(0,0)}}}),w.extend(w.Ellipse,w.Rect,w.FX,{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),w.extend(w.Circle,w.Ellipse,{x:function(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())},y:function(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())},cx:function(t){return null==t?this.attr("cx"):this.attr("cx",t)},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",t)},width:function(t){return null==t?2*this.rx():this.rx(new w.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new w.Number(t).divide(2))},size:function(t,e){var i=l(this,t,e);return this.rx(new w.Number(i.width).divide(2)).ry(new w.Number(i.height).divide(2))}}),w.Line=w.invent({create:"line",inherit:w.Shape,extend:{array:function(){return new w.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,i,n){return null==t?this.array():(t=void 0!==e?{x1:t,y1:e,x2:i,y2:n}:new w.PointArray(t).toLine(),this.attr(t))},move:function(t,e){return this.attr(this.array().move(t,e).toLine())},size:function(t,e){var i=l(this,t,e);return this.attr(this.array().size(i.width,i.height).toLine())}},construct:{line:function(t,e,i,n){return w.Line.prototype.plot.apply(this.put(new w.Line),null!=t?[t,e,i,n]:[0,0,0,0])}}}),w.Polyline=w.invent({create:"polyline",inherit:w.Shape,construct:{polyline:function(t){return this.put(new w.Polyline).plot(t||new w.PointArray)}}}),w.Polygon=w.invent({create:"polygon",inherit:w.Shape,construct:{polygon:function(t){return this.put(new w.Polygon).plot(t||new w.PointArray)}}}),w.extend(w.Polyline,w.Polygon,{array:function(){return this._array||(this._array=new w.PointArray(this.attr("points")))},plot:function(t){return null==t?this.array():this.clear().attr("points","string"==typeof t?t:this._array=new w.PointArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("points",this.array().move(t,e))},size:function(t,e){var i=l(this,t,e);return this.attr("points",this.array().size(i.width,i.height))}}),w.extend(w.Line,w.Polyline,w.Polygon,{morphArray:w.PointArray,x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},width:function(t){var e=this.bbox();return null==t?e.width:this.size(t,e.height)},height:function(t){var e=this.bbox();return null==t?e.height:this.size(e.width,t)}}),w.Path=w.invent({create:"path",inherit:w.Shape,extend:{morphArray:w.PathArray,array:function(){return this._array||(this._array=new w.PathArray(this.attr("d")))},plot:function(t){return null==t?this.array():this.clear().attr("d","string"==typeof t?t:this._array=new w.PathArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("d",this.array().move(t,e))},x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},size:function(t,e){var i=l(this,t,e);return this.attr("d",this.array().size(i.width,i.height))},width:function(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)},height:function(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}},construct:{path:function(t){return this.put(new w.Path).plot(t||new w.PathArray)}}}),w.Image=w.invent({create:"image",inherit:w.Shape,extend:{load:function(e){if(!e)return this;var i=this,n=new t.Image;return w.on(n,"load",function(){w.off(n);var t=i.parent(w.Pattern);null!==t&&(0==i.width()&&0==i.height()&&i.size(n.width,n.height),t&&0==t.width()&&0==t.height()&&t.size(i.width(),i.height()),"function"==typeof i._loaded&&i._loaded.call(i,{width:n.width,height:n.height,ratio:n.width/n.height,url:e}))}),w.on(n,"error",function(t){w.off(n),"function"==typeof i._error&&i._error.call(i,t)}),this.attr("href",n.src=this.src=e,w.xlink)},loaded:function(t){return this._loaded=t,this},error:function(t){return this._error=t,this}},construct:{image:function(t,e,i){return this.put(new w.Image).load(t).size(e||0,i||e||0)}}}),w.Text=w.invent({create:function(){this.constructor.call(this,w.create("text")),this.dom.leading=new w.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",w.defaults.attrs["font-family"])},inherit:w.Shape,extend:{x:function(t){return null==t?this.attr("x"):this.attr("x",t)},y:function(t){var e=this.attr("y"),i="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-i:e:this.attr("y","number"==typeof t.valueOf()?t+i:t)},cx:function(t){return null==t?this.bbox().cx:this.x(t-this.bbox().width/2)},cy:function(t){return null==t?this.bbox().cy:this.y(t-this.bbox().height/2)},text:function(t){if(void 0===t){for(var t="",e=this.node.childNodes,i=0,n=e.length;i=0;e--)null!=i[M[t][e]]&&this.attr(M.prefix(t,M[t][e]),i[M[t][e]]);return this},w.extend(w.Element,w.FX,i)}),w.extend(w.Element,w.FX,{rotate:function(t,e,i){return this.transform({rotation:t,cx:e,cy:i})},skew:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({skew:t,cx:e,cy:i}):this.transform({skewX:t,skewY:e,cx:i,cy:n})},scale:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({scale:t,cx:e,cy:i}):this.transform({scaleX:t,scaleY:e,cx:i,cy:n})},translate:function(t,e){return this.transform({x:t,y:e})},flip:function(t,e){return e="number"==typeof t?t:e,this.transform({flip:t||"both",offset:e})},matrix:function(t){return this.attr("transform",new w.Matrix(6==arguments.length?[].slice.call(arguments):t))},opacity:function(t){return this.attr("opacity",t)},dx:function(t){return this.x(new w.Number(t).plus(this instanceof w.FX?0:this.x()),!0)},dy:function(t){return this.y(new w.Number(t).plus(this instanceof w.FX?0:this.y()),!0)},dmove:function(t,e){return this.dx(t).dy(e)}}),w.extend(w.Rect,w.Ellipse,w.Circle,w.Gradient,w.FX,{radius:function(t,e){var i=(this._target||this).type;return"radial"==i||"circle"==i?this.attr("r",new w.Number(t)):this.rx(t).ry(null==e?t:e)}}),w.extend(w.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),w.extend(w.Parent,w.Text,w.Tspan,w.FX,{font:function(t,e){if("object"==typeof t)for(e in t)this.font(e,t[e]);return"leading"==t?this.leading(e):"anchor"==t?this.attr("text-anchor",e):"size"==t||"family"==t||"weight"==t||"stretch"==t||"variant"==t||"style"==t?this.attr("font-"+t,e):this.attr(t,e)}}),w.Set=w.invent({create:function(t){Array.isArray(t)?this.members=t:this.clear()},extend:{add:function(){var t,e,i=[].slice.call(arguments);for(t=0,e=i.length;t-1&&this.members.splice(e,1),this},each:function(t){for(var e=0,i=this.members.length;e=0},index:function(t){return this.members.indexOf(t)},get:function(t){return this.members[t]},first:function(){return this.get(0)},last:function(){return this.get(this.members.length-1)},valueOf:function(){return this.members},bbox:function(){if(0==this.members.length)return new w.RBox;var t=this.members[0].rbox(this.members[0].doc());return this.each(function(){t=t.merge(this.rbox(this.doc()))}),t}},construct:{set:function(t){return new w.Set(t)}}}),w.FX.Set=w.invent({create:function(t){this.set=t}}),w.Set.inherit=function(){ +var t,e=[];for(var t in w.Shape.prototype)"function"==typeof w.Shape.prototype[t]&&"function"!=typeof w.Set.prototype[t]&&e.push(t);e.forEach(function(t){w.Set.prototype[t]=function(){for(var e=0,i=this.members.length;e=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),w.get=function(t){var i=e.getElementById(v(t)||t);return w.adopt(i)},w.select=function(t,i){return new w.Set(w.utils.map((i||e).querySelectorAll(t),function(t){return w.adopt(t)}))},w.extend(w.Parent,{select:function(t){return w.select(t,this.node)}});var P="abcdef".split("");if("function"!=typeof t.CustomEvent){var k=function(t,i){i=i||{bubbles:!1,cancelable:!1,detail:void 0};var n=e.createEvent("CustomEvent");return n.initCustomEvent(t,i.bubbles,i.cancelable,i.detail),n};k.prototype=t.Event.prototype,w.CustomEvent=k}else w.CustomEvent=t.CustomEvent;return function(e){for(var i=0,n=["moz","webkit"],r=0;r. +# + +import os, subprocess, shlex +from inginious import feedback +from inginious import input + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("insertion_sort.py") + + p = subprocess.Popen(shlex.split("python3 insertion_sort.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("Quelque chose s'est mal passé. Veuillez vérifier que l'agencement de vos blocs est correct. "+make_output) + exit(0) + if make_output == "True": + feedback.set_global_result("success") + feedback.set_global_feedback("Félicitations ! La liste est bien triée.") + else: + feedback.set_global_result("failed") + feedback.set_global_feedback("Mauvaise réponse. " + make_output) diff --git a/Tri_PDS/7_quick_sort/student/insertion_sort.py b/Tri_PDS/7_quick_sort/student/insertion_sort.py new file mode 100644 index 0000000..828c5f7 --- /dev/null +++ b/Tri_PDS/7_quick_sort/student/insertion_sort.py @@ -0,0 +1,33 @@ +#!/bin/python3 + +# +# Copyright (c) 2018 Ilias Boutchichi +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +from random import randint +import os + +def student_code(): +@ @algo@@ + +if __name__ == "__main__": + try: + student_code() + except: + print("Unexpected error:", sys.exc_info()[0]) + correct_list = sorted(B) + if correct_list == B: + print('True', end='', flush=True) + else: + print('Vous obtenez la liste suivante: ' + str(B) + ' au lieu de: ' + str(correct_list)) diff --git a/Tri_PDS/7_quick_sort/task.yaml b/Tri_PDS/7_quick_sort/task.yaml new file mode 100644 index 0000000..a52f556 --- /dev/null +++ b/Tri_PDS/7_quick_sort/task.yaml @@ -0,0 +1,355 @@ +accessible: true +author: Celine Deknop +context: |- + Le tri rapide est un peu plus compliqué à comprendre, mais utilise un principe très courant en informatique : diviser pour mieux régner. Plutôt que d'essayer de trier toute la liste d'un coup, on prend un élément au hasard (le pivot), et on met les autres éléments avant s'ils sont plus petits, et après s'ils sont plus grands, sans ce soucier de leur ordre. Ensuite, on recommence avec un autre pivot, jusqu'à-ce que la liste soit triée. + + Cette fois-ci, l'animation est un peu plus compliquée. Peut-tu compter les comparaisons ? +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + output: '2' + time: '10' +name: Tri rapide +network_grading: false +problems: + algo: + options: + css: true + zoom: + scaleSpeed: 1.2 + maxScale: 3.0 + minScale: 0.3 + controls: true + startScale: 1.0 + wheel: false + grid: + snap: true + colour: '#ccc' + length: 3 + spacing: 20 + maxBlocks: '100' + scrollbars: true + toolboxPosition: start + media: plugins/blockly/static/blockly/media/ + visual: + position: left + files: + - svg_min.js + - insertion_sort.js + workspace: |- + + + premier + dernier + pivot + j + i + + + + + + + + + + + 0 + + + + + 9 + + + + + + + + + + + + partitionner + Décrire cette fonction… + + + + + dernier + + + + + pivot + + + + + j + + + premier + + + + + i + + + 1 + + + premier + + + + + 10 + + + MINUS + + + 1 + + + dernier + + + + + 1 + + + + + + + 1 + + + + + + + + + + + + i + + + + + j + + + + + j + + + ADD + + + 1 + + + j + + + + + 1 + + + + + + + + + + + + + + + dernier + + + + + j + + + + + + + + + + + + + j + + + + + + + + + tri_rapide + Décrire cette fonction… + + + + + LT + + + premier + + + + + dernier + + + + + + + pivot + + + + + premier + + + + + dernier + + + + + + + pivot + + + + + + + + + + premier + + + + + dernier + + + + + pivot + + + + + + + + + + + + + premier + + + + + MINUS + + + 1 + + + pivot + + + + + 1 + + + + + + + + + + + + + ADD + + + 1 + + + pivot + + + + + 1 + + + + + + + dernier + + + + + + + + + + + + + + + toolbox: '' + type: blockly + name: '' + blocks_files: + - customblocks.js + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/Cours 1/Lecon1/course.yaml b/Tri_PDS/course.yaml similarity index 90% rename from Cours 1/Lecon1/course.yaml rename to Tri_PDS/course.yaml index dd72b7a..44e2b7a 100644 --- a/Cours 1/Lecon1/course.yaml +++ b/Tri_PDS/course.yaml @@ -1,5 +1,6 @@ accessible: true -name: Cours1 +name: Tri_PDS +description: '' admins: - '' tutors: [] diff --git a/app0-2017/1_Full_scenario/public/blocks.js b/app0-2017/1_Full_scenario/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/1_Full_scenario/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/1_Full_scenario/public/interpreter.js b/app0-2017/1_Full_scenario/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/1_Full_scenario/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/1_Full_scenario/public/maze.js b/app0-2017/1_Full_scenario/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/1_Full_scenario/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/1_Full_scenario/public/maze/americans.png b/app0-2017/1_Full_scenario/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/americans.png differ diff --git a/app0-2017/1_Full_scenario/public/maze/avatar.png b/app0-2017/1_Full_scenario/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/avatar.png differ diff --git a/app0-2017/1_Full_scenario/public/maze/background.png b/app0-2017/1_Full_scenario/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/background.png differ diff --git a/app0-2017/1_Full_scenario/public/maze/failure.mp3 b/app0-2017/1_Full_scenario/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/failure.mp3 differ diff --git a/app0-2017/1_Full_scenario/public/maze/failure.ogg b/app0-2017/1_Full_scenario/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/failure.ogg differ diff --git a/app0-2017/1_Full_scenario/public/maze/failure_avatar.png b/app0-2017/1_Full_scenario/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/failure_avatar.png differ diff --git a/app0-2017/1_Full_scenario/public/maze/goal.gif b/app0-2017/1_Full_scenario/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/goal.gif differ diff --git a/app0-2017/1_Full_scenario/public/maze/goalIdle.gif b/app0-2017/1_Full_scenario/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/goalIdle.gif differ diff --git a/app0-2017/1_Full_scenario/public/maze/maze_forever.gif b/app0-2017/1_Full_scenario/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/maze_forever.gif differ diff --git a/app0-2017/1_Full_scenario/public/maze/nedstark.png b/app0-2017/1_Full_scenario/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/nedstark.png differ diff --git a/app0-2017/1_Full_scenario/public/maze/obstacle.gif b/app0-2017/1_Full_scenario/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/obstacle.gif differ diff --git a/app0-2017/1_Full_scenario/public/maze/obstacle.mp3 b/app0-2017/1_Full_scenario/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/obstacle.mp3 differ diff --git a/app0-2017/1_Full_scenario/public/maze/obstacle.ogg b/app0-2017/1_Full_scenario/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/obstacle.ogg differ diff --git a/app0-2017/1_Full_scenario/public/maze/obstacleIdle.gif b/app0-2017/1_Full_scenario/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/1_Full_scenario/public/maze/small_static_avatar.png b/app0-2017/1_Full_scenario/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/small_static_avatar.png differ diff --git a/app0-2017/1_Full_scenario/public/maze/spies-dead.png b/app0-2017/1_Full_scenario/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/spies-dead.png differ diff --git a/app0-2017/1_Full_scenario/public/maze/start.mp3 b/app0-2017/1_Full_scenario/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/start.mp3 differ diff --git a/app0-2017/1_Full_scenario/public/maze/start.ogg b/app0-2017/1_Full_scenario/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/start.ogg differ diff --git a/app0-2017/1_Full_scenario/public/maze/static_avatar.png b/app0-2017/1_Full_scenario/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/static_avatar.png differ diff --git a/app0-2017/1_Full_scenario/public/maze/test.png b/app0-2017/1_Full_scenario/public/maze/test.png new file mode 100644 index 0000000..832219e Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/test.png differ diff --git a/app0-2017/1_Full_scenario/public/maze/testBack.png b/app0-2017/1_Full_scenario/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/testBack.png differ diff --git a/app0-2017/1_Full_scenario/public/maze/testBackPlain.png b/app0-2017/1_Full_scenario/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/testBackPlain.png differ diff --git a/app0-2017/1_Full_scenario/public/maze/tiles.png b/app0-2017/1_Full_scenario/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/tiles.png differ diff --git a/app0-2017/1_Full_scenario/public/maze/wall.mp3 b/app0-2017/1_Full_scenario/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/wall.mp3 differ diff --git a/app0-2017/1_Full_scenario/public/maze/wall.ogg b/app0-2017/1_Full_scenario/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/wall.ogg differ diff --git a/app0-2017/1_Full_scenario/public/maze/wall.png b/app0-2017/1_Full_scenario/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/wall.png differ diff --git a/app0-2017/1_Full_scenario/public/maze/win.mp3 b/app0-2017/1_Full_scenario/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/win.mp3 differ diff --git a/app0-2017/1_Full_scenario/public/maze/win.ogg b/app0-2017/1_Full_scenario/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/win.ogg differ diff --git a/app0-2017/1_Full_scenario/public/maze/win_avatar.png b/app0-2017/1_Full_scenario/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/win_avatar.png differ diff --git a/app0-2017/1_Full_scenario/public/maze/wolf.png b/app0-2017/1_Full_scenario/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/1_Full_scenario/public/maze/wolf.png differ diff --git a/app0-2017/1_Full_scenario/public/maze_config.json b/app0-2017/1_Full_scenario/public/maze_config.json new file mode 100644 index 0000000..d491e72 --- /dev/null +++ b/app0-2017/1_Full_scenario/public/maze_config.json @@ -0,0 +1,463 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [2, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 3], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [2, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 3], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 4, 1, 1, 1, 1, 0, 1, 4, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 4, 0, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 4, 4, 1, 1, 1, 0, 1, 1], + [1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 3], + [1, 1, 1, 0, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1], + [1, 0, 1, 2, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 4, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 4, 1, 1, 1, 1, 0, 1, 4, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 4, 0, 1, 1], + [1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 4, 4, 1, 1, 1, 0, 1, 1], + [1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 3], + [1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 4, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 4, 1, 1], + [1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 3], + [1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1], + [1, 1, 2, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 4, 1, 1], + [1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 3], + [1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1], + [1, 1, 2, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 4, 1, 1, 4, 1, 1, 0, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 4, 1, 1, 1, 1], + [1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3], + [1, 0, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 4, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 4, 1, 1, 4, 1, 1, 0, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 3], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 4, 1, 1, 1, 1], + [1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 4, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 2, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 4, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 1, 1, 4, 1, 1, 1, 1, 0, 1, 0, 1], + [1, 1, 0, 1, 0, 1, 1, 4, 1, 1, 1, 1, 0, 1, 1, 3], + [1, 1, 0, 0, 0, 1, 1, 4, 1, 1, 0, 0, 0, 1, 4, 1], + [1, 1, 0, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 4, 4, 4, 1, 1, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 0, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 4, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 1, 1, 4, 1, 1, 1, 1, 0, 1, 0, 1], + [1, 1, 0, 1, 0, 1, 1, 4, 1, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 0, 0, 0, 1, 1, 4, 1, 1, 0, 0, 0, 1, 4, 1], + [1, 1, 0, 1, 1, 1, 1, 4, 1, 2, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 4, 4, 4, 1, 1, 0, 1, 1, 3], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 0, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 3], + [1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 2, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 3], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1], + [2, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [2, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1], + [1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 3], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1], + [1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 2, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1], + [1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 3], + [1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1], + [1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1], + [1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 3], + [1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 2, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 4, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 0, 2, 1, 0, 1, 1, 0, 0, 0, 1, 4, 1, 1, 1], + [1, 1, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3], + [1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 0, 1, 1, 1, 0, 1, 4, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 4, 1, 0, 1, 1, 1, 4, 1, 0, 1, 1], + [1, 0, 0, 1, 1, 1, 1, 0, 1, 4, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 4, 1, 3], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 4, 1, 1, 1], + [1, 1, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 0, 1, 1, 1, 0, 1, 4, 1, 1, 1, 0, 1, 1], + [1, 1, 2, 1, 1, 4, 1, 0, 1, 1, 1, 4, 1, 0, 1, 1], + [1, 0, 0, 1, 1, 1, 1, 0, 1, 4, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 4, 1, 1, 1], + [1, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 4, 1, 1, 1, 1], + [1, 4, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 3], + [1, 1, 4, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 2, 1, 1, 4, 1], + [1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 4, 1, 1, 1], + [1, 1, 4, 1, 0, 1, 2, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 4, 1, 1, 1, 3], + [1, 4, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1], + [1, 1, 4, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 4, 1], + [1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 2, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 3], + [1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1]], + + [[1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 2, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 3], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 0, 1, 4, 1, 1, 1, 1, 4, 1, 1], + [1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1], + [1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 4, 1, 4, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 3], + [1, 1, 4, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 4, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 1, 1, 1, 4, 1, 0, 1, 0, 1, 4, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 4, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 0, 1, 4, 1, 1, 1, 1, 4, 1, 1], + [1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 3], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 4, 1, 4, 1, 0, 1], + [1, 1, 2, 1, 1, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 4, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 4, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 1, 1, 1, 4, 1, 0, 1, 0, 1, 4, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 4, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 4, 1, 1, 2, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1], + [1, 0, 1, 1, 4, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 4, 1, 1, 1, 1, 4, 1], + [1, 4, 1, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1], + [1, 1, 1, 0, 0, 1, 4, 1, 1, 4, 1, 1, 1, 0, 1, 3], + [1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 0, 1, 1], + [1, 1, 1, 1, 4, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 0, 1, 1, 0, 1, 4, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 4, 1, 1, 2, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1], + [1, 0, 1, 1, 4, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 4, 1, 1, 1, 1, 4, 1], + [1, 4, 1, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1], + [1, 1, 1, 0, 0, 1, 4, 1, 1, 4, 1, 1, 1, 0, 1, 3], + [1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 0, 1, 1], + [1, 1, 1, 1, 4, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 0, 1, 1, 0, 1, 4, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 1, 1, 1, 1, 0, 4, 1, 0, 1, 1, 0, 3], + [1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 2, 1, 0, 1, 1], + [1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 2, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 1, 1, 1, 1, 0, 4, 1, 0, 1, 1, 0, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 3], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 4, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 4, 1, 4, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1], + [1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 3], + [1, 4, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 4, 1, 1, 1, 1, 1, 1, 1, 4, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 4, 1, 1, 0, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 4, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 4, 1, 4, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 1, 0, 1, 1, 1], + [1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1], + [1, 4, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 4, 1, 1, 1, 1, 1, 1, 1, 4, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 4, 1, 1, 0, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3]] + ], + "maxSteps":-1, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/1_Full_scenario/run b/app0-2017/1_Full_scenario/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/1_Full_scenario/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/1_Full_scenario/student/maze.tpl.py b/app0-2017/1_Full_scenario/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/1_Full_scenario/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/1_Full_scenario/task.yaml b/app0-2017/1_Full_scenario/task.yaml new file mode 100644 index 0000000..2f170f8 --- /dev/null +++ b/app0-2017/1_Full_scenario/task.yaml @@ -0,0 +1,128 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + memory: '100' + time: '30' + output: '2' +name: Scénario +network_grading: false +order: 0 +problems: + code: + name: '' + options: + zoom: + startScale: 1.0 + maxScale: 3.0 + controls: true + minScale: 0.3 + scaleSpeed: 1.2 + wheel: false + toolboxPosition: start + oneBasedIndex: true + visual: + position: left + sounds: true + trashcan: true + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + scrollbars: true + type: blockly + header: '' + blocks_files: + - blocks.js + files: + - maze.js + - interpreter.js + toolbox: |- + + + + + + + + + + TRUE + + + + + + EQ + + + + AND + + + + + + turnLeft + + + + + + + + WHILE + + + + + X + + + X + + + + + + + 0 + + + + + + + + + + fonction sans return + Décrire cette fonction… + + + fonction return + Décrire cette fonction… + + + + + + variable + + + variable + + + variable + + + workspace: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/APP0_senario_1/public/blocks.js b/app0-2017/APP0_senario_1/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/APP0_senario_1/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/APP0_senario_1/public/interpreter.js b/app0-2017/APP0_senario_1/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/APP0_senario_1/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/APP0_senario_1/public/maze.js b/app0-2017/APP0_senario_1/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/APP0_senario_1/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/APP0_senario_1/public/maze/americans.png b/app0-2017/APP0_senario_1/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/americans.png differ diff --git a/app0-2017/APP0_senario_1/public/maze/avatar.png b/app0-2017/APP0_senario_1/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/avatar.png differ diff --git a/app0-2017/APP0_senario_1/public/maze/background.png b/app0-2017/APP0_senario_1/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/background.png differ diff --git a/app0-2017/APP0_senario_1/public/maze/failure.mp3 b/app0-2017/APP0_senario_1/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/failure.mp3 differ diff --git a/app0-2017/APP0_senario_1/public/maze/failure.ogg b/app0-2017/APP0_senario_1/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/failure.ogg differ diff --git a/app0-2017/APP0_senario_1/public/maze/failure_avatar.png b/app0-2017/APP0_senario_1/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/failure_avatar.png differ diff --git a/app0-2017/APP0_senario_1/public/maze/goal.gif b/app0-2017/APP0_senario_1/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/goal.gif differ diff --git a/app0-2017/APP0_senario_1/public/maze/goalIdle.gif b/app0-2017/APP0_senario_1/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/goalIdle.gif differ diff --git a/app0-2017/APP0_senario_1/public/maze/maze_forever.gif b/app0-2017/APP0_senario_1/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/maze_forever.gif differ diff --git a/app0-2017/APP0_senario_1/public/maze/nedstark.png b/app0-2017/APP0_senario_1/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/nedstark.png differ diff --git a/app0-2017/APP0_senario_1/public/maze/obstacle.gif b/app0-2017/APP0_senario_1/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/obstacle.gif differ diff --git a/app0-2017/APP0_senario_1/public/maze/obstacle.mp3 b/app0-2017/APP0_senario_1/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/obstacle.mp3 differ diff --git a/app0-2017/APP0_senario_1/public/maze/obstacle.ogg b/app0-2017/APP0_senario_1/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/obstacle.ogg differ diff --git a/app0-2017/APP0_senario_1/public/maze/obstacleIdle.gif b/app0-2017/APP0_senario_1/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/APP0_senario_1/public/maze/small_static_avatar.png b/app0-2017/APP0_senario_1/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/small_static_avatar.png differ diff --git a/app0-2017/APP0_senario_1/public/maze/spies-dead.png b/app0-2017/APP0_senario_1/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/spies-dead.png differ diff --git a/app0-2017/APP0_senario_1/public/maze/start.mp3 b/app0-2017/APP0_senario_1/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/start.mp3 differ diff --git a/app0-2017/APP0_senario_1/public/maze/start.ogg b/app0-2017/APP0_senario_1/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/start.ogg differ diff --git a/app0-2017/APP0_senario_1/public/maze/static_avatar.png b/app0-2017/APP0_senario_1/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/static_avatar.png differ diff --git a/app0-2017/APP0_senario_1/public/maze/testBack.png b/app0-2017/APP0_senario_1/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/testBack.png differ diff --git a/app0-2017/APP0_senario_1/public/maze/testBackPlain.png b/app0-2017/APP0_senario_1/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/testBackPlain.png differ diff --git a/app0-2017/APP0_senario_1/public/maze/tiles.png b/app0-2017/APP0_senario_1/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/tiles.png differ diff --git a/app0-2017/APP0_senario_1/public/maze/wall.mp3 b/app0-2017/APP0_senario_1/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/wall.mp3 differ diff --git a/app0-2017/APP0_senario_1/public/maze/wall.ogg b/app0-2017/APP0_senario_1/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/wall.ogg differ diff --git a/app0-2017/APP0_senario_1/public/maze/wall.png b/app0-2017/APP0_senario_1/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/wall.png differ diff --git a/app0-2017/APP0_senario_1/public/maze/win.mp3 b/app0-2017/APP0_senario_1/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/win.mp3 differ diff --git a/app0-2017/APP0_senario_1/public/maze/win.ogg b/app0-2017/APP0_senario_1/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/win.ogg differ diff --git a/app0-2017/APP0_senario_1/public/maze/win_avatar.png b/app0-2017/APP0_senario_1/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/win_avatar.png differ diff --git a/app0-2017/APP0_senario_1/public/maze/wolf.png b/app0-2017/APP0_senario_1/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/APP0_senario_1/public/maze/wolf.png differ diff --git a/app0-2017/APP0_senario_1/public/maze_config.json b/app0-2017/APP0_senario_1/public/maze_config.json new file mode 100644 index 0000000..a87526a --- /dev/null +++ b/app0-2017/APP0_senario_1/public/maze_config.json @@ -0,0 +1,99 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 4, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 4, 1, 4, 1], + [1, 1, 1, 2, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 3], + [1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1], + [1, 4, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 4, 1, 1, 1, 1, 1, 1, 1, 4, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 4, 1, 1, 0, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 4, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 4, 1, 4, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 1, 0, 1, 1, 1], + [1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1], + [1, 4, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 4, 1, 1, 1, 1, 1, 1, 1, 4, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 4, 1, 1, 0, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/APP0_senario_1/run b/app0-2017/APP0_senario_1/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/APP0_senario_1/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/APP0_senario_1/student/maze.tpl.py b/app0-2017/APP0_senario_1/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/APP0_senario_1/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/APP0_senario_1/task.yaml b/app0-2017/APP0_senario_1/task.yaml new file mode 100644 index 0000000..9c83fbf --- /dev/null +++ b/app0-2017/APP0_senario_1/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: Scénario 2 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/APP0_senario_10/public/blocks.js b/app0-2017/APP0_senario_10/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/APP0_senario_10/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/APP0_senario_10/public/interpreter.js b/app0-2017/APP0_senario_10/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/APP0_senario_10/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/APP0_senario_10/public/maze.js b/app0-2017/APP0_senario_10/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/APP0_senario_10/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/APP0_senario_10/public/maze/americans.png b/app0-2017/APP0_senario_10/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/americans.png differ diff --git a/app0-2017/APP0_senario_10/public/maze/avatar.png b/app0-2017/APP0_senario_10/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/avatar.png differ diff --git a/app0-2017/APP0_senario_10/public/maze/background.png b/app0-2017/APP0_senario_10/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/background.png differ diff --git a/app0-2017/APP0_senario_10/public/maze/failure.mp3 b/app0-2017/APP0_senario_10/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/failure.mp3 differ diff --git a/app0-2017/APP0_senario_10/public/maze/failure.ogg b/app0-2017/APP0_senario_10/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/failure.ogg differ diff --git a/app0-2017/APP0_senario_10/public/maze/failure_avatar.png b/app0-2017/APP0_senario_10/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/failure_avatar.png differ diff --git a/app0-2017/APP0_senario_10/public/maze/goal.gif b/app0-2017/APP0_senario_10/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/goal.gif differ diff --git a/app0-2017/APP0_senario_10/public/maze/goalIdle.gif b/app0-2017/APP0_senario_10/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/goalIdle.gif differ diff --git a/app0-2017/APP0_senario_10/public/maze/maze_forever.gif b/app0-2017/APP0_senario_10/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/maze_forever.gif differ diff --git a/app0-2017/APP0_senario_10/public/maze/nedstark.png b/app0-2017/APP0_senario_10/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/nedstark.png differ diff --git a/app0-2017/APP0_senario_10/public/maze/obstacle.gif b/app0-2017/APP0_senario_10/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/obstacle.gif differ diff --git a/app0-2017/APP0_senario_10/public/maze/obstacle.mp3 b/app0-2017/APP0_senario_10/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/obstacle.mp3 differ diff --git a/app0-2017/APP0_senario_10/public/maze/obstacle.ogg b/app0-2017/APP0_senario_10/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/obstacle.ogg differ diff --git a/app0-2017/APP0_senario_10/public/maze/obstacleIdle.gif b/app0-2017/APP0_senario_10/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/APP0_senario_10/public/maze/small_static_avatar.png b/app0-2017/APP0_senario_10/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/small_static_avatar.png differ diff --git a/app0-2017/APP0_senario_10/public/maze/spies-dead.png b/app0-2017/APP0_senario_10/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/spies-dead.png differ diff --git a/app0-2017/APP0_senario_10/public/maze/start.mp3 b/app0-2017/APP0_senario_10/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/start.mp3 differ diff --git a/app0-2017/APP0_senario_10/public/maze/start.ogg b/app0-2017/APP0_senario_10/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/start.ogg differ diff --git a/app0-2017/APP0_senario_10/public/maze/static_avatar.png b/app0-2017/APP0_senario_10/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/static_avatar.png differ diff --git a/app0-2017/APP0_senario_10/public/maze/testBack.png b/app0-2017/APP0_senario_10/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/testBack.png differ diff --git a/app0-2017/APP0_senario_10/public/maze/testBackPlain.png b/app0-2017/APP0_senario_10/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/testBackPlain.png differ diff --git a/app0-2017/APP0_senario_10/public/maze/tiles.png b/app0-2017/APP0_senario_10/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/tiles.png differ diff --git a/app0-2017/APP0_senario_10/public/maze/wall.mp3 b/app0-2017/APP0_senario_10/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/wall.mp3 differ diff --git a/app0-2017/APP0_senario_10/public/maze/wall.ogg b/app0-2017/APP0_senario_10/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/wall.ogg differ diff --git a/app0-2017/APP0_senario_10/public/maze/wall.png b/app0-2017/APP0_senario_10/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/wall.png differ diff --git a/app0-2017/APP0_senario_10/public/maze/win.mp3 b/app0-2017/APP0_senario_10/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/win.mp3 differ diff --git a/app0-2017/APP0_senario_10/public/maze/win.ogg b/app0-2017/APP0_senario_10/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/win.ogg differ diff --git a/app0-2017/APP0_senario_10/public/maze/win_avatar.png b/app0-2017/APP0_senario_10/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/win_avatar.png differ diff --git a/app0-2017/APP0_senario_10/public/maze/wolf.png b/app0-2017/APP0_senario_10/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/APP0_senario_10/public/maze/wolf.png differ diff --git a/app0-2017/APP0_senario_10/public/maze_config.json b/app0-2017/APP0_senario_10/public/maze_config.json new file mode 100644 index 0000000..2ce6324 --- /dev/null +++ b/app0-2017/APP0_senario_10/public/maze_config.json @@ -0,0 +1,99 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 4, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 0, 2, 1, 0, 1, 1, 0, 0, 0, 1, 4, 1, 1, 1], + [1, 1, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3], + [1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 0, 1, 1, 1, 0, 1, 4, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 4, 1, 0, 1, 1, 1, 4, 1, 0, 1, 1], + [1, 0, 0, 1, 1, 1, 1, 0, 1, 4, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 4, 1, 3], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 4, 1, 1, 1], + [1, 1, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 0, 1, 1, 1, 0, 1, 4, 1, 1, 1, 0, 1, 1], + [1, 1, 2, 1, 1, 4, 1, 0, 1, 1, 1, 4, 1, 0, 1, 1], + [1, 0, 0, 1, 1, 1, 1, 0, 1, 4, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/APP0_senario_10/run b/app0-2017/APP0_senario_10/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/APP0_senario_10/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/APP0_senario_10/student/maze.tpl.py b/app0-2017/APP0_senario_10/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/APP0_senario_10/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/APP0_senario_10/task.yaml b/app0-2017/APP0_senario_10/task.yaml new file mode 100644 index 0000000..193c781 --- /dev/null +++ b/app0-2017/APP0_senario_10/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: Scénario 10 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/APP0_senario_11/public/blocks.js b/app0-2017/APP0_senario_11/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/APP0_senario_11/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/APP0_senario_11/public/interpreter.js b/app0-2017/APP0_senario_11/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/APP0_senario_11/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/APP0_senario_11/public/maze.js b/app0-2017/APP0_senario_11/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/APP0_senario_11/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/APP0_senario_11/public/maze/americans.png b/app0-2017/APP0_senario_11/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/americans.png differ diff --git a/app0-2017/APP0_senario_11/public/maze/avatar.png b/app0-2017/APP0_senario_11/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/avatar.png differ diff --git a/app0-2017/APP0_senario_11/public/maze/background.png b/app0-2017/APP0_senario_11/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/background.png differ diff --git a/app0-2017/APP0_senario_11/public/maze/failure.mp3 b/app0-2017/APP0_senario_11/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/failure.mp3 differ diff --git a/app0-2017/APP0_senario_11/public/maze/failure.ogg b/app0-2017/APP0_senario_11/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/failure.ogg differ diff --git a/app0-2017/APP0_senario_11/public/maze/failure_avatar.png b/app0-2017/APP0_senario_11/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/failure_avatar.png differ diff --git a/app0-2017/APP0_senario_11/public/maze/goal.gif b/app0-2017/APP0_senario_11/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/goal.gif differ diff --git a/app0-2017/APP0_senario_11/public/maze/goalIdle.gif b/app0-2017/APP0_senario_11/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/goalIdle.gif differ diff --git a/app0-2017/APP0_senario_11/public/maze/maze_forever.gif b/app0-2017/APP0_senario_11/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/maze_forever.gif differ diff --git a/app0-2017/APP0_senario_11/public/maze/nedstark.png b/app0-2017/APP0_senario_11/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/nedstark.png differ diff --git a/app0-2017/APP0_senario_11/public/maze/obstacle.gif b/app0-2017/APP0_senario_11/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/obstacle.gif differ diff --git a/app0-2017/APP0_senario_11/public/maze/obstacle.mp3 b/app0-2017/APP0_senario_11/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/obstacle.mp3 differ diff --git a/app0-2017/APP0_senario_11/public/maze/obstacle.ogg b/app0-2017/APP0_senario_11/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/obstacle.ogg differ diff --git a/app0-2017/APP0_senario_11/public/maze/obstacleIdle.gif b/app0-2017/APP0_senario_11/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/APP0_senario_11/public/maze/small_static_avatar.png b/app0-2017/APP0_senario_11/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/small_static_avatar.png differ diff --git a/app0-2017/APP0_senario_11/public/maze/spies-dead.png b/app0-2017/APP0_senario_11/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/spies-dead.png differ diff --git a/app0-2017/APP0_senario_11/public/maze/start.mp3 b/app0-2017/APP0_senario_11/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/start.mp3 differ diff --git a/app0-2017/APP0_senario_11/public/maze/start.ogg b/app0-2017/APP0_senario_11/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/start.ogg differ diff --git a/app0-2017/APP0_senario_11/public/maze/static_avatar.png b/app0-2017/APP0_senario_11/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/static_avatar.png differ diff --git a/app0-2017/APP0_senario_11/public/maze/testBack.png b/app0-2017/APP0_senario_11/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/testBack.png differ diff --git a/app0-2017/APP0_senario_11/public/maze/testBackPlain.png b/app0-2017/APP0_senario_11/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/testBackPlain.png differ diff --git a/app0-2017/APP0_senario_11/public/maze/tiles.png b/app0-2017/APP0_senario_11/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/tiles.png differ diff --git a/app0-2017/APP0_senario_11/public/maze/wall.mp3 b/app0-2017/APP0_senario_11/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/wall.mp3 differ diff --git a/app0-2017/APP0_senario_11/public/maze/wall.ogg b/app0-2017/APP0_senario_11/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/wall.ogg differ diff --git a/app0-2017/APP0_senario_11/public/maze/wall.png b/app0-2017/APP0_senario_11/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/wall.png differ diff --git a/app0-2017/APP0_senario_11/public/maze/win.mp3 b/app0-2017/APP0_senario_11/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/win.mp3 differ diff --git a/app0-2017/APP0_senario_11/public/maze/win.ogg b/app0-2017/APP0_senario_11/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/win.ogg differ diff --git a/app0-2017/APP0_senario_11/public/maze/win_avatar.png b/app0-2017/APP0_senario_11/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/win_avatar.png differ diff --git a/app0-2017/APP0_senario_11/public/maze/wolf.png b/app0-2017/APP0_senario_11/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/APP0_senario_11/public/maze/wolf.png differ diff --git a/app0-2017/APP0_senario_11/public/maze_config.json b/app0-2017/APP0_senario_11/public/maze_config.json new file mode 100644 index 0000000..c176c1d --- /dev/null +++ b/app0-2017/APP0_senario_11/public/maze_config.json @@ -0,0 +1,99 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 4, 1, 1, 1], + [1, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 4, 1, 1, 1, 1], + [1, 4, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 3], + [1, 1, 4, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 2, 1, 1, 4, 1], + [1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 4, 1, 1, 1], + [1, 1, 4, 1, 0, 1, 2, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 4, 1, 1, 1, 3], + [1, 4, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1], + [1, 1, 4, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 4, 1], + [1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/APP0_senario_11/run b/app0-2017/APP0_senario_11/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/APP0_senario_11/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/APP0_senario_11/student/maze.tpl.py b/app0-2017/APP0_senario_11/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/APP0_senario_11/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/APP0_senario_11/task.yaml b/app0-2017/APP0_senario_11/task.yaml new file mode 100644 index 0000000..b861767 --- /dev/null +++ b/app0-2017/APP0_senario_11/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: Scénario 11 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/APP0_senario_12/public/blocks.js b/app0-2017/APP0_senario_12/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/APP0_senario_12/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/APP0_senario_12/public/interpreter.js b/app0-2017/APP0_senario_12/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/APP0_senario_12/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/APP0_senario_12/public/maze.js b/app0-2017/APP0_senario_12/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/APP0_senario_12/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/APP0_senario_12/public/maze/americans.png b/app0-2017/APP0_senario_12/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/americans.png differ diff --git a/app0-2017/APP0_senario_12/public/maze/avatar.png b/app0-2017/APP0_senario_12/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/avatar.png differ diff --git a/app0-2017/APP0_senario_12/public/maze/background.png b/app0-2017/APP0_senario_12/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/background.png differ diff --git a/app0-2017/APP0_senario_12/public/maze/failure.mp3 b/app0-2017/APP0_senario_12/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/failure.mp3 differ diff --git a/app0-2017/APP0_senario_12/public/maze/failure.ogg b/app0-2017/APP0_senario_12/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/failure.ogg differ diff --git a/app0-2017/APP0_senario_12/public/maze/failure_avatar.png b/app0-2017/APP0_senario_12/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/failure_avatar.png differ diff --git a/app0-2017/APP0_senario_12/public/maze/goal.gif b/app0-2017/APP0_senario_12/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/goal.gif differ diff --git a/app0-2017/APP0_senario_12/public/maze/goalIdle.gif b/app0-2017/APP0_senario_12/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/goalIdle.gif differ diff --git a/app0-2017/APP0_senario_12/public/maze/maze_forever.gif b/app0-2017/APP0_senario_12/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/maze_forever.gif differ diff --git a/app0-2017/APP0_senario_12/public/maze/nedstark.png b/app0-2017/APP0_senario_12/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/nedstark.png differ diff --git a/app0-2017/APP0_senario_12/public/maze/obstacle.gif b/app0-2017/APP0_senario_12/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/obstacle.gif differ diff --git a/app0-2017/APP0_senario_12/public/maze/obstacle.mp3 b/app0-2017/APP0_senario_12/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/obstacle.mp3 differ diff --git a/app0-2017/APP0_senario_12/public/maze/obstacle.ogg b/app0-2017/APP0_senario_12/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/obstacle.ogg differ diff --git a/app0-2017/APP0_senario_12/public/maze/obstacleIdle.gif b/app0-2017/APP0_senario_12/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/APP0_senario_12/public/maze/small_static_avatar.png b/app0-2017/APP0_senario_12/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/small_static_avatar.png differ diff --git a/app0-2017/APP0_senario_12/public/maze/spies-dead.png b/app0-2017/APP0_senario_12/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/spies-dead.png differ diff --git a/app0-2017/APP0_senario_12/public/maze/start.mp3 b/app0-2017/APP0_senario_12/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/start.mp3 differ diff --git a/app0-2017/APP0_senario_12/public/maze/start.ogg b/app0-2017/APP0_senario_12/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/start.ogg differ diff --git a/app0-2017/APP0_senario_12/public/maze/static_avatar.png b/app0-2017/APP0_senario_12/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/static_avatar.png differ diff --git a/app0-2017/APP0_senario_12/public/maze/testBack.png b/app0-2017/APP0_senario_12/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/testBack.png differ diff --git a/app0-2017/APP0_senario_12/public/maze/testBackPlain.png b/app0-2017/APP0_senario_12/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/testBackPlain.png differ diff --git a/app0-2017/APP0_senario_12/public/maze/tiles.png b/app0-2017/APP0_senario_12/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/tiles.png differ diff --git a/app0-2017/APP0_senario_12/public/maze/wall.mp3 b/app0-2017/APP0_senario_12/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/wall.mp3 differ diff --git a/app0-2017/APP0_senario_12/public/maze/wall.ogg b/app0-2017/APP0_senario_12/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/wall.ogg differ diff --git a/app0-2017/APP0_senario_12/public/maze/wall.png b/app0-2017/APP0_senario_12/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/wall.png differ diff --git a/app0-2017/APP0_senario_12/public/maze/win.mp3 b/app0-2017/APP0_senario_12/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/win.mp3 differ diff --git a/app0-2017/APP0_senario_12/public/maze/win.ogg b/app0-2017/APP0_senario_12/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/win.ogg differ diff --git a/app0-2017/APP0_senario_12/public/maze/win_avatar.png b/app0-2017/APP0_senario_12/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/win_avatar.png differ diff --git a/app0-2017/APP0_senario_12/public/maze/wolf.png b/app0-2017/APP0_senario_12/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/APP0_senario_12/public/maze/wolf.png differ diff --git a/app0-2017/APP0_senario_12/public/maze_config.json b/app0-2017/APP0_senario_12/public/maze_config.json new file mode 100644 index 0000000..d55e7dd --- /dev/null +++ b/app0-2017/APP0_senario_12/public/maze_config.json @@ -0,0 +1,99 @@ +{ + "map":{ + "layout":[ + [[1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 2, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 3], + [1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1]], + + [[1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 2, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 3], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/APP0_senario_12/run b/app0-2017/APP0_senario_12/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/APP0_senario_12/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/APP0_senario_12/student/maze.tpl.py b/app0-2017/APP0_senario_12/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/APP0_senario_12/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/APP0_senario_12/task.yaml b/app0-2017/APP0_senario_12/task.yaml new file mode 100644 index 0000000..f9cece1 --- /dev/null +++ b/app0-2017/APP0_senario_12/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: Scénario 12 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/APP0_senario_13/public/blocks.js b/app0-2017/APP0_senario_13/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/APP0_senario_13/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/APP0_senario_13/public/interpreter.js b/app0-2017/APP0_senario_13/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/APP0_senario_13/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/APP0_senario_13/public/maze.js b/app0-2017/APP0_senario_13/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/APP0_senario_13/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/APP0_senario_13/public/maze/americans.png b/app0-2017/APP0_senario_13/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/americans.png differ diff --git a/app0-2017/APP0_senario_13/public/maze/avatar.png b/app0-2017/APP0_senario_13/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/avatar.png differ diff --git a/app0-2017/APP0_senario_13/public/maze/background.png b/app0-2017/APP0_senario_13/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/background.png differ diff --git a/app0-2017/APP0_senario_13/public/maze/failure.mp3 b/app0-2017/APP0_senario_13/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/failure.mp3 differ diff --git a/app0-2017/APP0_senario_13/public/maze/failure.ogg b/app0-2017/APP0_senario_13/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/failure.ogg differ diff --git a/app0-2017/APP0_senario_13/public/maze/failure_avatar.png b/app0-2017/APP0_senario_13/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/failure_avatar.png differ diff --git a/app0-2017/APP0_senario_13/public/maze/goal.gif b/app0-2017/APP0_senario_13/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/goal.gif differ diff --git a/app0-2017/APP0_senario_13/public/maze/goalIdle.gif b/app0-2017/APP0_senario_13/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/goalIdle.gif differ diff --git a/app0-2017/APP0_senario_13/public/maze/maze_forever.gif b/app0-2017/APP0_senario_13/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/maze_forever.gif differ diff --git a/app0-2017/APP0_senario_13/public/maze/nedstark.png b/app0-2017/APP0_senario_13/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/nedstark.png differ diff --git a/app0-2017/APP0_senario_13/public/maze/obstacle.gif b/app0-2017/APP0_senario_13/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/obstacle.gif differ diff --git a/app0-2017/APP0_senario_13/public/maze/obstacle.mp3 b/app0-2017/APP0_senario_13/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/obstacle.mp3 differ diff --git a/app0-2017/APP0_senario_13/public/maze/obstacle.ogg b/app0-2017/APP0_senario_13/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/obstacle.ogg differ diff --git a/app0-2017/APP0_senario_13/public/maze/obstacleIdle.gif b/app0-2017/APP0_senario_13/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/APP0_senario_13/public/maze/small_static_avatar.png b/app0-2017/APP0_senario_13/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/small_static_avatar.png differ diff --git a/app0-2017/APP0_senario_13/public/maze/spies-dead.png b/app0-2017/APP0_senario_13/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/spies-dead.png differ diff --git a/app0-2017/APP0_senario_13/public/maze/start.mp3 b/app0-2017/APP0_senario_13/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/start.mp3 differ diff --git a/app0-2017/APP0_senario_13/public/maze/start.ogg b/app0-2017/APP0_senario_13/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/start.ogg differ diff --git a/app0-2017/APP0_senario_13/public/maze/static_avatar.png b/app0-2017/APP0_senario_13/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/static_avatar.png differ diff --git a/app0-2017/APP0_senario_13/public/maze/testBack.png b/app0-2017/APP0_senario_13/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/testBack.png differ diff --git a/app0-2017/APP0_senario_13/public/maze/testBackPlain.png b/app0-2017/APP0_senario_13/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/testBackPlain.png differ diff --git a/app0-2017/APP0_senario_13/public/maze/tiles.png b/app0-2017/APP0_senario_13/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/tiles.png differ diff --git a/app0-2017/APP0_senario_13/public/maze/wall.mp3 b/app0-2017/APP0_senario_13/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/wall.mp3 differ diff --git a/app0-2017/APP0_senario_13/public/maze/wall.ogg b/app0-2017/APP0_senario_13/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/wall.ogg differ diff --git a/app0-2017/APP0_senario_13/public/maze/wall.png b/app0-2017/APP0_senario_13/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/wall.png differ diff --git a/app0-2017/APP0_senario_13/public/maze/win.mp3 b/app0-2017/APP0_senario_13/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/win.mp3 differ diff --git a/app0-2017/APP0_senario_13/public/maze/win.ogg b/app0-2017/APP0_senario_13/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/win.ogg differ diff --git a/app0-2017/APP0_senario_13/public/maze/win_avatar.png b/app0-2017/APP0_senario_13/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/win_avatar.png differ diff --git a/app0-2017/APP0_senario_13/public/maze/wolf.png b/app0-2017/APP0_senario_13/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/APP0_senario_13/public/maze/wolf.png differ diff --git a/app0-2017/APP0_senario_13/public/maze_config.json b/app0-2017/APP0_senario_13/public/maze_config.json new file mode 100644 index 0000000..6cdb18f --- /dev/null +++ b/app0-2017/APP0_senario_13/public/maze_config.json @@ -0,0 +1,99 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 0, 1, 4, 1, 1, 1, 1, 4, 1, 1], + [1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1], + [1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 4, 1, 4, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 3], + [1, 1, 4, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 4, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 1, 1, 1, 4, 1, 0, 1, 0, 1, 4, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 4, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 0, 1, 4, 1, 1, 1, 1, 4, 1, 1], + [1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 3], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 4, 1, 4, 1, 0, 1], + [1, 1, 2, 1, 1, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 4, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 4, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 0, 1, 1, 1, 4, 1, 0, 1, 0, 1, 4, 1, 1, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 4, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/APP0_senario_13/run b/app0-2017/APP0_senario_13/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/APP0_senario_13/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/APP0_senario_13/student/maze.tpl.py b/app0-2017/APP0_senario_13/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/APP0_senario_13/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/APP0_senario_13/task.yaml b/app0-2017/APP0_senario_13/task.yaml new file mode 100644 index 0000000..c581581 --- /dev/null +++ b/app0-2017/APP0_senario_13/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: Scénario 13 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/APP0_senario_14/public/blocks.js b/app0-2017/APP0_senario_14/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/APP0_senario_14/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/APP0_senario_14/public/interpreter.js b/app0-2017/APP0_senario_14/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/APP0_senario_14/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/APP0_senario_14/public/maze.js b/app0-2017/APP0_senario_14/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/APP0_senario_14/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/APP0_senario_14/public/maze/americans.png b/app0-2017/APP0_senario_14/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/americans.png differ diff --git a/app0-2017/APP0_senario_14/public/maze/avatar.png b/app0-2017/APP0_senario_14/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/avatar.png differ diff --git a/app0-2017/APP0_senario_14/public/maze/background.png b/app0-2017/APP0_senario_14/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/background.png differ diff --git a/app0-2017/APP0_senario_14/public/maze/failure.mp3 b/app0-2017/APP0_senario_14/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/failure.mp3 differ diff --git a/app0-2017/APP0_senario_14/public/maze/failure.ogg b/app0-2017/APP0_senario_14/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/failure.ogg differ diff --git a/app0-2017/APP0_senario_14/public/maze/failure_avatar.png b/app0-2017/APP0_senario_14/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/failure_avatar.png differ diff --git a/app0-2017/APP0_senario_14/public/maze/goal.gif b/app0-2017/APP0_senario_14/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/goal.gif differ diff --git a/app0-2017/APP0_senario_14/public/maze/goalIdle.gif b/app0-2017/APP0_senario_14/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/goalIdle.gif differ diff --git a/app0-2017/APP0_senario_14/public/maze/maze_forever.gif b/app0-2017/APP0_senario_14/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/maze_forever.gif differ diff --git a/app0-2017/APP0_senario_14/public/maze/nedstark.png b/app0-2017/APP0_senario_14/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/nedstark.png differ diff --git a/app0-2017/APP0_senario_14/public/maze/obstacle.gif b/app0-2017/APP0_senario_14/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/obstacle.gif differ diff --git a/app0-2017/APP0_senario_14/public/maze/obstacle.mp3 b/app0-2017/APP0_senario_14/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/obstacle.mp3 differ diff --git a/app0-2017/APP0_senario_14/public/maze/obstacle.ogg b/app0-2017/APP0_senario_14/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/obstacle.ogg differ diff --git a/app0-2017/APP0_senario_14/public/maze/obstacleIdle.gif b/app0-2017/APP0_senario_14/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/APP0_senario_14/public/maze/small_static_avatar.png b/app0-2017/APP0_senario_14/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/small_static_avatar.png differ diff --git a/app0-2017/APP0_senario_14/public/maze/spies-dead.png b/app0-2017/APP0_senario_14/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/spies-dead.png differ diff --git a/app0-2017/APP0_senario_14/public/maze/start.mp3 b/app0-2017/APP0_senario_14/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/start.mp3 differ diff --git a/app0-2017/APP0_senario_14/public/maze/start.ogg b/app0-2017/APP0_senario_14/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/start.ogg differ diff --git a/app0-2017/APP0_senario_14/public/maze/static_avatar.png b/app0-2017/APP0_senario_14/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/static_avatar.png differ diff --git a/app0-2017/APP0_senario_14/public/maze/testBack.png b/app0-2017/APP0_senario_14/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/testBack.png differ diff --git a/app0-2017/APP0_senario_14/public/maze/testBackPlain.png b/app0-2017/APP0_senario_14/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/testBackPlain.png differ diff --git a/app0-2017/APP0_senario_14/public/maze/tiles.png b/app0-2017/APP0_senario_14/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/tiles.png differ diff --git a/app0-2017/APP0_senario_14/public/maze/wall.mp3 b/app0-2017/APP0_senario_14/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/wall.mp3 differ diff --git a/app0-2017/APP0_senario_14/public/maze/wall.ogg b/app0-2017/APP0_senario_14/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/wall.ogg differ diff --git a/app0-2017/APP0_senario_14/public/maze/wall.png b/app0-2017/APP0_senario_14/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/wall.png differ diff --git a/app0-2017/APP0_senario_14/public/maze/win.mp3 b/app0-2017/APP0_senario_14/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/win.mp3 differ diff --git a/app0-2017/APP0_senario_14/public/maze/win.ogg b/app0-2017/APP0_senario_14/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/win.ogg differ diff --git a/app0-2017/APP0_senario_14/public/maze/win_avatar.png b/app0-2017/APP0_senario_14/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/win_avatar.png differ diff --git a/app0-2017/APP0_senario_14/public/maze/wolf.png b/app0-2017/APP0_senario_14/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/APP0_senario_14/public/maze/wolf.png differ diff --git a/app0-2017/APP0_senario_14/public/maze_config.json b/app0-2017/APP0_senario_14/public/maze_config.json new file mode 100644 index 0000000..0985a37 --- /dev/null +++ b/app0-2017/APP0_senario_14/public/maze_config.json @@ -0,0 +1,99 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 4, 1, 1, 2, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1], + [1, 0, 1, 1, 4, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 4, 1, 1, 1, 1, 4, 1], + [1, 4, 1, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1], + [1, 1, 1, 0, 0, 1, 4, 1, 1, 4, 1, 1, 1, 0, 1, 3], + [1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 0, 1, 1], + [1, 1, 1, 1, 4, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 0, 1, 1, 0, 1, 4, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 4, 1, 1, 2, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1], + [1, 0, 1, 1, 4, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 4, 1, 1, 1, 1, 4, 1], + [1, 4, 1, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1], + [1, 1, 1, 0, 0, 1, 4, 1, 1, 4, 1, 1, 1, 0, 1, 3], + [1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 0, 1, 1], + [1, 1, 1, 1, 4, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 0, 1, 1, 0, 1, 4, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/APP0_senario_14/run b/app0-2017/APP0_senario_14/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/APP0_senario_14/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/APP0_senario_14/student/maze.tpl.py b/app0-2017/APP0_senario_14/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/APP0_senario_14/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/APP0_senario_14/task.yaml b/app0-2017/APP0_senario_14/task.yaml new file mode 100644 index 0000000..97ad395 --- /dev/null +++ b/app0-2017/APP0_senario_14/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: Scénario 14 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/APP0_senario_15/public/blocks.js b/app0-2017/APP0_senario_15/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/APP0_senario_15/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/APP0_senario_15/public/interpreter.js b/app0-2017/APP0_senario_15/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/APP0_senario_15/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/APP0_senario_15/public/maze.js b/app0-2017/APP0_senario_15/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/APP0_senario_15/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/APP0_senario_15/public/maze/americans.png b/app0-2017/APP0_senario_15/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/americans.png differ diff --git a/app0-2017/APP0_senario_15/public/maze/avatar.png b/app0-2017/APP0_senario_15/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/avatar.png differ diff --git a/app0-2017/APP0_senario_15/public/maze/background.png b/app0-2017/APP0_senario_15/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/background.png differ diff --git a/app0-2017/APP0_senario_15/public/maze/failure.mp3 b/app0-2017/APP0_senario_15/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/failure.mp3 differ diff --git a/app0-2017/APP0_senario_15/public/maze/failure.ogg b/app0-2017/APP0_senario_15/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/failure.ogg differ diff --git a/app0-2017/APP0_senario_15/public/maze/failure_avatar.png b/app0-2017/APP0_senario_15/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/failure_avatar.png differ diff --git a/app0-2017/APP0_senario_15/public/maze/goal.gif b/app0-2017/APP0_senario_15/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/goal.gif differ diff --git a/app0-2017/APP0_senario_15/public/maze/goalIdle.gif b/app0-2017/APP0_senario_15/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/goalIdle.gif differ diff --git a/app0-2017/APP0_senario_15/public/maze/maze_forever.gif b/app0-2017/APP0_senario_15/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/maze_forever.gif differ diff --git a/app0-2017/APP0_senario_15/public/maze/nedstark.png b/app0-2017/APP0_senario_15/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/nedstark.png differ diff --git a/app0-2017/APP0_senario_15/public/maze/obstacle.gif b/app0-2017/APP0_senario_15/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/obstacle.gif differ diff --git a/app0-2017/APP0_senario_15/public/maze/obstacle.mp3 b/app0-2017/APP0_senario_15/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/obstacle.mp3 differ diff --git a/app0-2017/APP0_senario_15/public/maze/obstacle.ogg b/app0-2017/APP0_senario_15/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/obstacle.ogg differ diff --git a/app0-2017/APP0_senario_15/public/maze/obstacleIdle.gif b/app0-2017/APP0_senario_15/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/APP0_senario_15/public/maze/small_static_avatar.png b/app0-2017/APP0_senario_15/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/small_static_avatar.png differ diff --git a/app0-2017/APP0_senario_15/public/maze/spies-dead.png b/app0-2017/APP0_senario_15/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/spies-dead.png differ diff --git a/app0-2017/APP0_senario_15/public/maze/start.mp3 b/app0-2017/APP0_senario_15/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/start.mp3 differ diff --git a/app0-2017/APP0_senario_15/public/maze/start.ogg b/app0-2017/APP0_senario_15/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/start.ogg differ diff --git a/app0-2017/APP0_senario_15/public/maze/static_avatar.png b/app0-2017/APP0_senario_15/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/static_avatar.png differ diff --git a/app0-2017/APP0_senario_15/public/maze/testBack.png b/app0-2017/APP0_senario_15/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/testBack.png differ diff --git a/app0-2017/APP0_senario_15/public/maze/testBackPlain.png b/app0-2017/APP0_senario_15/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/testBackPlain.png differ diff --git a/app0-2017/APP0_senario_15/public/maze/tiles.png b/app0-2017/APP0_senario_15/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/tiles.png differ diff --git a/app0-2017/APP0_senario_15/public/maze/wall.mp3 b/app0-2017/APP0_senario_15/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/wall.mp3 differ diff --git a/app0-2017/APP0_senario_15/public/maze/wall.ogg b/app0-2017/APP0_senario_15/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/wall.ogg differ diff --git a/app0-2017/APP0_senario_15/public/maze/wall.png b/app0-2017/APP0_senario_15/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/wall.png differ diff --git a/app0-2017/APP0_senario_15/public/maze/win.mp3 b/app0-2017/APP0_senario_15/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/win.mp3 differ diff --git a/app0-2017/APP0_senario_15/public/maze/win.ogg b/app0-2017/APP0_senario_15/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/win.ogg differ diff --git a/app0-2017/APP0_senario_15/public/maze/win_avatar.png b/app0-2017/APP0_senario_15/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/win_avatar.png differ diff --git a/app0-2017/APP0_senario_15/public/maze/wolf.png b/app0-2017/APP0_senario_15/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/APP0_senario_15/public/maze/wolf.png differ diff --git a/app0-2017/APP0_senario_15/public/maze_config.json b/app0-2017/APP0_senario_15/public/maze_config.json new file mode 100644 index 0000000..972ef89 --- /dev/null +++ b/app0-2017/APP0_senario_15/public/maze_config.json @@ -0,0 +1,99 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 1, 1, 1, 1, 0, 4, 1, 0, 1, 1, 0, 3], + [1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 2, 1, 0, 1, 1], + [1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 2, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 1, 1, 1, 1, 0, 4, 1, 0, 1, 1, 0, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 3], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/APP0_senario_15/run b/app0-2017/APP0_senario_15/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/APP0_senario_15/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/APP0_senario_15/student/maze.tpl.py b/app0-2017/APP0_senario_15/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/APP0_senario_15/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/APP0_senario_15/task.yaml b/app0-2017/APP0_senario_15/task.yaml new file mode 100644 index 0000000..ab8cff4 --- /dev/null +++ b/app0-2017/APP0_senario_15/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: Scénario 15 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/APP0_senario_2/public/blocks.js b/app0-2017/APP0_senario_2/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/APP0_senario_2/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/APP0_senario_2/public/interpreter.js b/app0-2017/APP0_senario_2/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/APP0_senario_2/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/APP0_senario_2/public/maze.js b/app0-2017/APP0_senario_2/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/APP0_senario_2/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/APP0_senario_2/public/maze/americans.png b/app0-2017/APP0_senario_2/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/americans.png differ diff --git a/app0-2017/APP0_senario_2/public/maze/avatar.png b/app0-2017/APP0_senario_2/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/avatar.png differ diff --git a/app0-2017/APP0_senario_2/public/maze/background.png b/app0-2017/APP0_senario_2/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/background.png differ diff --git a/app0-2017/APP0_senario_2/public/maze/failure.mp3 b/app0-2017/APP0_senario_2/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/failure.mp3 differ diff --git a/app0-2017/APP0_senario_2/public/maze/failure.ogg b/app0-2017/APP0_senario_2/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/failure.ogg differ diff --git a/app0-2017/APP0_senario_2/public/maze/failure_avatar.png b/app0-2017/APP0_senario_2/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/failure_avatar.png differ diff --git a/app0-2017/APP0_senario_2/public/maze/goal.gif b/app0-2017/APP0_senario_2/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/goal.gif differ diff --git a/app0-2017/APP0_senario_2/public/maze/goalIdle.gif b/app0-2017/APP0_senario_2/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/goalIdle.gif differ diff --git a/app0-2017/APP0_senario_2/public/maze/maze_forever.gif b/app0-2017/APP0_senario_2/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/maze_forever.gif differ diff --git a/app0-2017/APP0_senario_2/public/maze/nedstark.png b/app0-2017/APP0_senario_2/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/nedstark.png differ diff --git a/app0-2017/APP0_senario_2/public/maze/obstacle.gif b/app0-2017/APP0_senario_2/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/obstacle.gif differ diff --git a/app0-2017/APP0_senario_2/public/maze/obstacle.mp3 b/app0-2017/APP0_senario_2/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/obstacle.mp3 differ diff --git a/app0-2017/APP0_senario_2/public/maze/obstacle.ogg b/app0-2017/APP0_senario_2/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/obstacle.ogg differ diff --git a/app0-2017/APP0_senario_2/public/maze/obstacleIdle.gif b/app0-2017/APP0_senario_2/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/APP0_senario_2/public/maze/small_static_avatar.png b/app0-2017/APP0_senario_2/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/small_static_avatar.png differ diff --git a/app0-2017/APP0_senario_2/public/maze/spies-dead.png b/app0-2017/APP0_senario_2/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/spies-dead.png differ diff --git a/app0-2017/APP0_senario_2/public/maze/start.mp3 b/app0-2017/APP0_senario_2/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/start.mp3 differ diff --git a/app0-2017/APP0_senario_2/public/maze/start.ogg b/app0-2017/APP0_senario_2/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/start.ogg differ diff --git a/app0-2017/APP0_senario_2/public/maze/static_avatar.png b/app0-2017/APP0_senario_2/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/static_avatar.png differ diff --git a/app0-2017/APP0_senario_2/public/maze/testBack.png b/app0-2017/APP0_senario_2/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/testBack.png differ diff --git a/app0-2017/APP0_senario_2/public/maze/testBackPlain.png b/app0-2017/APP0_senario_2/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/testBackPlain.png differ diff --git a/app0-2017/APP0_senario_2/public/maze/tiles.png b/app0-2017/APP0_senario_2/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/tiles.png differ diff --git a/app0-2017/APP0_senario_2/public/maze/wall.mp3 b/app0-2017/APP0_senario_2/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/wall.mp3 differ diff --git a/app0-2017/APP0_senario_2/public/maze/wall.ogg b/app0-2017/APP0_senario_2/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/wall.ogg differ diff --git a/app0-2017/APP0_senario_2/public/maze/wall.png b/app0-2017/APP0_senario_2/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/wall.png differ diff --git a/app0-2017/APP0_senario_2/public/maze/win.mp3 b/app0-2017/APP0_senario_2/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/win.mp3 differ diff --git a/app0-2017/APP0_senario_2/public/maze/win.ogg b/app0-2017/APP0_senario_2/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/win.ogg differ diff --git a/app0-2017/APP0_senario_2/public/maze/win_avatar.png b/app0-2017/APP0_senario_2/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/win_avatar.png differ diff --git a/app0-2017/APP0_senario_2/public/maze/wolf.png b/app0-2017/APP0_senario_2/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/APP0_senario_2/public/maze/wolf.png differ diff --git a/app0-2017/APP0_senario_2/public/maze_config.json b/app0-2017/APP0_senario_2/public/maze_config.json new file mode 100644 index 0000000..5c20728 --- /dev/null +++ b/app0-2017/APP0_senario_2/public/maze_config.json @@ -0,0 +1,99 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [2, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 3], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [2, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 3], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/APP0_senario_2/run b/app0-2017/APP0_senario_2/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/APP0_senario_2/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/APP0_senario_2/student/maze.tpl.py b/app0-2017/APP0_senario_2/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/APP0_senario_2/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/APP0_senario_2/task.yaml b/app0-2017/APP0_senario_2/task.yaml new file mode 100644 index 0000000..9c83fbf --- /dev/null +++ b/app0-2017/APP0_senario_2/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: Scénario 2 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/APP0_senario_3/public/blocks.js b/app0-2017/APP0_senario_3/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/APP0_senario_3/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/APP0_senario_3/public/interpreter.js b/app0-2017/APP0_senario_3/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/APP0_senario_3/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/APP0_senario_3/public/maze.js b/app0-2017/APP0_senario_3/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/APP0_senario_3/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/APP0_senario_3/public/maze/americans.png b/app0-2017/APP0_senario_3/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/americans.png differ diff --git a/app0-2017/APP0_senario_3/public/maze/avatar.png b/app0-2017/APP0_senario_3/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/avatar.png differ diff --git a/app0-2017/APP0_senario_3/public/maze/background.png b/app0-2017/APP0_senario_3/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/background.png differ diff --git a/app0-2017/APP0_senario_3/public/maze/failure.mp3 b/app0-2017/APP0_senario_3/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/failure.mp3 differ diff --git a/app0-2017/APP0_senario_3/public/maze/failure.ogg b/app0-2017/APP0_senario_3/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/failure.ogg differ diff --git a/app0-2017/APP0_senario_3/public/maze/failure_avatar.png b/app0-2017/APP0_senario_3/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/failure_avatar.png differ diff --git a/app0-2017/APP0_senario_3/public/maze/goal.gif b/app0-2017/APP0_senario_3/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/goal.gif differ diff --git a/app0-2017/APP0_senario_3/public/maze/goalIdle.gif b/app0-2017/APP0_senario_3/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/goalIdle.gif differ diff --git a/app0-2017/APP0_senario_3/public/maze/maze_forever.gif b/app0-2017/APP0_senario_3/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/maze_forever.gif differ diff --git a/app0-2017/APP0_senario_3/public/maze/nedstark.png b/app0-2017/APP0_senario_3/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/nedstark.png differ diff --git a/app0-2017/APP0_senario_3/public/maze/obstacle.gif b/app0-2017/APP0_senario_3/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/obstacle.gif differ diff --git a/app0-2017/APP0_senario_3/public/maze/obstacle.mp3 b/app0-2017/APP0_senario_3/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/obstacle.mp3 differ diff --git a/app0-2017/APP0_senario_3/public/maze/obstacle.ogg b/app0-2017/APP0_senario_3/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/obstacle.ogg differ diff --git a/app0-2017/APP0_senario_3/public/maze/obstacleIdle.gif b/app0-2017/APP0_senario_3/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/APP0_senario_3/public/maze/small_static_avatar.png b/app0-2017/APP0_senario_3/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/small_static_avatar.png differ diff --git a/app0-2017/APP0_senario_3/public/maze/spies-dead.png b/app0-2017/APP0_senario_3/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/spies-dead.png differ diff --git a/app0-2017/APP0_senario_3/public/maze/start.mp3 b/app0-2017/APP0_senario_3/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/start.mp3 differ diff --git a/app0-2017/APP0_senario_3/public/maze/start.ogg b/app0-2017/APP0_senario_3/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/start.ogg differ diff --git a/app0-2017/APP0_senario_3/public/maze/static_avatar.png b/app0-2017/APP0_senario_3/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/static_avatar.png differ diff --git a/app0-2017/APP0_senario_3/public/maze/testBack.png b/app0-2017/APP0_senario_3/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/testBack.png differ diff --git a/app0-2017/APP0_senario_3/public/maze/testBackPlain.png b/app0-2017/APP0_senario_3/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/testBackPlain.png differ diff --git a/app0-2017/APP0_senario_3/public/maze/tiles.png b/app0-2017/APP0_senario_3/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/tiles.png differ diff --git a/app0-2017/APP0_senario_3/public/maze/wall.mp3 b/app0-2017/APP0_senario_3/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/wall.mp3 differ diff --git a/app0-2017/APP0_senario_3/public/maze/wall.ogg b/app0-2017/APP0_senario_3/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/wall.ogg differ diff --git a/app0-2017/APP0_senario_3/public/maze/wall.png b/app0-2017/APP0_senario_3/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/wall.png differ diff --git a/app0-2017/APP0_senario_3/public/maze/win.mp3 b/app0-2017/APP0_senario_3/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/win.mp3 differ diff --git a/app0-2017/APP0_senario_3/public/maze/win.ogg b/app0-2017/APP0_senario_3/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/win.ogg differ diff --git a/app0-2017/APP0_senario_3/public/maze/win_avatar.png b/app0-2017/APP0_senario_3/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/win_avatar.png differ diff --git a/app0-2017/APP0_senario_3/public/maze/wolf.png b/app0-2017/APP0_senario_3/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/APP0_senario_3/public/maze/wolf.png differ diff --git a/app0-2017/APP0_senario_3/public/maze_config.json b/app0-2017/APP0_senario_3/public/maze_config.json new file mode 100644 index 0000000..cfce9d5 --- /dev/null +++ b/app0-2017/APP0_senario_3/public/maze_config.json @@ -0,0 +1,99 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 4, 1, 1, 1, 1, 0, 1, 4, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 4, 0, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 4, 4, 1, 1, 1, 0, 1, 1], + [1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 3], + [1, 1, 1, 0, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1], + [1, 0, 1, 2, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 4, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 4, 1, 1, 1, 1, 0, 1, 4, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 4, 0, 1, 1], + [1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 4, 4, 1, 1, 1, 0, 1, 1], + [1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 3], + [1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 4, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/APP0_senario_3/run b/app0-2017/APP0_senario_3/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/APP0_senario_3/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/APP0_senario_3/student/maze.tpl.py b/app0-2017/APP0_senario_3/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/APP0_senario_3/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/APP0_senario_3/task.yaml b/app0-2017/APP0_senario_3/task.yaml new file mode 100644 index 0000000..85c1c8e --- /dev/null +++ b/app0-2017/APP0_senario_3/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: Scénario 3 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/APP0_senario_4/public/blocks.js b/app0-2017/APP0_senario_4/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/APP0_senario_4/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/APP0_senario_4/public/interpreter.js b/app0-2017/APP0_senario_4/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/APP0_senario_4/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/APP0_senario_4/public/maze.js b/app0-2017/APP0_senario_4/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/APP0_senario_4/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/APP0_senario_4/public/maze/americans.png b/app0-2017/APP0_senario_4/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/americans.png differ diff --git a/app0-2017/APP0_senario_4/public/maze/avatar.png b/app0-2017/APP0_senario_4/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/avatar.png differ diff --git a/app0-2017/APP0_senario_4/public/maze/background.png b/app0-2017/APP0_senario_4/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/background.png differ diff --git a/app0-2017/APP0_senario_4/public/maze/failure.mp3 b/app0-2017/APP0_senario_4/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/failure.mp3 differ diff --git a/app0-2017/APP0_senario_4/public/maze/failure.ogg b/app0-2017/APP0_senario_4/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/failure.ogg differ diff --git a/app0-2017/APP0_senario_4/public/maze/failure_avatar.png b/app0-2017/APP0_senario_4/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/failure_avatar.png differ diff --git a/app0-2017/APP0_senario_4/public/maze/goal.gif b/app0-2017/APP0_senario_4/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/goal.gif differ diff --git a/app0-2017/APP0_senario_4/public/maze/goalIdle.gif b/app0-2017/APP0_senario_4/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/goalIdle.gif differ diff --git a/app0-2017/APP0_senario_4/public/maze/maze_forever.gif b/app0-2017/APP0_senario_4/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/maze_forever.gif differ diff --git a/app0-2017/APP0_senario_4/public/maze/nedstark.png b/app0-2017/APP0_senario_4/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/nedstark.png differ diff --git a/app0-2017/APP0_senario_4/public/maze/obstacle.gif b/app0-2017/APP0_senario_4/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/obstacle.gif differ diff --git a/app0-2017/APP0_senario_4/public/maze/obstacle.mp3 b/app0-2017/APP0_senario_4/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/obstacle.mp3 differ diff --git a/app0-2017/APP0_senario_4/public/maze/obstacle.ogg b/app0-2017/APP0_senario_4/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/obstacle.ogg differ diff --git a/app0-2017/APP0_senario_4/public/maze/obstacleIdle.gif b/app0-2017/APP0_senario_4/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/APP0_senario_4/public/maze/small_static_avatar.png b/app0-2017/APP0_senario_4/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/small_static_avatar.png differ diff --git a/app0-2017/APP0_senario_4/public/maze/spies-dead.png b/app0-2017/APP0_senario_4/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/spies-dead.png differ diff --git a/app0-2017/APP0_senario_4/public/maze/start.mp3 b/app0-2017/APP0_senario_4/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/start.mp3 differ diff --git a/app0-2017/APP0_senario_4/public/maze/start.ogg b/app0-2017/APP0_senario_4/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/start.ogg differ diff --git a/app0-2017/APP0_senario_4/public/maze/static_avatar.png b/app0-2017/APP0_senario_4/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/static_avatar.png differ diff --git a/app0-2017/APP0_senario_4/public/maze/testBack.png b/app0-2017/APP0_senario_4/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/testBack.png differ diff --git a/app0-2017/APP0_senario_4/public/maze/testBackPlain.png b/app0-2017/APP0_senario_4/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/testBackPlain.png differ diff --git a/app0-2017/APP0_senario_4/public/maze/tiles.png b/app0-2017/APP0_senario_4/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/tiles.png differ diff --git a/app0-2017/APP0_senario_4/public/maze/wall.mp3 b/app0-2017/APP0_senario_4/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/wall.mp3 differ diff --git a/app0-2017/APP0_senario_4/public/maze/wall.ogg b/app0-2017/APP0_senario_4/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/wall.ogg differ diff --git a/app0-2017/APP0_senario_4/public/maze/wall.png b/app0-2017/APP0_senario_4/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/wall.png differ diff --git a/app0-2017/APP0_senario_4/public/maze/win.mp3 b/app0-2017/APP0_senario_4/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/win.mp3 differ diff --git a/app0-2017/APP0_senario_4/public/maze/win.ogg b/app0-2017/APP0_senario_4/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/win.ogg differ diff --git a/app0-2017/APP0_senario_4/public/maze/win_avatar.png b/app0-2017/APP0_senario_4/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/win_avatar.png differ diff --git a/app0-2017/APP0_senario_4/public/maze/wolf.png b/app0-2017/APP0_senario_4/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/APP0_senario_4/public/maze/wolf.png differ diff --git a/app0-2017/APP0_senario_4/public/maze_config.json b/app0-2017/APP0_senario_4/public/maze_config.json new file mode 100644 index 0000000..87256ed --- /dev/null +++ b/app0-2017/APP0_senario_4/public/maze_config.json @@ -0,0 +1,99 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 4, 1, 1], + [1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 3], + [1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1], + [1, 1, 2, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 4, 1, 1], + [1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 4, 1, 1, 3], + [1, 4, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1], + [1, 1, 2, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/APP0_senario_4/run b/app0-2017/APP0_senario_4/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/APP0_senario_4/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/APP0_senario_4/student/maze.tpl.py b/app0-2017/APP0_senario_4/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/APP0_senario_4/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/APP0_senario_4/task.yaml b/app0-2017/APP0_senario_4/task.yaml new file mode 100644 index 0000000..f0428a2 --- /dev/null +++ b/app0-2017/APP0_senario_4/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: Scénario 4 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/APP0_senario_5/public/blocks.js b/app0-2017/APP0_senario_5/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/APP0_senario_5/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/APP0_senario_5/public/interpreter.js b/app0-2017/APP0_senario_5/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/APP0_senario_5/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/APP0_senario_5/public/maze.js b/app0-2017/APP0_senario_5/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/APP0_senario_5/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/APP0_senario_5/public/maze/americans.png b/app0-2017/APP0_senario_5/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/americans.png differ diff --git a/app0-2017/APP0_senario_5/public/maze/avatar.png b/app0-2017/APP0_senario_5/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/avatar.png differ diff --git a/app0-2017/APP0_senario_5/public/maze/background.png b/app0-2017/APP0_senario_5/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/background.png differ diff --git a/app0-2017/APP0_senario_5/public/maze/failure.mp3 b/app0-2017/APP0_senario_5/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/failure.mp3 differ diff --git a/app0-2017/APP0_senario_5/public/maze/failure.ogg b/app0-2017/APP0_senario_5/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/failure.ogg differ diff --git a/app0-2017/APP0_senario_5/public/maze/failure_avatar.png b/app0-2017/APP0_senario_5/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/failure_avatar.png differ diff --git a/app0-2017/APP0_senario_5/public/maze/goal.gif b/app0-2017/APP0_senario_5/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/goal.gif differ diff --git a/app0-2017/APP0_senario_5/public/maze/goalIdle.gif b/app0-2017/APP0_senario_5/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/goalIdle.gif differ diff --git a/app0-2017/APP0_senario_5/public/maze/maze_forever.gif b/app0-2017/APP0_senario_5/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/maze_forever.gif differ diff --git a/app0-2017/APP0_senario_5/public/maze/nedstark.png b/app0-2017/APP0_senario_5/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/nedstark.png differ diff --git a/app0-2017/APP0_senario_5/public/maze/obstacle.gif b/app0-2017/APP0_senario_5/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/obstacle.gif differ diff --git a/app0-2017/APP0_senario_5/public/maze/obstacle.mp3 b/app0-2017/APP0_senario_5/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/obstacle.mp3 differ diff --git a/app0-2017/APP0_senario_5/public/maze/obstacle.ogg b/app0-2017/APP0_senario_5/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/obstacle.ogg differ diff --git a/app0-2017/APP0_senario_5/public/maze/obstacleIdle.gif b/app0-2017/APP0_senario_5/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/APP0_senario_5/public/maze/small_static_avatar.png b/app0-2017/APP0_senario_5/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/small_static_avatar.png differ diff --git a/app0-2017/APP0_senario_5/public/maze/spies-dead.png b/app0-2017/APP0_senario_5/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/spies-dead.png differ diff --git a/app0-2017/APP0_senario_5/public/maze/start.mp3 b/app0-2017/APP0_senario_5/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/start.mp3 differ diff --git a/app0-2017/APP0_senario_5/public/maze/start.ogg b/app0-2017/APP0_senario_5/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/start.ogg differ diff --git a/app0-2017/APP0_senario_5/public/maze/static_avatar.png b/app0-2017/APP0_senario_5/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/static_avatar.png differ diff --git a/app0-2017/APP0_senario_5/public/maze/testBack.png b/app0-2017/APP0_senario_5/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/testBack.png differ diff --git a/app0-2017/APP0_senario_5/public/maze/testBackPlain.png b/app0-2017/APP0_senario_5/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/testBackPlain.png differ diff --git a/app0-2017/APP0_senario_5/public/maze/tiles.png b/app0-2017/APP0_senario_5/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/tiles.png differ diff --git a/app0-2017/APP0_senario_5/public/maze/wall.mp3 b/app0-2017/APP0_senario_5/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/wall.mp3 differ diff --git a/app0-2017/APP0_senario_5/public/maze/wall.ogg b/app0-2017/APP0_senario_5/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/wall.ogg differ diff --git a/app0-2017/APP0_senario_5/public/maze/wall.png b/app0-2017/APP0_senario_5/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/wall.png differ diff --git a/app0-2017/APP0_senario_5/public/maze/win.mp3 b/app0-2017/APP0_senario_5/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/win.mp3 differ diff --git a/app0-2017/APP0_senario_5/public/maze/win.ogg b/app0-2017/APP0_senario_5/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/win.ogg differ diff --git a/app0-2017/APP0_senario_5/public/maze/win_avatar.png b/app0-2017/APP0_senario_5/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/win_avatar.png differ diff --git a/app0-2017/APP0_senario_5/public/maze/wolf.png b/app0-2017/APP0_senario_5/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/APP0_senario_5/public/maze/wolf.png differ diff --git a/app0-2017/APP0_senario_5/public/maze_config.json b/app0-2017/APP0_senario_5/public/maze_config.json new file mode 100644 index 0000000..9ca5e3e --- /dev/null +++ b/app0-2017/APP0_senario_5/public/maze_config.json @@ -0,0 +1,99 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 4, 1, 1, 4, 1, 1, 0, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 4, 1, 1, 1, 1], + [1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3], + [1, 0, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 4, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 4, 1, 1, 4, 1, 1, 0, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 3], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 4, 1, 1, 1, 1], + [1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 4, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/APP0_senario_5/run b/app0-2017/APP0_senario_5/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/APP0_senario_5/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/APP0_senario_5/student/maze.tpl.py b/app0-2017/APP0_senario_5/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/APP0_senario_5/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/APP0_senario_5/task.yaml b/app0-2017/APP0_senario_5/task.yaml new file mode 100644 index 0000000..9610008 --- /dev/null +++ b/app0-2017/APP0_senario_5/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: Scénario 5 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/APP0_senario_6/public/blocks.js b/app0-2017/APP0_senario_6/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/APP0_senario_6/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/APP0_senario_6/public/interpreter.js b/app0-2017/APP0_senario_6/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/APP0_senario_6/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/APP0_senario_6/public/maze.js b/app0-2017/APP0_senario_6/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/APP0_senario_6/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/APP0_senario_6/public/maze/americans.png b/app0-2017/APP0_senario_6/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/americans.png differ diff --git a/app0-2017/APP0_senario_6/public/maze/avatar.png b/app0-2017/APP0_senario_6/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/avatar.png differ diff --git a/app0-2017/APP0_senario_6/public/maze/background.png b/app0-2017/APP0_senario_6/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/background.png differ diff --git a/app0-2017/APP0_senario_6/public/maze/failure.mp3 b/app0-2017/APP0_senario_6/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/failure.mp3 differ diff --git a/app0-2017/APP0_senario_6/public/maze/failure.ogg b/app0-2017/APP0_senario_6/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/failure.ogg differ diff --git a/app0-2017/APP0_senario_6/public/maze/failure_avatar.png b/app0-2017/APP0_senario_6/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/failure_avatar.png differ diff --git a/app0-2017/APP0_senario_6/public/maze/goal.gif b/app0-2017/APP0_senario_6/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/goal.gif differ diff --git a/app0-2017/APP0_senario_6/public/maze/goalIdle.gif b/app0-2017/APP0_senario_6/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/goalIdle.gif differ diff --git a/app0-2017/APP0_senario_6/public/maze/maze_forever.gif b/app0-2017/APP0_senario_6/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/maze_forever.gif differ diff --git a/app0-2017/APP0_senario_6/public/maze/nedstark.png b/app0-2017/APP0_senario_6/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/nedstark.png differ diff --git a/app0-2017/APP0_senario_6/public/maze/obstacle.gif b/app0-2017/APP0_senario_6/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/obstacle.gif differ diff --git a/app0-2017/APP0_senario_6/public/maze/obstacle.mp3 b/app0-2017/APP0_senario_6/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/obstacle.mp3 differ diff --git a/app0-2017/APP0_senario_6/public/maze/obstacle.ogg b/app0-2017/APP0_senario_6/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/obstacle.ogg differ diff --git a/app0-2017/APP0_senario_6/public/maze/obstacleIdle.gif b/app0-2017/APP0_senario_6/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/APP0_senario_6/public/maze/small_static_avatar.png b/app0-2017/APP0_senario_6/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/small_static_avatar.png differ diff --git a/app0-2017/APP0_senario_6/public/maze/spies-dead.png b/app0-2017/APP0_senario_6/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/spies-dead.png differ diff --git a/app0-2017/APP0_senario_6/public/maze/start.mp3 b/app0-2017/APP0_senario_6/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/start.mp3 differ diff --git a/app0-2017/APP0_senario_6/public/maze/start.ogg b/app0-2017/APP0_senario_6/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/start.ogg differ diff --git a/app0-2017/APP0_senario_6/public/maze/static_avatar.png b/app0-2017/APP0_senario_6/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/static_avatar.png differ diff --git a/app0-2017/APP0_senario_6/public/maze/testBack.png b/app0-2017/APP0_senario_6/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/testBack.png differ diff --git a/app0-2017/APP0_senario_6/public/maze/testBackPlain.png b/app0-2017/APP0_senario_6/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/testBackPlain.png differ diff --git a/app0-2017/APP0_senario_6/public/maze/tiles.png b/app0-2017/APP0_senario_6/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/tiles.png differ diff --git a/app0-2017/APP0_senario_6/public/maze/wall.mp3 b/app0-2017/APP0_senario_6/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/wall.mp3 differ diff --git a/app0-2017/APP0_senario_6/public/maze/wall.ogg b/app0-2017/APP0_senario_6/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/wall.ogg differ diff --git a/app0-2017/APP0_senario_6/public/maze/wall.png b/app0-2017/APP0_senario_6/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/wall.png differ diff --git a/app0-2017/APP0_senario_6/public/maze/win.mp3 b/app0-2017/APP0_senario_6/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/win.mp3 differ diff --git a/app0-2017/APP0_senario_6/public/maze/win.ogg b/app0-2017/APP0_senario_6/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/win.ogg differ diff --git a/app0-2017/APP0_senario_6/public/maze/win_avatar.png b/app0-2017/APP0_senario_6/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/win_avatar.png differ diff --git a/app0-2017/APP0_senario_6/public/maze/wolf.png b/app0-2017/APP0_senario_6/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/APP0_senario_6/public/maze/wolf.png differ diff --git a/app0-2017/APP0_senario_6/public/maze_config.json b/app0-2017/APP0_senario_6/public/maze_config.json new file mode 100644 index 0000000..c71ee02 --- /dev/null +++ b/app0-2017/APP0_senario_6/public/maze_config.json @@ -0,0 +1,99 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 2, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 4, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 1, 1, 4, 1, 1, 1, 1, 0, 1, 0, 1], + [1, 1, 0, 1, 0, 1, 1, 4, 1, 1, 1, 1, 0, 1, 1, 3], + [1, 1, 0, 0, 0, 1, 1, 4, 1, 1, 0, 0, 0, 1, 4, 1], + [1, 1, 0, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 4, 4, 4, 1, 1, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 0, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 4, 1, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 1, 1, 4, 1, 1, 1, 1, 0, 1, 0, 1], + [1, 1, 0, 1, 0, 1, 1, 4, 1, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 0, 0, 0, 1, 1, 4, 1, 1, 0, 0, 0, 1, 4, 1], + [1, 1, 0, 1, 1, 1, 1, 4, 1, 2, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 4, 4, 4, 1, 1, 0, 1, 1, 3], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 0, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/APP0_senario_6/run b/app0-2017/APP0_senario_6/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/APP0_senario_6/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/APP0_senario_6/student/maze.tpl.py b/app0-2017/APP0_senario_6/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/APP0_senario_6/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/APP0_senario_6/task.yaml b/app0-2017/APP0_senario_6/task.yaml new file mode 100644 index 0000000..15dab34 --- /dev/null +++ b/app0-2017/APP0_senario_6/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: Scénario 6 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/APP0_senario_7/public/blocks.js b/app0-2017/APP0_senario_7/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/APP0_senario_7/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/APP0_senario_7/public/interpreter.js b/app0-2017/APP0_senario_7/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/APP0_senario_7/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/APP0_senario_7/public/maze.js b/app0-2017/APP0_senario_7/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/APP0_senario_7/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/APP0_senario_7/public/maze/americans.png b/app0-2017/APP0_senario_7/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/americans.png differ diff --git a/app0-2017/APP0_senario_7/public/maze/avatar.png b/app0-2017/APP0_senario_7/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/avatar.png differ diff --git a/app0-2017/APP0_senario_7/public/maze/background.png b/app0-2017/APP0_senario_7/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/background.png differ diff --git a/app0-2017/APP0_senario_7/public/maze/failure.mp3 b/app0-2017/APP0_senario_7/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/failure.mp3 differ diff --git a/app0-2017/APP0_senario_7/public/maze/failure.ogg b/app0-2017/APP0_senario_7/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/failure.ogg differ diff --git a/app0-2017/APP0_senario_7/public/maze/failure_avatar.png b/app0-2017/APP0_senario_7/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/failure_avatar.png differ diff --git a/app0-2017/APP0_senario_7/public/maze/goal.gif b/app0-2017/APP0_senario_7/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/goal.gif differ diff --git a/app0-2017/APP0_senario_7/public/maze/goalIdle.gif b/app0-2017/APP0_senario_7/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/goalIdle.gif differ diff --git a/app0-2017/APP0_senario_7/public/maze/maze_forever.gif b/app0-2017/APP0_senario_7/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/maze_forever.gif differ diff --git a/app0-2017/APP0_senario_7/public/maze/nedstark.png b/app0-2017/APP0_senario_7/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/nedstark.png differ diff --git a/app0-2017/APP0_senario_7/public/maze/obstacle.gif b/app0-2017/APP0_senario_7/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/obstacle.gif differ diff --git a/app0-2017/APP0_senario_7/public/maze/obstacle.mp3 b/app0-2017/APP0_senario_7/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/obstacle.mp3 differ diff --git a/app0-2017/APP0_senario_7/public/maze/obstacle.ogg b/app0-2017/APP0_senario_7/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/obstacle.ogg differ diff --git a/app0-2017/APP0_senario_7/public/maze/obstacleIdle.gif b/app0-2017/APP0_senario_7/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/APP0_senario_7/public/maze/small_static_avatar.png b/app0-2017/APP0_senario_7/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/small_static_avatar.png differ diff --git a/app0-2017/APP0_senario_7/public/maze/spies-dead.png b/app0-2017/APP0_senario_7/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/spies-dead.png differ diff --git a/app0-2017/APP0_senario_7/public/maze/start.mp3 b/app0-2017/APP0_senario_7/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/start.mp3 differ diff --git a/app0-2017/APP0_senario_7/public/maze/start.ogg b/app0-2017/APP0_senario_7/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/start.ogg differ diff --git a/app0-2017/APP0_senario_7/public/maze/static_avatar.png b/app0-2017/APP0_senario_7/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/static_avatar.png differ diff --git a/app0-2017/APP0_senario_7/public/maze/testBack.png b/app0-2017/APP0_senario_7/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/testBack.png differ diff --git a/app0-2017/APP0_senario_7/public/maze/testBackPlain.png b/app0-2017/APP0_senario_7/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/testBackPlain.png differ diff --git a/app0-2017/APP0_senario_7/public/maze/tiles.png b/app0-2017/APP0_senario_7/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/tiles.png differ diff --git a/app0-2017/APP0_senario_7/public/maze/wall.mp3 b/app0-2017/APP0_senario_7/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/wall.mp3 differ diff --git a/app0-2017/APP0_senario_7/public/maze/wall.ogg b/app0-2017/APP0_senario_7/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/wall.ogg differ diff --git a/app0-2017/APP0_senario_7/public/maze/wall.png b/app0-2017/APP0_senario_7/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/wall.png differ diff --git a/app0-2017/APP0_senario_7/public/maze/win.mp3 b/app0-2017/APP0_senario_7/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/win.mp3 differ diff --git a/app0-2017/APP0_senario_7/public/maze/win.ogg b/app0-2017/APP0_senario_7/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/win.ogg differ diff --git a/app0-2017/APP0_senario_7/public/maze/win_avatar.png b/app0-2017/APP0_senario_7/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/win_avatar.png differ diff --git a/app0-2017/APP0_senario_7/public/maze/wolf.png b/app0-2017/APP0_senario_7/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/APP0_senario_7/public/maze/wolf.png differ diff --git a/app0-2017/APP0_senario_7/public/maze_config.json b/app0-2017/APP0_senario_7/public/maze_config.json new file mode 100644 index 0000000..9c3aaf4 --- /dev/null +++ b/app0-2017/APP0_senario_7/public/maze_config.json @@ -0,0 +1,99 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 3], + [1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 2, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/APP0_senario_7/run b/app0-2017/APP0_senario_7/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/APP0_senario_7/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/APP0_senario_7/student/maze.tpl.py b/app0-2017/APP0_senario_7/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/APP0_senario_7/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/APP0_senario_7/task.yaml b/app0-2017/APP0_senario_7/task.yaml new file mode 100644 index 0000000..67af90b --- /dev/null +++ b/app0-2017/APP0_senario_7/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: Scénario 7 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/APP0_senario_8/public/blocks.js b/app0-2017/APP0_senario_8/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/APP0_senario_8/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/APP0_senario_8/public/interpreter.js b/app0-2017/APP0_senario_8/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/APP0_senario_8/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/APP0_senario_8/public/maze.js b/app0-2017/APP0_senario_8/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/APP0_senario_8/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/APP0_senario_8/public/maze/americans.png b/app0-2017/APP0_senario_8/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/americans.png differ diff --git a/app0-2017/APP0_senario_8/public/maze/avatar.png b/app0-2017/APP0_senario_8/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/avatar.png differ diff --git a/app0-2017/APP0_senario_8/public/maze/background.png b/app0-2017/APP0_senario_8/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/background.png differ diff --git a/app0-2017/APP0_senario_8/public/maze/failure.mp3 b/app0-2017/APP0_senario_8/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/failure.mp3 differ diff --git a/app0-2017/APP0_senario_8/public/maze/failure.ogg b/app0-2017/APP0_senario_8/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/failure.ogg differ diff --git a/app0-2017/APP0_senario_8/public/maze/failure_avatar.png b/app0-2017/APP0_senario_8/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/failure_avatar.png differ diff --git a/app0-2017/APP0_senario_8/public/maze/goal.gif b/app0-2017/APP0_senario_8/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/goal.gif differ diff --git a/app0-2017/APP0_senario_8/public/maze/goalIdle.gif b/app0-2017/APP0_senario_8/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/goalIdle.gif differ diff --git a/app0-2017/APP0_senario_8/public/maze/maze_forever.gif b/app0-2017/APP0_senario_8/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/maze_forever.gif differ diff --git a/app0-2017/APP0_senario_8/public/maze/nedstark.png b/app0-2017/APP0_senario_8/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/nedstark.png differ diff --git a/app0-2017/APP0_senario_8/public/maze/obstacle.gif b/app0-2017/APP0_senario_8/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/obstacle.gif differ diff --git a/app0-2017/APP0_senario_8/public/maze/obstacle.mp3 b/app0-2017/APP0_senario_8/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/obstacle.mp3 differ diff --git a/app0-2017/APP0_senario_8/public/maze/obstacle.ogg b/app0-2017/APP0_senario_8/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/obstacle.ogg differ diff --git a/app0-2017/APP0_senario_8/public/maze/obstacleIdle.gif b/app0-2017/APP0_senario_8/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/APP0_senario_8/public/maze/small_static_avatar.png b/app0-2017/APP0_senario_8/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/small_static_avatar.png differ diff --git a/app0-2017/APP0_senario_8/public/maze/spies-dead.png b/app0-2017/APP0_senario_8/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/spies-dead.png differ diff --git a/app0-2017/APP0_senario_8/public/maze/start.mp3 b/app0-2017/APP0_senario_8/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/start.mp3 differ diff --git a/app0-2017/APP0_senario_8/public/maze/start.ogg b/app0-2017/APP0_senario_8/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/start.ogg differ diff --git a/app0-2017/APP0_senario_8/public/maze/static_avatar.png b/app0-2017/APP0_senario_8/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/static_avatar.png differ diff --git a/app0-2017/APP0_senario_8/public/maze/testBack.png b/app0-2017/APP0_senario_8/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/testBack.png differ diff --git a/app0-2017/APP0_senario_8/public/maze/testBackPlain.png b/app0-2017/APP0_senario_8/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/testBackPlain.png differ diff --git a/app0-2017/APP0_senario_8/public/maze/tiles.png b/app0-2017/APP0_senario_8/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/tiles.png differ diff --git a/app0-2017/APP0_senario_8/public/maze/wall.mp3 b/app0-2017/APP0_senario_8/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/wall.mp3 differ diff --git a/app0-2017/APP0_senario_8/public/maze/wall.ogg b/app0-2017/APP0_senario_8/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/wall.ogg differ diff --git a/app0-2017/APP0_senario_8/public/maze/wall.png b/app0-2017/APP0_senario_8/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/wall.png differ diff --git a/app0-2017/APP0_senario_8/public/maze/win.mp3 b/app0-2017/APP0_senario_8/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/win.mp3 differ diff --git a/app0-2017/APP0_senario_8/public/maze/win.ogg b/app0-2017/APP0_senario_8/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/win.ogg differ diff --git a/app0-2017/APP0_senario_8/public/maze/win_avatar.png b/app0-2017/APP0_senario_8/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/win_avatar.png differ diff --git a/app0-2017/APP0_senario_8/public/maze/wolf.png b/app0-2017/APP0_senario_8/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/APP0_senario_8/public/maze/wolf.png differ diff --git a/app0-2017/APP0_senario_8/public/maze_config.json b/app0-2017/APP0_senario_8/public/maze_config.json new file mode 100644 index 0000000..f26f711 --- /dev/null +++ b/app0-2017/APP0_senario_8/public/maze_config.json @@ -0,0 +1,99 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 3], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1], + [2, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [2, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1], + [1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1], + [1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 3], + [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/APP0_senario_8/run b/app0-2017/APP0_senario_8/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/APP0_senario_8/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/APP0_senario_8/student/maze.tpl.py b/app0-2017/APP0_senario_8/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/APP0_senario_8/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/APP0_senario_8/task.yaml b/app0-2017/APP0_senario_8/task.yaml new file mode 100644 index 0000000..9be4d80 --- /dev/null +++ b/app0-2017/APP0_senario_8/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: Scénario 8 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/APP0_senario_9/public/blocks.js b/app0-2017/APP0_senario_9/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/APP0_senario_9/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/APP0_senario_9/public/interpreter.js b/app0-2017/APP0_senario_9/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/APP0_senario_9/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/APP0_senario_9/public/maze.js b/app0-2017/APP0_senario_9/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/APP0_senario_9/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/APP0_senario_9/public/maze/americans.png b/app0-2017/APP0_senario_9/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/americans.png differ diff --git a/app0-2017/APP0_senario_9/public/maze/avatar.png b/app0-2017/APP0_senario_9/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/avatar.png differ diff --git a/app0-2017/APP0_senario_9/public/maze/background.png b/app0-2017/APP0_senario_9/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/background.png differ diff --git a/app0-2017/APP0_senario_9/public/maze/failure.mp3 b/app0-2017/APP0_senario_9/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/failure.mp3 differ diff --git a/app0-2017/APP0_senario_9/public/maze/failure.ogg b/app0-2017/APP0_senario_9/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/failure.ogg differ diff --git a/app0-2017/APP0_senario_9/public/maze/failure_avatar.png b/app0-2017/APP0_senario_9/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/failure_avatar.png differ diff --git a/app0-2017/APP0_senario_9/public/maze/goal.gif b/app0-2017/APP0_senario_9/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/goal.gif differ diff --git a/app0-2017/APP0_senario_9/public/maze/goalIdle.gif b/app0-2017/APP0_senario_9/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/goalIdle.gif differ diff --git a/app0-2017/APP0_senario_9/public/maze/maze_forever.gif b/app0-2017/APP0_senario_9/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/maze_forever.gif differ diff --git a/app0-2017/APP0_senario_9/public/maze/nedstark.png b/app0-2017/APP0_senario_9/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/nedstark.png differ diff --git a/app0-2017/APP0_senario_9/public/maze/obstacle.gif b/app0-2017/APP0_senario_9/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/obstacle.gif differ diff --git a/app0-2017/APP0_senario_9/public/maze/obstacle.mp3 b/app0-2017/APP0_senario_9/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/obstacle.mp3 differ diff --git a/app0-2017/APP0_senario_9/public/maze/obstacle.ogg b/app0-2017/APP0_senario_9/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/obstacle.ogg differ diff --git a/app0-2017/APP0_senario_9/public/maze/obstacleIdle.gif b/app0-2017/APP0_senario_9/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/APP0_senario_9/public/maze/small_static_avatar.png b/app0-2017/APP0_senario_9/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/small_static_avatar.png differ diff --git a/app0-2017/APP0_senario_9/public/maze/spies-dead.png b/app0-2017/APP0_senario_9/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/spies-dead.png differ diff --git a/app0-2017/APP0_senario_9/public/maze/start.mp3 b/app0-2017/APP0_senario_9/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/start.mp3 differ diff --git a/app0-2017/APP0_senario_9/public/maze/start.ogg b/app0-2017/APP0_senario_9/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/start.ogg differ diff --git a/app0-2017/APP0_senario_9/public/maze/static_avatar.png b/app0-2017/APP0_senario_9/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/static_avatar.png differ diff --git a/app0-2017/APP0_senario_9/public/maze/testBack.png b/app0-2017/APP0_senario_9/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/testBack.png differ diff --git a/app0-2017/APP0_senario_9/public/maze/testBackPlain.png b/app0-2017/APP0_senario_9/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/testBackPlain.png differ diff --git a/app0-2017/APP0_senario_9/public/maze/tiles.png b/app0-2017/APP0_senario_9/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/tiles.png differ diff --git a/app0-2017/APP0_senario_9/public/maze/wall.mp3 b/app0-2017/APP0_senario_9/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/wall.mp3 differ diff --git a/app0-2017/APP0_senario_9/public/maze/wall.ogg b/app0-2017/APP0_senario_9/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/wall.ogg differ diff --git a/app0-2017/APP0_senario_9/public/maze/wall.png b/app0-2017/APP0_senario_9/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/wall.png differ diff --git a/app0-2017/APP0_senario_9/public/maze/win.mp3 b/app0-2017/APP0_senario_9/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/win.mp3 differ diff --git a/app0-2017/APP0_senario_9/public/maze/win.ogg b/app0-2017/APP0_senario_9/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/win.ogg differ diff --git a/app0-2017/APP0_senario_9/public/maze/win_avatar.png b/app0-2017/APP0_senario_9/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/win_avatar.png differ diff --git a/app0-2017/APP0_senario_9/public/maze/wolf.png b/app0-2017/APP0_senario_9/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/APP0_senario_9/public/maze/wolf.png differ diff --git a/app0-2017/APP0_senario_9/public/maze_config.json b/app0-2017/APP0_senario_9/public/maze_config.json new file mode 100644 index 0000000..6184f16 --- /dev/null +++ b/app0-2017/APP0_senario_9/public/maze_config.json @@ -0,0 +1,99 @@ +{ + "map":{ + "layout":[ + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1], + [1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 2, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1], + [1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 3], + [1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], + + [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1], + [1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1], + [1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 3], + [1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1], + [1, 0, 2, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/APP0_senario_9/run b/app0-2017/APP0_senario_9/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/APP0_senario_9/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/APP0_senario_9/student/maze.tpl.py b/app0-2017/APP0_senario_9/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/APP0_senario_9/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/APP0_senario_9/task.yaml b/app0-2017/APP0_senario_9/task.yaml new file mode 100644 index 0000000..9e578f1 --- /dev/null +++ b/app0-2017/APP0_senario_9/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: Scénario 9 +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/blockly_app0_step1/public/blocks.js b/app0-2017/blockly_app0_step1/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/blockly_app0_step1/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/blockly_app0_step1/public/interpreter.js b/app0-2017/blockly_app0_step1/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/blockly_app0_step1/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/blockly_app0_step1/public/maze.js b/app0-2017/blockly_app0_step1/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/blockly_app0_step1/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/blockly_app0_step1/public/maze/americans.png b/app0-2017/blockly_app0_step1/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/americans.png differ diff --git a/app0-2017/blockly_app0_step1/public/maze/avatar.png b/app0-2017/blockly_app0_step1/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/avatar.png differ diff --git a/app0-2017/blockly_app0_step1/public/maze/background.png b/app0-2017/blockly_app0_step1/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/background.png differ diff --git a/app0-2017/blockly_app0_step1/public/maze/failure.mp3 b/app0-2017/blockly_app0_step1/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/failure.mp3 differ diff --git a/app0-2017/blockly_app0_step1/public/maze/failure.ogg b/app0-2017/blockly_app0_step1/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/failure.ogg differ diff --git a/app0-2017/blockly_app0_step1/public/maze/failure_avatar.png b/app0-2017/blockly_app0_step1/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/failure_avatar.png differ diff --git a/app0-2017/blockly_app0_step1/public/maze/goal.gif b/app0-2017/blockly_app0_step1/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/goal.gif differ diff --git a/app0-2017/blockly_app0_step1/public/maze/goalIdle.gif b/app0-2017/blockly_app0_step1/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/goalIdle.gif differ diff --git a/app0-2017/blockly_app0_step1/public/maze/maze_forever.gif b/app0-2017/blockly_app0_step1/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/maze_forever.gif differ diff --git a/app0-2017/blockly_app0_step1/public/maze/nedstark.png b/app0-2017/blockly_app0_step1/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/nedstark.png differ diff --git a/app0-2017/blockly_app0_step1/public/maze/obstacle.gif b/app0-2017/blockly_app0_step1/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/obstacle.gif differ diff --git a/app0-2017/blockly_app0_step1/public/maze/obstacle.mp3 b/app0-2017/blockly_app0_step1/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/obstacle.mp3 differ diff --git a/app0-2017/blockly_app0_step1/public/maze/obstacle.ogg b/app0-2017/blockly_app0_step1/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/obstacle.ogg differ diff --git a/app0-2017/blockly_app0_step1/public/maze/obstacleIdle.gif b/app0-2017/blockly_app0_step1/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/blockly_app0_step1/public/maze/small_static_avatar.png b/app0-2017/blockly_app0_step1/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/small_static_avatar.png differ diff --git a/app0-2017/blockly_app0_step1/public/maze/spies-dead.png b/app0-2017/blockly_app0_step1/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/spies-dead.png differ diff --git a/app0-2017/blockly_app0_step1/public/maze/start.mp3 b/app0-2017/blockly_app0_step1/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/start.mp3 differ diff --git a/app0-2017/blockly_app0_step1/public/maze/start.ogg b/app0-2017/blockly_app0_step1/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/start.ogg differ diff --git a/app0-2017/blockly_app0_step1/public/maze/static_avatar.png b/app0-2017/blockly_app0_step1/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/static_avatar.png differ diff --git a/app0-2017/blockly_app0_step1/public/maze/testBack.png b/app0-2017/blockly_app0_step1/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/testBack.png differ diff --git a/app0-2017/blockly_app0_step1/public/maze/testBackPlain.png b/app0-2017/blockly_app0_step1/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/testBackPlain.png differ diff --git a/app0-2017/blockly_app0_step1/public/maze/tiles.png b/app0-2017/blockly_app0_step1/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/tiles.png differ diff --git a/app0-2017/blockly_app0_step1/public/maze/wall.mp3 b/app0-2017/blockly_app0_step1/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/wall.mp3 differ diff --git a/app0-2017/blockly_app0_step1/public/maze/wall.ogg b/app0-2017/blockly_app0_step1/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/wall.ogg differ diff --git a/app0-2017/blockly_app0_step1/public/maze/wall.png b/app0-2017/blockly_app0_step1/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/wall.png differ diff --git a/app0-2017/blockly_app0_step1/public/maze/win.mp3 b/app0-2017/blockly_app0_step1/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/win.mp3 differ diff --git a/app0-2017/blockly_app0_step1/public/maze/win.ogg b/app0-2017/blockly_app0_step1/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/win.ogg differ diff --git a/app0-2017/blockly_app0_step1/public/maze/win_avatar.png b/app0-2017/blockly_app0_step1/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/win_avatar.png differ diff --git a/app0-2017/blockly_app0_step1/public/maze/wolf.png b/app0-2017/blockly_app0_step1/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/blockly_app0_step1/public/maze/wolf.png differ diff --git a/app0-2017/blockly_app0_step1/public/maze_config.json b/app0-2017/blockly_app0_step1/public/maze_config.json new file mode 100644 index 0000000..786db81 --- /dev/null +++ b/app0-2017/blockly_app0_step1/public/maze_config.json @@ -0,0 +1,94 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 2, 1, 1, 3, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0]], + + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 1, 1, 2, 3, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0]], + + [[0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0], + [2, 1, 1, 1, 1, 3, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/blockly_app0_step1/run b/app0-2017/blockly_app0_step1/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/blockly_app0_step1/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/blockly_app0_step1/student/maze.tpl.py b/app0-2017/blockly_app0_step1/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/blockly_app0_step1/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/blockly_app0_step1/task.yaml b/app0-2017/blockly_app0_step1/task.yaml new file mode 100644 index 0000000..4bab4b5 --- /dev/null +++ b/app0-2017/blockly_app0_step1/task.yaml @@ -0,0 +1,90 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: 'Première étape : aller tout droit' +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/blockly_app0_step2/public/blocks.js b/app0-2017/blockly_app0_step2/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/blockly_app0_step2/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/blockly_app0_step2/public/interpreter.js b/app0-2017/blockly_app0_step2/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/blockly_app0_step2/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/blockly_app0_step2/public/maze.js b/app0-2017/blockly_app0_step2/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/blockly_app0_step2/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/blockly_app0_step2/public/maze/americans.png b/app0-2017/blockly_app0_step2/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/americans.png differ diff --git a/app0-2017/blockly_app0_step2/public/maze/avatar.png b/app0-2017/blockly_app0_step2/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/avatar.png differ diff --git a/app0-2017/blockly_app0_step2/public/maze/background.png b/app0-2017/blockly_app0_step2/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/background.png differ diff --git a/app0-2017/blockly_app0_step2/public/maze/failure.mp3 b/app0-2017/blockly_app0_step2/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/failure.mp3 differ diff --git a/app0-2017/blockly_app0_step2/public/maze/failure.ogg b/app0-2017/blockly_app0_step2/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/failure.ogg differ diff --git a/app0-2017/blockly_app0_step2/public/maze/failure_avatar.png b/app0-2017/blockly_app0_step2/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/failure_avatar.png differ diff --git a/app0-2017/blockly_app0_step2/public/maze/goal.gif b/app0-2017/blockly_app0_step2/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/goal.gif differ diff --git a/app0-2017/blockly_app0_step2/public/maze/goalIdle.gif b/app0-2017/blockly_app0_step2/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/goalIdle.gif differ diff --git a/app0-2017/blockly_app0_step2/public/maze/maze_forever.gif b/app0-2017/blockly_app0_step2/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/maze_forever.gif differ diff --git a/app0-2017/blockly_app0_step2/public/maze/nedstark.png b/app0-2017/blockly_app0_step2/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/nedstark.png differ diff --git a/app0-2017/blockly_app0_step2/public/maze/obstacle.gif b/app0-2017/blockly_app0_step2/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/obstacle.gif differ diff --git a/app0-2017/blockly_app0_step2/public/maze/obstacle.mp3 b/app0-2017/blockly_app0_step2/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/obstacle.mp3 differ diff --git a/app0-2017/blockly_app0_step2/public/maze/obstacle.ogg b/app0-2017/blockly_app0_step2/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/obstacle.ogg differ diff --git a/app0-2017/blockly_app0_step2/public/maze/obstacleIdle.gif b/app0-2017/blockly_app0_step2/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/blockly_app0_step2/public/maze/small_static_avatar.png b/app0-2017/blockly_app0_step2/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/small_static_avatar.png differ diff --git a/app0-2017/blockly_app0_step2/public/maze/spies-dead.png b/app0-2017/blockly_app0_step2/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/spies-dead.png differ diff --git a/app0-2017/blockly_app0_step2/public/maze/start.mp3 b/app0-2017/blockly_app0_step2/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/start.mp3 differ diff --git a/app0-2017/blockly_app0_step2/public/maze/start.ogg b/app0-2017/blockly_app0_step2/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/start.ogg differ diff --git a/app0-2017/blockly_app0_step2/public/maze/static_avatar.png b/app0-2017/blockly_app0_step2/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/static_avatar.png differ diff --git a/app0-2017/blockly_app0_step2/public/maze/testBack.png b/app0-2017/blockly_app0_step2/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/testBack.png differ diff --git a/app0-2017/blockly_app0_step2/public/maze/testBackPlain.png b/app0-2017/blockly_app0_step2/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/testBackPlain.png differ diff --git a/app0-2017/blockly_app0_step2/public/maze/tiles.png b/app0-2017/blockly_app0_step2/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/tiles.png differ diff --git a/app0-2017/blockly_app0_step2/public/maze/wall.mp3 b/app0-2017/blockly_app0_step2/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/wall.mp3 differ diff --git a/app0-2017/blockly_app0_step2/public/maze/wall.ogg b/app0-2017/blockly_app0_step2/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/wall.ogg differ diff --git a/app0-2017/blockly_app0_step2/public/maze/wall.png b/app0-2017/blockly_app0_step2/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/wall.png differ diff --git a/app0-2017/blockly_app0_step2/public/maze/win.mp3 b/app0-2017/blockly_app0_step2/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/win.mp3 differ diff --git a/app0-2017/blockly_app0_step2/public/maze/win.ogg b/app0-2017/blockly_app0_step2/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/win.ogg differ diff --git a/app0-2017/blockly_app0_step2/public/maze/win_avatar.png b/app0-2017/blockly_app0_step2/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/win_avatar.png differ diff --git a/app0-2017/blockly_app0_step2/public/maze/wolf.png b/app0-2017/blockly_app0_step2/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/blockly_app0_step2/public/maze/wolf.png differ diff --git a/app0-2017/blockly_app0_step2/public/maze_config.json b/app0-2017/blockly_app0_step2/public/maze_config.json new file mode 100644 index 0000000..c85ed8b --- /dev/null +++ b/app0-2017/blockly_app0_step2/public/maze_config.json @@ -0,0 +1,94 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 0, 0], + [2, 1, 1, 0, 1, 3, 0, 0], + [1, 1, 1, 1, 1, 1, 0, 0]], + + [[0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 0, 0], + [2, 0, 1, 1, 1, 3, 0, 0], + [1, 1, 1, 1, 1, 1, 0, 0]], + + [[0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 0, 0], + [2, 1, 1, 1, 0, 3, 0, 0], + [1, 1, 1, 1, 1, 1, 0, 0]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/blockly_app0_step2/run b/app0-2017/blockly_app0_step2/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/blockly_app0_step2/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/blockly_app0_step2/student/maze.tpl.py b/app0-2017/blockly_app0_step2/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/blockly_app0_step2/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/blockly_app0_step2/task.yaml b/app0-2017/blockly_app0_step2/task.yaml new file mode 100644 index 0000000..07dd270 --- /dev/null +++ b/app0-2017/blockly_app0_step2/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Celine Deknop +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + memory: '100' + output: '2' +name: 'Deuxième étape : éviter un obstacle' +network_grading: false +order: 0 +problems: + code: + options: + scrollbars: true + toolboxPosition: start + visual: + position: left + css: true + media: /static/common/js/blockly/media/ + maxBlocks: Infinity + sounds: true + oneBasedIndex: true + trashcan: true + files: + - maze.js + - interpreter.js + type: blockly + name: '' + blocks_files: + - blocks.js + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + + workspace: '' + header: '' +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/blockly_app0_step3/public/blocks.js b/app0-2017/blockly_app0_step3/public/blocks.js new file mode 100644 index 0000000..313a901 --- /dev/null +++ b/app0-2017/blockly_app0_step3/public/blocks.js @@ -0,0 +1,455 @@ +/** + * Blockly Games: Maze Blocks + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Blocks for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +'use strict'; + +//File to modify to change the maze configuration +var task_directory_path = window.location.pathname + "/"; +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +var json = JSON.parse(request.responseText); + +Maze.Blocks = {}; + +/** + * Common HSV hue for all movement blocks. + */ +Maze.Blocks.MOVEMENT_HUE = 290; + +/** + * HSV hue for loop block. + */ +Maze.Blocks.LOOPS_HUE = 120; + +/** + * Common HSV hue for all logic blocks. + */ +Maze.Blocks.LOGIC_HUE = 210; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.LEFT_TURN = ' \u21BA'; + +/** + * Left turn arrow to be appended to messages. + */ +Maze.Blocks.RIGHT_TURN = ' \u21BB'; + +// Extensions to Blockly's language and JavaScript generator. +Blockly.Blocks['maze_moveForward'] = { + /** + * Block for moving forward. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": json.blocs.move.name, + "previousStatement": null, + "nextStatement": null, + "colour": Maze.Blocks.MOVEMENT_HUE, + "tooltip": json.blocs.move.tooltip + }); + } +}; + +Blockly.JavaScript['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_moveForward'] = function(block) { + // Generate JavaScript for moving forward. + return 'moveForward()\n'; +}; + +Blockly.Blocks['maze_turn'] = { + /** + * Block for turning left or right. + * @this Blockly.Block + */ + init: function() { + var DIRECTIONS = [ + [json.blocs.turn.name1, 'turnRight'], + [json.blocs.turn.name2, 'turnLeft'] + ]; + // Append arrows to direction messages. + DIRECTIONS[0][0] += Maze.Blocks.RIGHT_TURN; + DIRECTIONS[1][0] += Maze.Blocks.LEFT_TURN; + this.setColour(Maze.Blocks.MOVEMENT_HUE); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(DIRECTIONS), 'DIR'); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setTooltip(json.blocs.turn.tooltip); + } +}; + +Blockly.JavaScript['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '(\'block_id_' + block.id + '\');\n'; +}; + +Blockly.Python['maze_turn'] = function(block) { + // Generate JavaScript for turning left or right. + var dir = block.getFieldValue('DIR'); + return dir + '()\n'; +}; + +Blockly.Blocks['get_player_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_player_pos", + "message0": json.blocs.getPlayerPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.Blocks['custom_if_else'] = { + init: function() { + this.jsonInit({ + "type": "custom_if_else", + "message0": "if %1 %2 else %3", + "args0": [ + { + "type": "input_value", + "name": "COND", + "check": "Boolean" + }, + { + "type": "input_statement", + "name": "IF_STAT" + }, + { + "type": "input_statement", + "name": "ELSE_STAT" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": 200, + "tooltip": "if COND is true, execute the first block. Otherwise, execute the second", + "helpUrl": "" + }); + } + }; + +Blockly.Python['custom_if_else'] = function(block) { + var value_cond = Blockly.Python.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.Python.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.Python.statementToCode(block, 'ELSE_STAT'); + var code = 'if '+value_cond+" :\n"+statements_if_stat+" \nelse:\n"+statements_else_stat+"\n"; + return code; +}; + +Blockly.JavaScript['custom_if_else'] = function(block) { + var value_cond = Blockly.JavaScript.valueToCode(block, 'COND', Blockly.Python.ORDER_ATOMIC); + var statements_if_stat = Blockly.JavaScript.statementToCode(block, 'IF_STAT'); + var statements_else_stat = Blockly.JavaScript.statementToCode(block, 'ELSE_STAT'); + var code = 'if ('+value_cond+"){\n"+statements_if_stat+"\n} \nelse{\n"+statements_else_stat+"\n}\n"; + return code; +}; + +Blockly.JavaScript['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_NONE]; +}; + +Blockly.Python['get_player_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getPlayer'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_NONE]; +}; + +Blockly.Blocks['get_target_pos'] = { + init: function() { + this.jsonInit({ + "type": "get_target_pos", + "message0": json.blocs.getTargetPosition.name+" %1", + "args0": [ + { + "type": "field_dropdown", + "name": "VALUE", + "options": [ + [ + "x", + "X" + ], + [ + "y", + "Y" + ] + ] + } + ], + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getTargetPosition.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()';; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_target_pos'] = function(block) { + var dropdown_value = block.getFieldValue('VALUE'); + var code = 'getTarget'+dropdown_value+'()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['get_player_dir'] = { + init: function() { + this.jsonInit({ + "type": "get_player_dir", + "message0": json.blocs.getPlayerDirection.name, + "output": "Number", + "colour": 230, + "tooltip": json.blocs.getPlayerDirection.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['get_player_dir'] = function(block) { + var code = 'getPlayerDir()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['north_value'] = { + init: function() { + this.appendDummyInput() + .appendField("North"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au nord"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['north_value'] = function(block) { + var code = '0'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['east_value'] = { + init: function() { + this.appendDummyInput() + .appendField("East"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'est"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['east_value'] = function(block) { + var code = '1'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['south_value'] = { + init: function() { + this.appendDummyInput() + .appendField("South"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant au sud"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['south_value'] = function(block) { + var code = '2'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['west_value'] = { + init: function() { + this.appendDummyInput() + .appendField("West"); + this.setOutput(true, null); + this.setColour(230); + this.setTooltip("Retourne la valeur correspondant à l'ouest"); + this.setHelpUrl(""); + } +}; + +Blockly.JavaScript['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['west_value'] = function(block) { + var code = '3'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['can_move'] = { + init: function() { + this.jsonInit({ + "type": "can_move", + "message0": json.blocs.canMove.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.canMove.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['can_move'] = function(block) { + var code = 'canMove()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_in_front_of_enemy'] = { + init: function() { + this.jsonInit({ + "type": "is_in_front_of_enemy", + "message0": json.blocs.isInFrontOfEnemy.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isInFrontOfEnemy.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_in_front_of_enemy'] = function(block) { + var code = 'isInFrontOfEnemy()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['is_on_target'] = { + init: function() { + this.jsonInit({ + "type": "is_on_target", + "message0": json.blocs.isOnTarget.name, + "output": "Boolean", + "colour": 230, + "tooltip": json.blocs.isOnTarget.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.JavaScript.ORDER_MEMBER]; +}; + +Blockly.Python['is_on_target'] = function(block) { + var code = 'isOnTarget()'; + return [code, Blockly.Python.ORDER_MEMBER]; +}; + +Blockly.Blocks['spy_on_target'] = { + init: function() { + this.jsonInit({ + "type": "spy_on_target", + "message0": json.blocs.finish.name, + "previousStatement": null, + "nextStatement": null, + "colour": 230, + "tooltip": json.blocs.finish.tooltip, + "helpUrl": "" + }); + } +}; + +Blockly.JavaScript['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; + +Blockly.Python['spy_on_target'] = function(block) { + var code = 'spyOnTarget()'; + return code; +}; \ No newline at end of file diff --git a/app0-2017/blockly_app0_step3/public/interpreter.js b/app0-2017/blockly_app0_step3/public/interpreter.js new file mode 100644 index 0000000..842c6b0 --- /dev/null +++ b/app0-2017/blockly_app0_step3/public/interpreter.js @@ -0,0 +1,95 @@ +var initInterpreterApi = function(interpreter, scope) { + var wrapper; + wrapper = function(id) { + Maze.move(0, id.toString()); + }; + interpreter.setProperty(scope, 'moveForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.move(2, id.toString()); + }; + interpreter.setProperty(scope, 'moveBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(0, id.toString()); + }; + interpreter.setProperty(scope, 'turnLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + Maze.turn(1, id.toString()); + }; + interpreter.setProperty(scope, 'turnRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(0, id.toString())); + }; + interpreter.setProperty(scope, 'isPathForward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(1, id.toString())); + }; + interpreter.setProperty(scope, 'isPathRight', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(2, id.toString())); + }; + interpreter.setProperty(scope, 'isPathBackward', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isPath(3, id.toString())); + }; + interpreter.setProperty(scope, 'isPathLeft', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerX()); + }; + interpreter.setProperty(scope, 'getPlayerX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerY()); + }; + interpreter.setProperty(scope, 'getPlayerY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetX()); + }; + interpreter.setProperty(scope, 'getTargetX', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getTargetY()); + }; + interpreter.setProperty(scope, 'getTargetY', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.getPlayerDir()); + }; + interpreter.setProperty(scope, 'getPlayerDir', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.canMove()); + }; + interpreter.setProperty(scope, 'canMove', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isInFrontOfEnemy()); + }; + interpreter.setProperty(scope, 'isInFrontOfEnemy', + interpreter.createNativeFunction(wrapper)); + wrapper = function(id) { + return interpreter.createPrimitive(Maze.isOnTarget()); + }; + interpreter.setProperty(scope, 'isOnTarget', + interpreter.createNativeFunction(wrapper)); + wrapper = function() { + return interpreter.createPrimitive(Maze.notDone()); + }; + interpreter.setProperty(scope, 'notDone', + interpreter.createNativeFunction(wrapper)); + + Maze.log = []; + Maze.reset(false); +}; + +var animate = function() { + Maze.animate(); +}; diff --git a/app0-2017/blockly_app0_step3/public/maze.js b/app0-2017/blockly_app0_step3/public/maze.js new file mode 100644 index 0000000..d8f2d6c --- /dev/null +++ b/app0-2017/blockly_app0_step3/public/maze.js @@ -0,0 +1,925 @@ +/** + * Blockly Games: Maze + * + * Copyright 2012 Google Inc. + * https://github.com/google/blockly-games + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview JavaScript for Blockly's Maze application. + * @author fraser@google.com (Neil Fraser) + * @author celine.deknop@student.uclouvain.be (Céline Deknop) + * @author victor.feyens@student.uclouvain.be (Victor Feyens) + */ +"use strict"; + +var task_directory_path = window.location.pathname + "/"; +window.Maze = {}; + +//File to modify to change the maze configuration +var maze_file = "" +if(task_directory_path.includes("edit")){ //When we are editing the task + maze_file = task_directory_path.replace("admin","course").replace("edit/task/","")+"maze_config.json" +}else { //When displaying the task + maze_file = task_directory_path + "maze_config.json"; +} + +var request = new XMLHttpRequest(); +request.open("GET", maze_file, false); +request.send(null); +request.responseText; +var json = JSON.parse(request.responseText); + + +// Crash type constants. +Maze.CRASH_STOP = 1; +Maze.CRASH_SPIN = 2; +Maze.CRASH_FALL = 3; + +Maze.SKIN = { + sprite: task_directory_path + json.visuals.sprite, + tiles: task_directory_path + json.visuals.tiles, + marker: task_directory_path + json.visuals.marker, + goalAnimation: task_directory_path + json.visuals.goalAnimation, + obstacleIdle: task_directory_path + json.visuals.obstacleIdle, + obstacleAnimation: task_directory_path + json.visuals.obstacleAnimation, + wall: task_directory_path + json.visuals.wall, + obstacleScale: json.visuals.obstacleScale, + background: task_directory_path + json.visuals.background, + graph: json.visuals.graph, + look: '#000', + obstacleSound: [task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg'], + winSound: [task_directory_path + 'maze/win.mp3', task_directory_path + 'maze/win.ogg'], + crashSound: [task_directory_path + 'maze/failure.mp3', task_directory_path + 'maze/failure.ogg'], + crashType: Maze.CRASH_STOP +}; + +/** + * Milliseconds between each animation frame. + */ +window.stepSpeed = json.map.animationSpeed; + +/** + * The types of squares in the maze, which is represented + * as a 2D array of SquareType values. + * @enum {number} + */ +Maze.SquareType = json.map.squareType; + +// The maze square constants +Maze.map = json.map.layout[0]; + +/** + * Measure maze dimensions and set sizes. + * ROWS: Number of tiles down. + * COLS: Number of tiles across. + * SQUARE_SIZE: Pixel height and width of each maze square (i.e. tile). + */ +Maze.ROWS = Maze.map.length; +Maze.COLS = Maze.map[0].length; +Maze.SQUARE_SIZE = json.map.squareSize; +Maze.PEGMAN_HEIGHT = json.map.avatarHeight; +Maze.PEGMAN_WIDTH = json.map.avatarWidth; + +Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; +Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; +Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; + +/** + * Constants for cardinal directions. Subsequent code assumes these are + * in the range 0..3 and that opposites have an absolute difference of 2. + * @enum {number} + */ +Maze.DirectionType = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +}; + +/** + * Outcomes of running the user program. + */ +Maze.ResultType = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +}; + +/** + * Result of last execution. + */ +Maze.result = Maze.ResultType.UNSET; +Maze.finished = false; + +/** + * Starting direction. + */ +Maze.startDirection = Maze.DirectionType[json.map.startDirection]; + +/** + * PIDs of animation tasks currently executing. + */ +Maze.pidList = []; + + +Maze.updateMap = function(map){ + Maze.map = map; + Maze.ROWS = Maze.map.length; + Maze.COLS = Maze.map[0].length; + Maze.MAZE_WIDTH = Maze.SQUARE_SIZE * Maze.COLS; + Maze.MAZE_HEIGHT = Maze.SQUARE_SIZE * Maze.ROWS; + Maze.PATH_WIDTH = Maze.SQUARE_SIZE / 3; +} + +Maze.reload_maze = function(map) { + if (typeof Maze !== "undefined" && typeof Maze.reset !== "undefined") { + Maze.updateMap(map); + $("#blocklySvgZone").empty(); + Maze.init(); + } + +} + + +/** + * Create and layout all the nodes for the path, scenery, Pegman, and goal. + */ +Maze.drawMap = function() { + var svg = document.getElementById('blocklySvgZone'); + var x, y, tile; + var scale = Math.max(Maze.ROWS, Maze.COLS) * Maze.SQUARE_SIZE; + svg.setAttribute('viewBox', '0 0 ' + scale + ' ' + scale); + svg.setAttribute('style', ''); + + // Draw the outer square. + var square = document.createElementNS(Blockly.SVG_NS, 'rect'); + square.setAttribute('width', Maze.MAZE_WIDTH); + square.setAttribute('height', Maze.MAZE_HEIGHT); + square.setAttribute('fill', '#F1EEE7'); + square.setAttribute('stroke-width', 1); + square.setAttribute('stroke', '#CCB'); + svg.appendChild(square); + + if (Maze.SKIN.background) { + for(var xVal = 0; xVal < Maze.COLS; xVal++){ + for(var yVal = 0; yVal < Maze.ROWS; yVal++){ + var tile = document.createElementNS(Blockly.SVG_NS, 'image'); + tile.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.background); + tile.setAttribute('height', Maze.SQUARE_SIZE); + tile.setAttribute('width', Maze.SQUARE_SIZE); + tile.setAttribute('x', xVal*Maze.SQUARE_SIZE); + tile.setAttribute('y', yVal*Maze.SQUARE_SIZE); + svg.appendChild(tile); + } + } + } + if (Maze.SKIN.graph) { + // Draw the grid lines. + var offset = 0.5; + for (var k = 0; k < Maze.ROWS; k++) { + var h_line = document.createElementNS(Blockly.SVG_NS, 'line'); + h_line.setAttribute('y1', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('x2', Maze.MAZE_WIDTH); + h_line.setAttribute('y2', k * Maze.SQUARE_SIZE + offset); + h_line.setAttribute('stroke', Maze.SKIN.graph); + h_line.setAttribute('stroke-width', 1); + svg.appendChild(h_line); + } + for (var k = 0; k < Maze.COLS; k++) { + var v_line = document.createElementNS(Blockly.SVG_NS, 'line'); + v_line.setAttribute('x1', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('x2', k * Maze.SQUARE_SIZE + offset); + v_line.setAttribute('y2', Maze.MAZE_HEIGHT); + v_line.setAttribute('stroke', Maze.SKIN.graph); + v_line.setAttribute('stroke-width', 1); + svg.appendChild(v_line); + } + } + + // Add finish marker. + var finishMarker = document.createElementNS(Blockly.SVG_NS, 'image'); + finishMarker.setAttribute('id', 'finish'); + finishMarker.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.marker); + finishMarker.setAttribute('height', 43); + finishMarker.setAttribute('width', 50); + svg.appendChild(finishMarker); + + // Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman + var pegmanClip = document.createElementNS(Blockly.SVG_NS, 'clipPath'); + pegmanClip.setAttribute('id', 'pegmanClipPath'); + var clipRect = document.createElementNS(Blockly.SVG_NS, 'rect'); + clipRect.setAttribute('id', 'clipRect'); + clipRect.setAttribute('width', Maze.PEGMAN_WIDTH); + clipRect.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanClip.appendChild(clipRect); + svg.appendChild(pegmanClip); + + // Add obstacles and walls + var obsId = 0; + var wallID = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] === Maze.SquareType.OBSTACLE) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'obstacle' + obsId); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.obstacleIdle); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height')); + svg.appendChild(obsIcon); + ++obsId; + } + if (Maze.map[y][x] === Maze.SquareType.WALL) { + var obsIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + obsIcon.setAttribute('id', 'wall' + wallID); + obsIcon.setAttribute('height', 43 * Maze.SKIN.obstacleScale); + obsIcon.setAttribute('width', 50 * Maze.SKIN.obstacleScale); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.wall); + obsIcon.setAttribute('x', + Maze.SQUARE_SIZE * (x + 0.5) - + obsIcon.getAttribute('width') / 2); + obsIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.9) - + obsIcon.getAttribute('height') ); + svg.appendChild(obsIcon); + ++wallID; + } + + } + } + + // Add Pegman. + var pegmanIcon = document.createElementNS(Blockly.SVG_NS, 'image'); + pegmanIcon.setAttribute('id', 'pegman'); + pegmanIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.sprite); + pegmanIcon.setAttribute('height', Maze.PEGMAN_HEIGHT); + pegmanIcon.setAttribute('width', Maze.PEGMAN_WIDTH * 21); // 49 * 21 = 1029 + pegmanIcon.setAttribute('clip-path', 'url(#pegmanClipPath)'); + svg.appendChild(pegmanIcon); +}; + +/** + * Initialize Blockly and the maze. Called on page load. + */ +Maze.init = function() { + + if (typeof Blockly === "undefined" || typeof Blockly.getMainWorkspace() === "undefined" || Blockly.getMainWorkspace() === null) { + console.warn("Maze.init() called but Blockly or workspace was not loaded."); + window.setTimeout(Maze.init, 20); + return; + } + + // + // Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + // Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); + + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.winSound, 'win'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.crashSound, 'fail'); + Blockly.getMainWorkspace().getAudioManager().load(Maze.SKIN.obstacleSound, 'obstacle'); + // Not really needed, there are no user-defined functions or variables. + Blockly.JavaScript.addReservedWords('moveForward,moveBackward,' + + 'turnRight,turnLeft,isPathForward,isPathRight,isPathBackward,isPathLeft'); + + Maze.drawMap(); + + // Locate the start and finish squares. + for (var y = 0; y < Maze.ROWS; y++) { + for (var x = 0; x < Maze.COLS; x++) { + if (Maze.map[y][x] == Maze.SquareType.START) { + Maze.start_ = { + x: x, + y: y + }; + } else if (Maze.map[y][x] == Maze.SquareType.FINISH) { + Maze.finish_ = { + x: x, + y: y + }; + } + } + } + + Maze.reset(true); + + // document.body.addEventListener('mousemove', Maze.updatePegSpin_, true); + + // Switch to zero-based indexing so that later JS levels match the blocks. + Blockly.Blocks && (Blockly.Blocks.ONE_BASED_INDEXING = false); + Blockly.JavaScript && (Blockly.JavaScript.ONE_BASED_INDEXING = false); +}; + +/** + * Reset the maze to the start position and kill any pending animation tasks. + * @param {boolean} first True if an opening animation is to be played. + */ +Maze.reset = function(first) { + var x, y; + + // Kill all tasks. + for (x = 0; x < Maze.pidList.length; x++) { + window.clearTimeout(Maze.pidList[x]); + } + Maze.pidList = []; + + // Move Pegman into position. + Maze.pegmanX = Maze.start_.x; + Maze.pegmanY = Maze.start_.y; + + if (first) { + Maze.pegmanD = Maze.startDirection + 1; + Maze.scheduleFinish(false); + Maze.pidList.push(setTimeout(function() { + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD++; + }, window.stepSpeed * 5)); + } else { + Maze.pegmanD = Maze.startDirection; + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4); + } + + // Move the finish icon into position. + var finishIcon = document.getElementById('finish'); + finishIcon.setAttribute('x', Maze.SQUARE_SIZE * (Maze.finish_.x)); + finishIcon.setAttribute('y', Maze.SQUARE_SIZE * (Maze.finish_.y)); + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.marker); + + // Reset pegman's visibility. + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('opacity', 1); + pegmanIcon.setAttribute('visibility', 'visible'); + + // Reset the obstacle image. + var obsId = 0; + for (y = 0; y < Maze.ROWS; y++) { + for (x = 0; x < Maze.COLS; x++) { + var obsIcon = document.getElementById('obstacle' + obsId); + if (obsIcon) { + obsIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleIdle); + } + ++obsId; + } + } + +}; + + +/** + * Iterate through the recorded path and animate pegman's actions. + */ +Maze.animate = function() { + var action = Maze.log.shift(); + if (!action) { + // for (var x = 0; x < Maze.pidList.length; x++) { + // window.clearTimeout(Maze.pidList[x]); + // } + return; + } + switch (action[0]) { + case 'north': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY - 1, Maze.pegmanD * 4]); + Maze.pegmanY--; + break; + case 'east': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX + 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX++; + break; + case 'south': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY + 1, Maze.pegmanD * 4]); + Maze.pegmanY++; + break; + case 'west': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX - 1, Maze.pegmanY, Maze.pegmanD * 4]); + Maze.pegmanX--; + break; + case 'look_north': + Maze.scheduleLook(Maze.DirectionType.NORTH); + break; + case 'look_east': + Maze.scheduleLook(Maze.DirectionType.EAST); + break; + case 'look_south': + Maze.scheduleLook(Maze.DirectionType.SOUTH); + break; + case 'look_west': + Maze.scheduleLook(Maze.DirectionType.WEST); + break; + case 'fail_forward': + Maze.scheduleFail(true); + break; + case 'fail_backward': + Maze.scheduleFail(false); + break; + case 'right': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 + 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD + 1); + break; + case 'left': + Maze.schedule([Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4], [Maze.pegmanX, Maze.pegmanY, Maze.pegmanD * 4 - 4]); + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD - 1); + break; + case 'finish': + Maze.scheduleFinish(true); + break; + // TODO maybe add this + // case 'plant': + // Maze.animatePlant(); + // break; + } +}; + +Maze.getPlayerX = function(){ + return Maze.pegmanX +} + +Maze.getPlayerY = function(){ + return Maze.pegmanY +} + +Maze.getTargetX = function(){ + return Maze.finish_.x +} + +Maze.getTargetY = function(){ + return Maze.finish_.y +} + +Maze.getPlayerDir = function(){ + return Maze.pegmanD; +} + +Maze.canMove = function(){ + console.log("can move ?") + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else + { + + console.log(Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY-1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 1: //East + if(Maze.pegmanX == Maze.map[0].length-1) return false + else + { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX+1] != Maze.SquareType.WALL + } + case 2: //South + if(Maze.pegmanY == Maze.map.length-1) return false + else { + console.log(Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY+1][Maze.pegmanX] != Maze.SquareType.WALL + } + case 3: //West + if(Maze.pegmanX == 0) return false + else { + console.log(Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL) + return Maze.map[Maze.pegmanY][Maze.pegmanX-1] != Maze.SquareType.WALL + } + } +} + +Maze.isInFrontOfEnemy = function(){ + switch(Maze.pegmanD){ + case 0: //North + if(Maze.pegmanY == 0) return false + else return Maze.map[Maze.pegmanY-1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 1: //East + if(Maze.pegmanX == Maze.map.length-1) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX+1] == Maze.SquareType.OBSTACLE + case 2: //South + if(Maze.pegmanY == Maze.map[0].length-1) return false + else return Maze.map[Maze.pegmanY+1][Maze.pegmanX] == Maze.SquareType.OBSTACLE + case 3: //West + if(Maze.pegmanX == 0) return false + else return Maze.map[Maze.pegmanY][Maze.pegmanX-1] == Maze.SquareType.OBSTACLE + } +} + +Maze.isOnTarget = function(){ + return Maze.finish_.y == Maze.pegmanY && Maze.finish_.x == Maze.pegmanX +} + +Maze.spyOnTarget = function(){ + if (Maze.isOnTarget()){ + Maze.finished = true + Maze.result = Maze.ResultType.SUCCESS; + } +} + +/** + * Schedule the animations for a move or turn. + * @param {!Array.} startPos X, Y and direction starting points. + * @param {!Array.} endPos X, Y and direction ending points. + */ +Maze.schedule = function(startPos, endPos) { + var deltas = [(endPos[0] - startPos[0]) / 4, + (endPos[1] - startPos[1]) / 4, + (endPos[2] - startPos[2]) / 4 + ]; + Maze.displayPegman(startPos[0] + deltas[0], + startPos[1] + deltas[1], + Maze.constrainDirection16(startPos[2] + deltas[2])); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 2, + startPos[1] + deltas[1] * 2, + Maze.constrainDirection16(startPos[2] + deltas[2] * 2)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(startPos[0] + deltas[0] * 3, + startPos[1] + deltas[1] * 3, + Maze.constrainDirection16(startPos[2] + deltas[2] * 3)); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(endPos[0], endPos[1], + Maze.constrainDirection16(endPos[2])); + }, window.stepSpeed)); + + if (Maze.finish_.x == endPos[0] && Maze.finish_.y == endPos[1]) { + Maze.pidList.push(setTimeout(function() { + var finishIcon = document.getElementById('finish'); + if (finishIcon.getAttribute('xlink:href') != Maze.SKIN.goalAnimation) { + finishIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', Maze.SKIN.goalAnimation); + Blockly.getMainWorkspace().getAudioManager().play('win', 0.3); + } + }, window.stepSpeed * 4)); + } +}; + +/** + * Schedule the animations and sounds for a failed move. + * @param {boolean} forward True if forward, false if backward. + */ +Maze.scheduleFail = function(forward) { + var deltaX = 0; + var deltaY = 0; + switch (Maze.pegmanD) { + case Maze.DirectionType.NORTH: + deltaY = -1; + break; + case Maze.DirectionType.EAST: + deltaX = 1; + break; + case Maze.DirectionType.SOUTH: + deltaY = 1; + break; + case Maze.DirectionType.WEST: + deltaX = -1; + break; + } + if (!forward) { + deltaX = -deltaX; + deltaY = -deltaY; + } + + var targetX = Maze.pegmanX + deltaX + 1; + var targetY = Maze.pegmanY + deltaY; + var squareType = Maze.map[targetY][targetX]; + + if (squareType === Maze.SquareType.OBSTACLE) { + BlocklyTaskInterpreter.alert("Vous avez heurté un obstacle !"); + // Play the sound + Blockly.getMainWorkspace().getAudioManager().play('obstacle'); + + // Play the animation + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + var obsId = targetX + Maze.COLS * targetY; + var obsIcon = document.getElementById('obstacle' + obsId); + obsIcon.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', + Maze.SKIN.obstacleAnimation); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX / 2, + Maze.pegmanY + deltaY / 2, + direction16); + }, window.stepSpeed)); + + + var pegmanIcon = document.getElementById('pegman'); + + Maze.pidList.push(setTimeout(function() { + pegmanIcon.setAttribute('visibility', 'hidden'); + }, window.stepSpeed * 2)); + + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('failure'); + }, window.stepSpeed)); + } else if (Maze.SKIN.crashType == Maze.CRASH_STOP) { + BlocklyTaskInterpreter.alert("Vous avez heurté un mur !"); + // Bounce bounce. + deltaX /= 4; + deltaY /= 4; + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, + Maze.pegmanY, + direction16); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX + deltaX, + Maze.pegmanY + deltaY, + direction16); + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); + } else { + // Add a small random delta away from the grid. + var deltaZ = (Math.random() - 0.5) * 10; + var deltaD = (Math.random() - 0.5) / 2; + deltaX += (Math.random() - 0.5) / 4; + deltaY += (Math.random() - 0.5) / 4; + deltaX /= 8; + deltaY /= 8; + var acceleration = 0; + if (Maze.SKIN.crashType == Maze.CRASH_FALL) { + acceleration = 0.01; + } + Maze.pidList.push(setTimeout(function() { + Blockly.getMainWorkspace().getAudioManager().play('fail', 0.5); + }, window.stepSpeed * 2)); + var setPosition = function(n) { + return function() { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4 + + deltaD * n); + Maze.displayPegman(Maze.pegmanX + deltaX * n, + Maze.pegmanY + deltaY * n, + direction16, + deltaZ * n); + deltaY += acceleration; + }; + }; + // 100 frames should get Pegman offscreen. + for (var i = 1; i < 100; i++) { + Maze.pidList.push(setTimeout(setPosition(i), + window.stepSpeed * i / 2)); + } + } +}; + +/** + * Schedule the animations and sound for a victory dance. + * @param {boolean} sound Play the victory sound. + */ +Maze.scheduleFinish = function(sound) { + var direction16 = Maze.constrainDirection16(Maze.pegmanD * 4); + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + if (sound) { + Blockly.getMainWorkspace().getAudioManager().play('win', 0.5); + } + window.stepSpeed = 250; // Slow down victory animation a bit. + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 18); + }, window.stepSpeed)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, 16); + }, window.stepSpeed * 2)); + Maze.pidList.push(setTimeout(function() { + Maze.displayPegman(Maze.pegmanX, Maze.pegmanY, direction16); + }, window.stepSpeed * 3)); +}; + +/** + * Display Pegman at the specified location, facing the specified direction. + * @param {number} x Horizontal grid (or fraction thereof). + * @param {number} y Vertical grid (or fraction thereof). + * @param {number} d Direction (0 - 15) or dance (16 - 17). + * @param {number} opt_angle Optional angle (in degrees) to rotate Pegman. + */ +Maze.displayPegman = function(x, y, d, opt_angle) { + var pegmanIcon = document.getElementById('pegman'); + pegmanIcon.setAttribute('x', + x * Maze.SQUARE_SIZE - d * Maze.PEGMAN_WIDTH + 1); + pegmanIcon.setAttribute('y', + Maze.SQUARE_SIZE * (y + 0.5) - Maze.PEGMAN_HEIGHT / 2); + if (opt_angle) { + pegmanIcon.setAttribute('transform', 'rotate(' + opt_angle + ', ' + + (x * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ', ' + + (y * Maze.SQUARE_SIZE + Maze.SQUARE_SIZE / 2) + ')'); + } else { + pegmanIcon.setAttribute('transform', 'rotate(0, 0, 0)'); + } + + var clipRect = document.getElementById('clipRect'); + clipRect.setAttribute('x', x * Maze.SQUARE_SIZE + 1); + clipRect.setAttribute('y', pegmanIcon.getAttribute('y')); +}; + +/** + * Display the look icon at Pegman's current location, + * in the specified direction. + * @param {!Maze.DirectionType} d Direction (0 - 3). + */ +Maze.scheduleLook = function(d) { + var x = Maze.pegmanX; + var y = Maze.pegmanY; + switch (d) { + case Maze.DirectionType.NORTH: + x += 0.5; + break; + case Maze.DirectionType.EAST: + x += 1; + y += 0.5; + break; + case Maze.DirectionType.SOUTH: + x += 0.5; + y += 1; + break; + case Maze.DirectionType.WEST: + y += 0.5; + break; + } + x *= Maze.SQUARE_SIZE; + y *= Maze.SQUARE_SIZE; + d = d * 90 - 45; + + var lookIcon = document.getElementById('look'); + lookIcon.setAttribute('transform', + 'translate(' + x + ', ' + y + ') ' + + 'rotate(' + d + ' 0 0) scale(.4)'); + var paths = lookIcon.getElementsByTagName('path'); + lookIcon.style.display = 'inline'; + for (var x = 0, path; path = paths[x]; x++) { + Maze.scheduleLookStep(path, window.stepSpeed * x); + } +}; + +/** + * Schedule one of the 'look' icon's waves to appear, then disappear. + * @param {!Element} path Element to make appear. + * @param {number} delay Milliseconds to wait before making wave appear. + */ +Maze.scheduleLookStep = function(path, delay) { + Maze.pidList.push(setTimeout(function() { + path.style.display = 'inline'; + setTimeout(function() { + path.style.display = 'none'; + }, window.stepSpeed * 2); + }, delay)); +}; + +/** + * Keep the direction within 0-3, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection4 = function(d) { + d = Math.round(d) % 4; + if (d < 0) { + d += 4; + } + return d; +}; + +/** + * Keep the direction within 0-15, wrapping at both ends. + * @param {number} d Potentially out-of-bounds direction value. + * @return {number} Legal direction value. + */ +Maze.constrainDirection16 = function(d) { + d = Math.round(d) % 16; + if (d < 0) { + d += 16; + } + return d; +}; + +// Core functions. + +/** + * Attempt to move pegman forward or backward. + * @param {number} direction Direction to move (0 = forward, 2 = backward). + * @param {string} id ID of block that triggered this action. + * @throws {true} If the end of the maze is reached. + * @throws {false} If Pegman collides with a wall. + */ +Maze.move = function(direction, id) { + var isNotAPath = !Maze.isPath(direction, null); + if (isNotAPath) { + Maze.log.push(['fail_' + (direction ? 'backward' : 'forward'), id]); + Maze.result = Maze.ResultType.ERROR; + } + // If moving backward, flip the effective direction. + var effectiveDirection = Maze.pegmanD + direction; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + if (isNotAPath) Maze.pegmanY++; + command = 'north'; + break; + case Maze.DirectionType.EAST: + if (isNotAPath) Maze.pegmanX--; + command = 'east'; + break; + case Maze.DirectionType.SOUTH: + if (isNotAPath) Maze.pegmanY--; + command = 'south'; + break; + case Maze.DirectionType.WEST: + if (isNotAPath) Maze.pegmanX++; + command = 'west'; + break; + } + Maze.log.push([command, id]); +}; + +/** + * Turn pegman left or right. + * @param {number} direction Direction to turn (0 = left, 1 = right). + * @param {string} id ID of block that triggered this action. + */ +Maze.turn = function(direction, id) { + if (direction) { + // Right turn (clockwise). + // Maze.pegmanD++; + Maze.log.push(['right', id]); + } else { + // Left turn (counterclockwise). + // Maze.pegmanD--; + Maze.log.push(['left', id]); + } + Maze.pegmanD = Maze.constrainDirection4(Maze.pegmanD); +}; + +/** + * Is there a path next to pegman? + * @param {number} direction Direction to look + * (0 = forward, 1 = right, 2 = backward, 3 = left). + * @param {?string} id ID of block that triggered this action. + * Null if called as a helper function in Maze.move(). + * @return {boolean} True if there is a path. + */ +Maze.isPath = function(direction, id) { + var effectiveDirection = Maze.pegmanD + direction; + var square; + var command; + switch (Maze.constrainDirection4(effectiveDirection)) { + case Maze.DirectionType.NORTH: + square = Maze.map[Maze.pegmanY - 1] && + Maze.map[Maze.pegmanY - 1][Maze.pegmanX]; + command = 'look_north'; + break; + case Maze.DirectionType.EAST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX + 1]; + command = 'look_east'; + break; + case Maze.DirectionType.SOUTH: + square = Maze.map[Maze.pegmanY + 1] && + Maze.map[Maze.pegmanY + 1][Maze.pegmanX]; + command = 'look_south'; + break; + case Maze.DirectionType.WEST: + square = Maze.map[Maze.pegmanY][Maze.pegmanX - 1]; + command = 'look_west'; + break; + } + if (id) { + Maze.log.push([command, id]); + } + return square !== Maze.SquareType.WALL && square !== Maze.SquareType.OBSTACLE && square !== undefined; +}; + +/** + * Has the player finished the maze ? + */ +Maze.notDone = function() { + return !Maze.finished; +}; + +if (document.getElementById('blocklySvgZone') != null) { + window.addEventListener('load', Maze.init); +} else { + console.warn('Cannot find blocklySvgZone element.'); +} diff --git a/app0-2017/blockly_app0_step3/public/maze/americans.png b/app0-2017/blockly_app0_step3/public/maze/americans.png new file mode 100644 index 0000000..342bd4e Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/americans.png differ diff --git a/app0-2017/blockly_app0_step3/public/maze/avatar.png b/app0-2017/blockly_app0_step3/public/maze/avatar.png new file mode 100644 index 0000000..62386e1 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/avatar.png differ diff --git a/app0-2017/blockly_app0_step3/public/maze/background.png b/app0-2017/blockly_app0_step3/public/maze/background.png new file mode 100644 index 0000000..c2a80aa Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/background.png differ diff --git a/app0-2017/blockly_app0_step3/public/maze/failure.mp3 b/app0-2017/blockly_app0_step3/public/maze/failure.mp3 new file mode 100644 index 0000000..d3d73b9 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/failure.mp3 differ diff --git a/app0-2017/blockly_app0_step3/public/maze/failure.ogg b/app0-2017/blockly_app0_step3/public/maze/failure.ogg new file mode 100644 index 0000000..d7883c1 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/failure.ogg differ diff --git a/app0-2017/blockly_app0_step3/public/maze/failure_avatar.png b/app0-2017/blockly_app0_step3/public/maze/failure_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/failure_avatar.png differ diff --git a/app0-2017/blockly_app0_step3/public/maze/goal.gif b/app0-2017/blockly_app0_step3/public/maze/goal.gif new file mode 100644 index 0000000..6a4ea6b Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/goal.gif differ diff --git a/app0-2017/blockly_app0_step3/public/maze/goalIdle.gif b/app0-2017/blockly_app0_step3/public/maze/goalIdle.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/goalIdle.gif differ diff --git a/app0-2017/blockly_app0_step3/public/maze/maze_forever.gif b/app0-2017/blockly_app0_step3/public/maze/maze_forever.gif new file mode 100644 index 0000000..02dab59 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/maze_forever.gif differ diff --git a/app0-2017/blockly_app0_step3/public/maze/nedstark.png b/app0-2017/blockly_app0_step3/public/maze/nedstark.png new file mode 100644 index 0000000..6105fe7 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/nedstark.png differ diff --git a/app0-2017/blockly_app0_step3/public/maze/obstacle.gif b/app0-2017/blockly_app0_step3/public/maze/obstacle.gif new file mode 100644 index 0000000..1fa6dae Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/obstacle.gif differ diff --git a/app0-2017/blockly_app0_step3/public/maze/obstacle.mp3 b/app0-2017/blockly_app0_step3/public/maze/obstacle.mp3 new file mode 100644 index 0000000..b3ddb3a Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/obstacle.mp3 differ diff --git a/app0-2017/blockly_app0_step3/public/maze/obstacle.ogg b/app0-2017/blockly_app0_step3/public/maze/obstacle.ogg new file mode 100644 index 0000000..e320903 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/obstacle.ogg differ diff --git a/app0-2017/blockly_app0_step3/public/maze/obstacleIdle.gif b/app0-2017/blockly_app0_step3/public/maze/obstacleIdle.gif new file mode 100644 index 0000000..aa27ffc Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/obstacleIdle.gif differ diff --git a/app0-2017/blockly_app0_step3/public/maze/small_static_avatar.png b/app0-2017/blockly_app0_step3/public/maze/small_static_avatar.png new file mode 100644 index 0000000..439b36b Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/small_static_avatar.png differ diff --git a/app0-2017/blockly_app0_step3/public/maze/spies-dead.png b/app0-2017/blockly_app0_step3/public/maze/spies-dead.png new file mode 100644 index 0000000..505c475 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/spies-dead.png differ diff --git a/app0-2017/blockly_app0_step3/public/maze/start.mp3 b/app0-2017/blockly_app0_step3/public/maze/start.mp3 new file mode 100644 index 0000000..bdd6ea6 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/start.mp3 differ diff --git a/app0-2017/blockly_app0_step3/public/maze/start.ogg b/app0-2017/blockly_app0_step3/public/maze/start.ogg new file mode 100644 index 0000000..009fe7d Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/start.ogg differ diff --git a/app0-2017/blockly_app0_step3/public/maze/static_avatar.png b/app0-2017/blockly_app0_step3/public/maze/static_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/static_avatar.png differ diff --git a/app0-2017/blockly_app0_step3/public/maze/testBack.png b/app0-2017/blockly_app0_step3/public/maze/testBack.png new file mode 100644 index 0000000..7ed9e54 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/testBack.png differ diff --git a/app0-2017/blockly_app0_step3/public/maze/testBackPlain.png b/app0-2017/blockly_app0_step3/public/maze/testBackPlain.png new file mode 100644 index 0000000..ab8e699 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/testBackPlain.png differ diff --git a/app0-2017/blockly_app0_step3/public/maze/tiles.png b/app0-2017/blockly_app0_step3/public/maze/tiles.png new file mode 100644 index 0000000..b809691 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/tiles.png differ diff --git a/app0-2017/blockly_app0_step3/public/maze/wall.mp3 b/app0-2017/blockly_app0_step3/public/maze/wall.mp3 new file mode 100644 index 0000000..7814930 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/wall.mp3 differ diff --git a/app0-2017/blockly_app0_step3/public/maze/wall.ogg b/app0-2017/blockly_app0_step3/public/maze/wall.ogg new file mode 100644 index 0000000..0f324bc Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/wall.ogg differ diff --git a/app0-2017/blockly_app0_step3/public/maze/wall.png b/app0-2017/blockly_app0_step3/public/maze/wall.png new file mode 100644 index 0000000..02b4f87 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/wall.png differ diff --git a/app0-2017/blockly_app0_step3/public/maze/win.mp3 b/app0-2017/blockly_app0_step3/public/maze/win.mp3 new file mode 100644 index 0000000..e768c1b Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/win.mp3 differ diff --git a/app0-2017/blockly_app0_step3/public/maze/win.ogg b/app0-2017/blockly_app0_step3/public/maze/win.ogg new file mode 100644 index 0000000..64adef0 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/win.ogg differ diff --git a/app0-2017/blockly_app0_step3/public/maze/win_avatar.png b/app0-2017/blockly_app0_step3/public/maze/win_avatar.png new file mode 100644 index 0000000..0004eba Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/win_avatar.png differ diff --git a/app0-2017/blockly_app0_step3/public/maze/wolf.png b/app0-2017/blockly_app0_step3/public/maze/wolf.png new file mode 100644 index 0000000..06bab35 Binary files /dev/null and b/app0-2017/blockly_app0_step3/public/maze/wolf.png differ diff --git a/app0-2017/blockly_app0_step3/public/maze_config.json b/app0-2017/blockly_app0_step3/public/maze_config.json new file mode 100644 index 0000000..3a5ba36 --- /dev/null +++ b/app0-2017/blockly_app0_step3/public/maze_config.json @@ -0,0 +1,94 @@ +{ + "map":{ + "layout":[ + [[0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1], + [1, 4, 1, 0, 1, 1, 2, 3], + [1, 1, 1, 1, 1, 1, 1, 1]], + + [[0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1], + [2, 4, 1, 1, 0, 1, 1, 3], + [1, 1, 1, 1, 1, 1, 1, 1]], + + [[0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1], + [2, 1, 1, 4, 1, 4, 1, 3], + [1, 1, 1, 1, 1, 1, 1, 1]] + ], + "maxSteps":100, + "animationSpeed":50, + "squareSize":50, + "squareType":{ + "WALL": 0, + "OPEN": 1, + "START": 2, + "FINISH": 3, + "OBSTACLE": 4, + "STARTANDFINISH": 5 + }, + "startDirection":"EAST", + "avatarHeight":52, + "avatarWidth":49 + }, + "visuals":{ + "sprite":"maze/avatar.png", + "tiles":"maze/tiles.png", + "marker":"maze/nedstark.png", + "goalAnimation":"maze/goal.gif", + "obstacleIdle":"maze/wolf.png", + "obstacleAnimation":"maze/spies-dead.png", + "wall":"maze/wall.png", + "obstacleScale":1.2, + "background":"maze/testBackPlain.png", + "graph":"black", + "obstacleSound":"[task_directory_path + 'maze/obstacle.mp3', task_directory_path + 'maze/obstacle.ogg']", + "winSound":"['maze/win.mp3', 'maze/win.ogg']", + "crashSound":"['maze/failure.mp3', 'maze/failure.ogg']" + }, + "blocs":{ + "move":{ + "name":"move", + "tooltip":"Avance le joueur d'un espace" + }, + "turn":{ + "name1":"turn right", + "name2":"turn left", + "tooltip":"Tourne le joueur à gauche ou à droite de 90 degrés." + }, + "getPlayerPosition":{ + "name":"get", + "tooltip": "Retourne la position x ou y du joueur" + }, + "getTargetPosition":{ + "name":"getTarget", + "tooltip":"Retourne la position x ou y de Ned Stark" + }, + "getPlayerDirection":{ + "name":"getDirection", + "tooltip":"Retourne la direction dans laquelle est tournée le joueur" + }, + "canMove":{ + "name":"canMove", + "tooltip":"Retourne vrai si le personnage peut avancer" + }, + "isInFrontOfEnemy":{ + "name":"isInFrontOfWolf", + "tooltip":"Retourne vrai si le personnage est en face d'un loup" + }, + "isOnTarget":{ + "name":"isOnTarget", + "tooltip":"Retourne vrai si le personnage est sur la case de Ned" + }, + "finish":{ + "name":"spyOnTarget", + "tooltip":"Si Philip et Elizabeth sont sur la même case que Ned, espionne. Sinon, affiche un message à l'écran." + } + } +} diff --git a/app0-2017/blockly_app0_step3/run b/app0-2017/blockly_app0_step3/run new file mode 100644 index 0000000..a2acda3 --- /dev/null +++ b/app0-2017/blockly_app0_step3/run @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +# Auteur(s) : Florian Thuin +# This file is part of INGInious +import os +import subprocess +import shlex +from inginious import feedback +from inginious import input + + +if __name__ == "__main__": + os.chdir("student") + input.parse_template("maze.tpl.py") + + p = subprocess.Popen(shlex.split("python3 maze.tpl.py"), stderr=subprocess.STDOUT, stdout=subprocess.PIPE) + make_output = p.communicate()[0].decode('utf-8') + + if p.returncode: + feedback.set_global_result("failed") + feedback.set_global_feedback("La compilation de votre code a échoué. Voici l'erreur:" + make_output) + # feedback.set_global_feedback(rst.get_codeblock('', make_output), True) + exit(0) + elif "True" in make_output: + feedback.set_global_result("success") + feedback.set_global_feedback("Vous avez résolu l'exercice. En moyenne, vous avez fait"+make_output.replace("True","")+" pas.") + #feedback.set_global_feedback("Vous avez résolu l'exercice.") + feedback.set_problem_feedback("Bien","code") #attention! code est l'id de la sous-question + else: + feedback.set_global_result("failed") + tab = make_output.split("###") + feedback.set_global_feedback("Vous n'avez pas résolu cette instance. "+tab[1]) + feedback.set_grade(tab[2]) + feedback.set_problem_feedback(tab[0],"code") diff --git a/app0-2017/blockly_app0_step3/student/maze.tpl.py b/app0-2017/blockly_app0_step3/student/maze.tpl.py new file mode 100644 index 0000000..57732a9 --- /dev/null +++ b/app0-2017/blockly_app0_step3/student/maze.tpl.py @@ -0,0 +1,263 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +''' +This file is a bit messed up because it tests Python code generated from code also tested in javascript equivalent. +Try to forget the basic Python syntax for a while. +''' +import json +import os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +data = "" +with open(dir_path.replace("student","public/")+'maze_config.json') as f: + data = json.load(f) + +class BadPathException(Exception): + pass + +class StepNumberExceededException(Exception): + pass + +UNSET = "UNSET" +SUCCESS = "SUCCESS" +FAILURE = "FAILURE" +TIMEOUT = "TIMEOUT" +ERROR = "ERROR" +STEPS = 0 +MAXSTEPS = data["map"]["maxSteps"] + +RESULT_TYPE = { + UNSET: 0, + SUCCESS: 1, + FAILURE: -1, + TIMEOUT: 2, + ERROR: -2 +} + + + +WALL = "WALL" +OPEN = "OPEN" +START = "START" +FINISH = "FINISH" +OBSTACLE = "OBSTACLE" + +SQUARE_TYPE = data["map"]["squareType"] + +PLAYER_POSITION = { + 'x': None, + 'y': None +} + +FINISH_POSITION = { + 'x': None, + 'y': None +} + +FINISHED = False + +EAST = "EAST" +SOUTH = "SOUTH" +WEST = "WEST" +NORTH = "NORTH" + +DIRECTION_TYPE = { + NORTH: 0, + EAST: 1, + SOUTH: 2, + WEST: 3 +} + +MOVE_POSITION = { + DIRECTION_TYPE[EAST]: { + 'x': 1, + 'y': 0 + }, + DIRECTION_TYPE[SOUTH]: { + 'x': 0, + 'y': 1 + }, + DIRECTION_TYPE[WEST]: { + 'x': -1, + 'y': 0 + }, + DIRECTION_TYPE[NORTH]: { + 'x': 0, + 'y': -1 + } +} + +MAP = "" +ROWS = 0 +COLS = 0 +RESULT = RESULT_TYPE[UNSET] +PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + +def student_code(): +@ @code@@ + + +def init(map): + global ROWS,COLS,RESULT,PLAYER_ORIENTATION,MAP + MAP = map + ROWS = len(map) + COLS = len(map[0]) + RESULT = RESULT_TYPE[UNSET] + PLAYER_ORIENTATION = DIRECTION_TYPE[data["map"]["startDirection"]] + for y in range(ROWS): + for x in range(COLS): + if MAP[y][x] == SQUARE_TYPE[START]: + PLAYER_POSITION['x'] = x + PLAYER_POSITION['y'] = y + if MAP[y][x] == SQUARE_TYPE[FINISH]: + FINISH_POSITION['x'] = x + FINISH_POSITION['y'] = y + +def constrain_direction4(direction): + d = direction % 4 + if d < 0: + d += 4 + return d + +def getPlayerX(): + return PLAYER_POSITION['x'] + +def getPlayerY(): + return PLAYER_POSITION['y'] + +def getTargetX(): + return FINISH_POSITION['x'] + +def getTargetY(): + return FINISH_POSITION['y'] + +def getPlayerDir(): + return PLAYER_ORIENTATION + +def canMove(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] + +def isInFrontOfEnemy(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = PLAYER_ORIENTATION + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + +def isOnTarget(): + return PLAYER_POSITION['y'] == FINISH_POSITION['y'] and PLAYER_POSITION['x'] == FINISH_POSITION['x'] + +def spyOnTarget(): + global FINISHED + if(isOnTarget()): + FINISHED = True + +def isPath(direction): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, SQUARE_TYPE, WALL, ROWS, COLS, DIRECTION_TYPE + effective_direction = constrain_direction4(PLAYER_ORIENTATION + direction) + test_x = PLAYER_POSITION['x'] + MOVE_POSITION[effective_direction]['x'] + test_y = PLAYER_POSITION['y'] + MOVE_POSITION[effective_direction]['y'] + if test_x < 0 or test_x >= COLS: + return False + elif test_y < 0 or test_y >= ROWS: + return False + else: + return not MAP[test_y][test_x] == SQUARE_TYPE[WALL] and not MAP[test_y][test_x] == SQUARE_TYPE[OBSTACLE] + + +def isPathForward(): + return isPath(0) + + +def isPathRight(): + return isPath(1) + + +def isPathBackward(): + return isPath(2) + + +def isPathLeft(): + return isPath(3) + + +def moveForward(): + global PLAYER_POSITION, PLAYER_ORIENTATION, MOVE_POSITION, STEPS, MAXSTEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + elif isPathForward(): + PLAYER_POSITION['x'] = PLAYER_POSITION['x'] + MOVE_POSITION[PLAYER_ORIENTATION]['x'] + PLAYER_POSITION['y'] = PLAYER_POSITION['y'] + MOVE_POSITION[PLAYER_ORIENTATION]['y'] + STEPS += 1 + else: + raise BadPathException() + + +def turnLeft(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[EAST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[WEST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def turnRight(): + global PLAYER_ORIENTATION, STEPS + if (STEPS + 1 > MAXSTEPS and MAXSTEPS != -1) or STEPS > 10000: + raise StepNumberExceededException() + PLAYER_ORIENTATION = {DIRECTION_TYPE[EAST]: DIRECTION_TYPE[SOUTH], + DIRECTION_TYPE[SOUTH]: DIRECTION_TYPE[WEST], + DIRECTION_TYPE[WEST]: DIRECTION_TYPE[NORTH], + DIRECTION_TYPE[NORTH]: DIRECTION_TYPE[EAST] + }[PLAYER_ORIENTATION] + STEPS += 1 + + +def isDone(): + global FINISHED + return FINISHED + + +def notDone(): + return not isDone() +allsteps = 0 +total = len(data["map"]["layout"]) +for i in range(total): + init(data["map"]["layout"][i]) + try: + student_code() + if isOnTarget() and notDone(): + print(str(data["map"]["layout"][i])+"###Vous y êtes presque ! Votre presonnage atteint le but mais il manque une action.###"+str((i/total)*100)) + quit() + elif notDone(): + print(str(data["map"]["layout"][i])+"### ###"+str((i/total)*100)) + quit() + allsteps += STEPS + STEPS = 0 + except BadPathException: + print(str(data["map"]["layout"][i])+"###Le personnage emprunte un chemin inexistant.###"+str((i/total)*100)) + quit() + except StepNumberExceededException: + print(str(data["map"]["layout"][i])+"###Le personnage fait trop de pas ("+str(STEPS)+").###"+str((i/total)*100)) + quit() + +print("True "+str(allsteps/30)) + diff --git a/app0-2017/blockly_app0_step3/task.yaml b/app0-2017/blockly_app0_step3/task.yaml new file mode 100644 index 0000000..6e75093 --- /dev/null +++ b/app0-2017/blockly_app0_step3/task.yaml @@ -0,0 +1,91 @@ +accessible: true +author: Florian Thuin +context: '' +environment: default +evaluate: best +groups: false +input_random: '0' +limits: + time: '30' + output: '2' + memory: '100' +name: 'Troisième étape : des ennemis' +network_grading: false +order: 0 +problems: + code: + options: + maxBlocks: Infinity + scrollbars: true + media: /static/common/js/blockly/media/ + toolboxPosition: start + css: true + trashcan: true + sounds: true + visual: + position: left + oneBasedIndex: true + name: '' + header: '' + files: + - maze.js + - interpreter.js + blocks_files: + - blocks.js + type: blockly + workspace: '' + toolbox: |- + + + + + + + + + + + + + EQ + + + + AND + + + TRUE + + + + + + turnLeft + + + + + + + + WHILE + + + 0 + + + + + X + + + X + + + +stored_submissions: 0 +submission_limit: + amount: -1 + period: -1 +tags: {} +weight: 1.0 diff --git a/app0-2017/course.yaml b/app0-2017/course.yaml new file mode 100644 index 0000000..61a5c9d --- /dev/null +++ b/app0-2017/course.yaml @@ -0,0 +1,25 @@ +name: '[APP0] Game of Spies' +admins: +- gegoa +- taffin +- obonaventure +- duchenef +- reinbold +- detiennen +- ogoletti +- bnothomb +accessible: true +tutors: [] +groups_student_choice: false +use_classrooms: true +registration: true +registration_password: null +registration_ac: null +registration_ac_list: +- '' +allow_unregister: true +description: '' +allow_preview: false +is_lti: false +lti_keys: {} +lti_send_back_grade: false