ソースを参照

created README.md file
the game now displays progress bar while preloading assets (this feature was lost when removing phaser)
no longer uses xmlhttprequest, only fetch api
removed some unused images
fixed some unintentional global variable declarations
code refactoring and documenting

laira 3 年 前
コミット
63028a4ab7

+ 45 - 0
README.md

@@ -0,0 +1,45 @@
+# iFractions
+
+## About
+
+**iFractions** is an online collection of games for teaching fractions currently being developed by LInE (Laboratório de Informática na Educação).
+
+It can be used both on a server or as an activity inside Moodle.
+
+Play iFractions online: http://www.usp.br/line/ifractions/
+
+## How to use this code
+
+Follow the description below based on in which platform you want to run iFractions:
+
+#### 1) on a server
+
+* Extract the content of **Ifraction-web.zip** inside the server directory
+
+* Check that the variable **moodle** is set to **false** inside **/js/globals.js**
+
+* It can now be accessed through the **browser** (e.g http://localhost/Ifractions-web)
+
+* If you want to save the players information on your database:
+
+  * you'll need to have **PHP** and **MySQL** installed on the machine
+
+  * follow the steps on **/php/README.md** to setup the database and variables
+
+* Otherwise you're good to go.
+
+#### 2) on Moodle
+
+iFractions is one of the iLM (Interactive Learning Modules) provided by the iAssign package for Moodle. 
+
+* To download and setup iAssign access: http://200.144.254.107/git/LInE/iassign
+
+* Be sure to check that the variable **moodle** is set to **true** inside **/js/globals.js**
+
+* With iAssign installed, as a Moodle professor on you'll be able to:
+  
+  * create activities for your course that are customizable iFractions games
+  
+  * analyze the student's progress on the activities
+
+  * get the automatic evaluation for the activities

BIN
assets/img/NOT-USED/block.png


BIN
assets/img/NOT-USED/eraser.png


BIN
assets/img/NOT-USED/fractionLine.png


BIN
assets/img/NOT-USED/frame-0.png


BIN
assets/img/NOT-USED/frame-1.png


BIN
assets/img/NOT-USED/frame-2.png


BIN
assets/img/NOT-USED/frame-3.png


BIN
assets/img/NOT-USED/frame-4.png


BIN
assets/img/NOT-USED/frame-5.png


BIN
assets/img/NOT-USED/frame-6.png


BIN
assets/img/NOT-USED/frame-7.png


BIN
assets/img/NOT-USED/frame-8.png


BIN
assets/img/NOT-USED/frame-9.png


BIN
assets/img/NOT-USED/pgbar.png


BIN
assets/img/NOT-USED/progressBar.png


+ 3 - 2
assets/lang/en_US

@@ -7,7 +7,6 @@ difficulties=Difficulties
 difficulty=Difficulty
 empty_name=You forgot to type your name
 equals=Equality
-error_moodle_file_ext=empty (File with extension .frc not found)
 error_must_select_game=You must select at least one game!
 game=Game
 game_mode=Game Mode
@@ -52,4 +51,6 @@ welcome=Hi
 hits=Hits
 errors=Errors
 time=Time
-results=RESULTS
+results=RESULTS
+student=Student
+professor=Professor

+ 3 - 2
assets/lang/es_PE

@@ -7,7 +7,6 @@ difficulties=Dificultades
 difficulty=Dificultade
 empty_name=Usted ha olvidado de escribir su nombre
 equals=Igualdad
-error_moodle_file_ext=vacío (archivo con extensión .frc no encontrado) 
 error_must_select_game=Debes seleccionar al menos un juego! 
 game=Juego
 game_mode=Modo de Juego
@@ -52,4 +51,6 @@ welcome=Hola
 hits=Aciertos
 errors=Errores
 time=Tiempo
-results=RESULTADOS
+results=RESULTADOS
+student=Estudiante
+professor=Professor

+ 3 - 2
assets/lang/fr_FR

@@ -7,7 +7,6 @@ difficulties=Difficultés
 difficulty=Difficulté
 empty_name=Vous avez oublié de taper votre nom
 equals=Égalité
-error_moodle_file_ext=empty (Fichier avec extension .frc introuvable)
 error_must_select_game=vous devez sélectionner au moins un jeu!
 game=Jeu
 game_mode=Mode de Jeu
@@ -52,4 +51,6 @@ welcome=Salut
 hits=Réussites
 errors=Erreurs
 time=Temps
-results=RÉSULTATS
+results=RÉSULTATS
+student=Étudiant
+professor=Professeur

+ 3 - 2
assets/lang/it_IT

@@ -7,7 +7,6 @@ difficulties=Difficoltà
 difficulty=Difficoltà
 empty_name=Ti sei dimenticato di digitare il tuo nome
 equals=Uguaglianza
-error_moodle_file_ext=vuoto (File con estensione .frc non trovato) 
 error_must_select_game=Devi selezionare almeno un gioco!
 game=Gioco
 game_mode=Modalità di Gioco
@@ -52,4 +51,6 @@ welcome=Ciao
 hits=Successi
 errors=Errori
 time=Tempo
-results=RISULTATI
+results=RISULTATI
+student=Alunno
+professor=Professore

+ 3 - 2
assets/lang/pt_BR

@@ -7,7 +7,6 @@ difficulties=Dificuldades
 difficulty=Dificuldade
 empty_name=Você esqueceu de digitar seu nome
 equals=Igualdade
-error_moodle_file_ext=vazio (Arquivo com extensão .frc não encontrado)
 error_must_select_game=Você precisa selecionar pelo menos um jogo!
 game=Jogo
 game_mode=Modo de Jogo
@@ -52,4 +51,6 @@ welcome=Olá
 hits=Acertos
 errors=Erros
 time=Tempo
-results=RESULTADOS
+results=RESULTADOS
+student=Aluno
+professor=Professor

+ 12 - 21
index.html

@@ -1,8 +1,5 @@
 <!DOCTYPE html>
-<!--
-LInE - Free Education, Private Data.
-www.usp.br/line
--->
+<!-- LInE - Free Education, Private Data - http://www.usp.br/line -->
 
 <html>
 
@@ -12,7 +9,7 @@ www.usp.br/line
   <meta name="description" content="Educational game for teaching fractions" />
   <meta name="keywords" content="ifractions, fraction, game, private data" />
   <link rel="shortcut icon" href="assets/img/scene/flag.png">
-  <title> iFractions by LInE-IME-USP</title>
+  <title> iFractions by LInE</title>
   <link rel="stylesheet" href="css/bootstrap.min.css">
 
   <style>
@@ -95,8 +92,7 @@ www.usp.br/line
       font-weight: bold;
     }
 
-    .close:hover,
-    .close:focus {
+    .close:hover, .close:focus {
       color: #000;
       text-decoration: none;
       cursor: pointer;
@@ -118,19 +114,13 @@ www.usp.br/line
 
       <div class="panel-body">
 
-        <!-- iFractions game -->
-        <canvas id="iFractions-canvas"></canvas>
-
-        <!-- Textbox to get player name -->
+        <canvas id="iFractions-canvas"></canvas> <!-- iFractions game -->
+        
         <div id="textbox" onsubmit="return false">
-          <input type="text" id="textbox-content" value="" size="13" maxlength="36">
+          <input type="text" id="textbox-content" value="" size="13" maxlength="36"> <!-- Textbox to get player name -->
         </div>
-
-        <!-- Display fps in debugmode -->
-        <div id="display-fps"></div>
-
-        <!-- The Modal -->
-        <div id="myModal" class="modal">
+               
+        <div id="myModal" class="modal">  <!-- Modal -->
           <div class="modal-content">
             <span class="close">&times;</span>
             <div id='infobox-content'></div> <!-- Modal content -->
@@ -155,7 +145,9 @@ www.usp.br/line
     <script src="js/globals.js"></script>
     
     <script>
-      const displayFps = document.getElementById("display-fps");
+
+      const defaultWidth = 900; // Default width for the Canvas
+      const defaultHeight = 600; // Default height for the Canvas
 
       canvas = document.getElementById("iFractions-canvas");
       canvas.width = defaultWidth;
@@ -181,8 +173,7 @@ www.usp.br/line
       game.state.add('circleOne', circleOne);
       game.state.add('squareTwo', squareTwo);
 
-      // FOR MOODLE
-      game.state.add('studentReport', studentReport);
+      game.state.add('studentReport', studentReport); // FOR MOODLE
 
       // CALLING FIRST GAME STATE
       game.state.start('boot');

+ 108 - 113
js/circleOne.js

@@ -1,25 +1,33 @@
-/**
- * LInE - Free Education, Private Data
- *
- * iFractions GAME STATE
+/******************************
+ * This file holds game states.
+ ******************************/
+
+/** [GAME STATE]
  *
- * Name of game state : 'circleOne'
- * Shape : circle
+ * .....circleOne.... = gameType
+ * ....../....\......
+ * .....A......B..... = gameMode
+ * .......\./........
+ * ........|.........
+ * ....../.|.\.......
+ * .Plus.Minus.Mixed. = gameOperation
+ * ......\.|./.......
+ * ........|.........
+ * ....1,2,3,4,5..... = gameDifficulty
+ * 
  * Character : kid/balloon
  * Theme : flying in a balloon
  * Concept : 'How much the kid has to walk to get to the balloon?'
- * Represent fractions as : circles
- *
- * # of different difficulties : 5
+ * Represent fractions as : circles/arcs
  *
- * Game modes can be : 'A' or 'B' (in variable 'gameMode')
+ * Game modes can be :
  *
  *   A : Player can place balloon position
  *       Place balloon in position (so the kid can get to it)
  *   B : Player can select # of circles
  *       Selects number of circles (that represent distance kid needs to walk to get to the balloon)
  *
- * Operations can be : 'Plus', 'Minus' or 'Mixed' (in variable 'gameOperation')
+ * Operations can be :
  *
  *   Plus : addition of fractions
  *     Represented by : kid going to the right (floor positions 0..5)
@@ -83,16 +91,16 @@ const circleOne = {
 
     // FOR MOODLE
     if (moodle) {
-      navigationIcons.func_addIcons(
+      navigationIcons.add(
         false, false, false, // Left buttons
         true, false,         // Right buttons
         false, false
       );
     } else {
-      navigationIcons.func_addIcons(
+      navigationIcons.add(
         true, true, true, // Left buttons
         true, false,      // Right buttons
-        'customMenu', this.func_viewHelp
+        'customMenu', this.viewHelp
       );
     }
 
@@ -262,8 +270,8 @@ const circleOne = {
 
     if (!this.restart) {
       game.timer.start(); // Set a timer for the current level (used in postScore())
-      game.event.add('click', this.func_onInputDown);
-      game.event.add('mousemove', this.func_onInputOver);
+      game.event.add('click', this.onInputDown);
+      game.event.add('mousemove', this.onInputOver);
     }
   },
 
@@ -351,13 +359,13 @@ const circleOne = {
 
       game.animation.stop(self.kid.animation[0]);
 
-      if (self.func_checkOverlap(self.basket, self.kid)) {
+      if (self.checkOverlap(self.basket, self.kid)) {
         self.result = true; // Answer is correct
         self.kid.curFrame = (self.kid.curFrame < 12) ? 24 : 25;
         if (audioStatus) game.audio.okSound.play();
         game.add.image(defaultWidth / 2, defaultHeight / 2, 'ok').anchor(0.5, 0.5);
         completedLevels++;
-        if (debugMode) console.log('completedLevels = ' + completedLevels);
+        if (debugMode) console.log('Completed Levels: ' + completedLevels);
       } else {
         self.result = false; // Answer is incorrect
         if (audioStatus) game.audio.errorSound.play();
@@ -390,86 +398,12 @@ const circleOne = {
     game.render.all();
   },
 
-
-  /* EVENT HANDLER */
-
-  /**
-   * Called by mouse click event
-   *
-   * @param {object} mouseEvent contains the mouse click coordinates
-   */
-  func_onInputDown: function (mouseEvent) {
-    const x = mouseEvent.offsetX;
-    const y = mouseEvent.offsetY;
-
-    // GAME MODE A : click road
-    if (gameMode == 'A') {
-      const cur = self.road;
-
-      const valid = y > 60 && (x >= cur.xWithAnchor && x <= (cur.xWithAnchor + cur.width * cur.scale));
-      if (valid) self.func_clicked(x);
-    }
-
-    // GAME MODE B : click circle
-    if (gameMode == 'B') {
-      self.circles.all.forEach(cur => {
-        const valid = game.math.distanceToPointer(x, cur.xWithAnchor, y, cur.yWithAnchor) <= (cur.diameter / 2) * cur.scale;
-        if (valid) self.func_clicked(cur);
-      });
-    }
-
-    navigationIcons.func_onInputDown(x, y);
-
-    game.render.all();
-  },
-
-  /**
-   * Called by mouse move event
-   *
-   * @param {object} mouseEvent contains the mouse move coordinates
-   */
-  func_onInputOver: function (mouseEvent) {
-    const x = mouseEvent.offsetX;
-    const y = mouseEvent.offsetY;
-    let flag = false;
-
-    // GAME MODE A : balloon follow mouse
-    if (gameMode == 'A' && !self.hasClicked) {
-      if (game.math.distanceToPointer(x, self.balloon.x, y, self.balloon.y) > 8) {
-        self.balloon.x = x;
-        self.basket.x = x;
-      }
-
-      document.body.style.cursor = 'auto';
-    }
-
-    // GAME MODE B : hover circle
-    if (gameMode == 'B' && !self.hasClicked) {
-      self.circles.all.forEach(cur => {
-        const valid = game.math.distanceToPointer(x, cur.xWithAnchor, y, cur.yWithAnchor) <= (cur.diameter / 2) * cur.scale;
-        if (valid) {
-          self.func_overCircle(cur);
-          flag = true;
-        }
-      });
-      if (!flag) self.func_outCircle();
-    }
-
-    navigationIcons.func_onInputOver(x, y);
-
-    game.render.all();
-  },
-
-  /* CALLED BY EVENT HANDLER */
-
   /**
-   * (in gameMode 'B') <br>
-   * 
-   * Function called when cursor is over a valid circle
+   * (in gameMode 'B') Function called when cursor is over a valid circle
    * 
    * @param {object} cur circle the cursor is over
    */
-  func_overCircle: function (cur) {
+  overCircle: function (cur) {
     if (!self.hasClicked) {
       document.body.style.cursor = 'pointer';
       for (let i in self.circles.all) {
@@ -479,11 +413,9 @@ const circleOne = {
   },
 
   /**
-   * (in gameMode 'B') <br>
-   * 
-   * Function called when cursor is out of a valid circle
+   * (in gameMode 'B') Function called when cursor is out of a valid circle
    */
-  func_outCircle: function () {
+  outCircle: function () {
     if (!self.hasClicked) {
       document.body.style.cursor = 'auto';
       self.circles.all.forEach(cur => {
@@ -493,13 +425,11 @@ const circleOne = {
   },
 
   /**
-   * (in gameMode 'B') <br>
-   * 
-   * Function called when player clicked over a valid circle
+   * (in gameMode 'B') Function called when player clicked over a valid circle
    * 
    * @param {number|object} cur clicked circle
    */
-  func_clicked: function (cur) {
+  clicked: function (cur) {
     if (!self.hasClicked) {
 
       // On gameMode A
@@ -517,7 +447,7 @@ const circleOne = {
             self.circles.all[i].alpha = 1; // Keep selected circle
             self.fractionIndex = cur.index;
           } else {
-            self.circles.all[i].alpha = 0;   // Hide unselected circle
+            self.circles.all[i].alpha = 0;  // Hide unselected circle
             self.kid.y += self.circles.diameter;  // Lower kid to selected circle
           }
         }
@@ -545,16 +475,15 @@ const circleOne = {
     }
   },
 
-  /* GAME FUNCTIONS */
-
   /**
    * Checks if 2 images overlap
    * 
    * @param {object} spriteA image 1
    * @param {object} spriteB image 2
-   * @returns {boolean}
+   * 
+   * @returns {boolean} true if there is overlap
    */
-  func_checkOverlap: function (spriteA, spriteB) {
+  checkOverlap: function (spriteA, spriteB) {
     const xA = spriteA.x;
     const xB = spriteB.x;
 
@@ -566,7 +495,7 @@ const circleOne = {
   /**
    * Display correct answer
    */
-  func_viewHelp: function () {
+  viewHelp: function () {
     if (!self.hasClicked) {
       // On gameMode A
       if (gameMode == 'A') {
@@ -581,12 +510,78 @@ const circleOne = {
     }
   },
 
-  /* METADATA FOR GAME */
+  /**
+   * Called by mouse click event
+   *
+   * @param {object} mouseEvent contains the mouse click coordinates
+   */
+  onInputDown: function (mouseEvent) {
+    const x = mouseEvent.offsetX;
+    const y = mouseEvent.offsetY;
+
+    // GAME MODE A : click road
+    if (gameMode == 'A') {
+      const cur = self.road;
+
+      const valid = y > 60 && (x >= cur.xWithAnchor && x <= (cur.xWithAnchor + cur.width * cur.scale));
+      if (valid) self.clicked(x);
+    }
+
+    // GAME MODE B : click circle
+    if (gameMode == 'B') {
+      self.circles.all.forEach(cur => {
+        const valid = game.math.distanceToPointer(x, cur.xWithAnchor, y, cur.yWithAnchor) <= (cur.diameter / 2) * cur.scale;
+        if (valid) self.clicked(cur);
+      });
+    }
+
+    navigationIcons.onInputDown(x, y);
+
+    game.render.all();
+  },
+
+  /**
+   * Called by mouse move event
+   *
+   * @param {object} mouseEvent contains the mouse move coordinates
+   */
+  onInputOver: function (mouseEvent) {
+    const x = mouseEvent.offsetX;
+    const y = mouseEvent.offsetY;
+    let flag = false;
+
+    // GAME MODE A : balloon follow mouse
+    if (gameMode == 'A' && !self.hasClicked) {
+      if (game.math.distanceToPointer(x, self.balloon.x, y, self.balloon.y) > 8) {
+        self.balloon.x = x;
+        self.basket.x = x;
+      }
+
+      document.body.style.cursor = 'auto';
+    }
+
+    // GAME MODE B : hover circle
+    if (gameMode == 'B' && !self.hasClicked) {
+      self.circles.all.forEach(cur => {
+        const valid = game.math.distanceToPointer(x, cur.xWithAnchor, y, cur.yWithAnchor) <= (cur.diameter / 2) * cur.scale;
+        if (valid) {
+          self.overCircle(cur);
+          flag = true;
+        }
+      });
+      if (!flag) self.outCircle();
+    }
+
+    navigationIcons.onInputOver(x, y);
+
+    game.render.all();
+  },
 
   /**
    * Saves players data after level ends - to be sent to database <br>
    * 
-   * Attention: the "line_" prefix data table must be compatible to data table fields (MySQL server)
+   * Attention: the 'line_' prefix data table must be compatible to data table fields (MySQL server)
+   * 
    * @see /php/squareOne.js
    */
   postScore: function () {
@@ -605,7 +600,7 @@ const circleOne = {
       + ', selIndex: ' + self.fractionIndex;
 
     // FOR MOODLE
-    if (moodle) sendToDB(data, self.result, game.timer.elapsed);
-    else sendToDB(data);
+    sendToDB(data);
   }
-};
+
+};

+ 42 - 32
js/customMenu.js

@@ -1,5 +1,8 @@
-/**
- * SECUNDARY MENU STATE: player can select game mode, math operation and overall game difficulty
+/******************************
+ * This file holds game states.
+ ******************************/
+
+/** [CUSTOM MENU STATE] Screen where the user can customise the selected game - game mode, math operation, level of difficulty.
  * 
  * @namespace
  */
@@ -22,8 +25,10 @@ const customMenuState = {
   create: function () {
 
     // FOR MOODLE
-    if (moodle && iLMparameters.iLM_PARAM_SendAnswer == 'false') {
+    if (moodle && iLMparameters.iLM_PARAM_SendAnswer == 'false') { // Student role
+
       game.state.start('map');
+
     } else {
 
       const iconScale = 0.7;
@@ -41,7 +46,7 @@ const customMenuState = {
       game.add.text(defaultWidth / 2, 80, game.lang.custom_game, textStyles.h1_green);
 
       // Loads navigation icons
-      navigationIcons.func_addIcons(
+      navigationIcons.add(
         true, false, false,
         true, true,
         'menu', false);
@@ -122,7 +127,7 @@ const customMenuState = {
 
       x = 150 + offsetW;
       y = baseY;
-      offsetH = this.func_getOffset(height, info[gameTypeString].gameMode.length);
+      offsetH = this.getOffset(height, info[gameTypeString].gameMode.length);
 
       for (let i = 0; i < info[gameTypeString].gameModeUrl.length; i++, y += offsetH) {
         const icon = game.add.sprite(x, y, info[gameTypeString].gameModeUrl[i], 0, iconScale, 1);
@@ -142,7 +147,7 @@ const customMenuState = {
 
       x += 2 * offsetW;
       y = baseY;
-      offsetH = this.func_getOffset(height, info[gameTypeString].gameOperation.length);
+      offsetH = this.getOffset(height, info[gameTypeString].gameOperation.length);
 
       let icon;
       let aux = [];
@@ -324,8 +329,8 @@ const customMenuState = {
 
       // ------------- EVENTS
 
-      game.event.add('click', this.func_onInputDown);
-      game.event.add('mousemove', this.func_onInputOver);
+      game.event.add('click', this.onInputDown);
+      game.event.add('mousemove', this.onInputOver);
 
     }
 
@@ -334,25 +339,24 @@ const customMenuState = {
   /**
    * Displays game menu information boxes.
    */
-  func_showInfoBox: function (icon) {
+  showInfoBox: function (icon) {
     self.infoBox.style.display = 'block';
 
     const element = (icon.id == 'gameOperation') ? self.infoBoxContent[icon.id] : self.infoBoxContent[icon.id][gameTypeString];
 
     let msg = '<h3>' + element.title + '</h3>'
-      + '<p>' + element.body + '</p>'
+      + '<p align=justify>' + element.body + '</p>'
       + element.img;
 
     document.getElementById('infobox-content').innerHTML = msg;
   },
 
-
   /**
    * Saves information selected by the player 
    * 
    * @param {object} icon selected icon
    */
-  func_load: function (icon) {
+  load: function (icon) {
 
     if (audioStatus) game.audio.beepSound.play();
 
@@ -361,7 +365,7 @@ const customMenuState = {
       case 'gameMode': gameMode = icon.gameMode; break;
       case 'gameOperation': gameOperation = icon.gameOperation; break;
       case 'difficulty': gameDifficulty = icon.difficulty; break;
-      case 'infoIcon': self.func_showInfoBox(icon); break;
+      case 'infoIcon': self.showInfoBox(icon); break;
       case 'selectionBox':
         if (icon.curFrame == 0) {
           icon.curFrame = 1;
@@ -373,9 +377,14 @@ const customMenuState = {
         game.render.all();
         break;
       case 'enter':
-        if (debugMode) console.log('Game State: ' + gameTypeString + ', ' + gameMode);
-        mapPosition = 0;      // Map position
-        mapMove = true;       // Move no next point
+        if (debugMode) {
+          console.log('------------------------------'+
+          '\nGame State: ' + gameTypeString +
+          '\nGame Mode: ' + gameMode + 
+          '\n------------------------------');
+        }
+        mapPosition = 0;  // Map position
+        mapMove = true; // Move no next point
         completedLevels = 0;  // Reset the game progress when entering a new level
         game.state.start('map');
         break;
@@ -388,9 +397,10 @@ const customMenuState = {
    * 
    * @param {number} width width of the available part of the screen
    * @param {number} numberOfIcons number or icons to be put on the screen
-   * @returns {number}
+   * 
+   * @returns {number} correct spacing between icons
    */
-  func_getOffset: function (width, numberOfIcons) {
+  getOffset: function (width, numberOfIcons) {
     return width / (numberOfIcons + 1);
   },
 
@@ -399,7 +409,7 @@ const customMenuState = {
    * 
    * @param {object} mouseEvent contains the mouse click coordinates
    */
-  func_onInputDown: function (mouseEvent) {
+  onInputDown: function (mouseEvent) {
     const x = mouseEvent.offsetX, y = mouseEvent.offsetY;
     let overIcon;
 
@@ -412,11 +422,11 @@ const customMenuState = {
     }
 
     // Update gui
-    if (overIcon) { // if has clicked on an icon
+    if (overIcon) { // If has clicked on an icon
       document.body.style.cursor = 'pointer';
       self.menuIcons.forEach(cur => {
-        if (cur.iconType == self.menuIcons[overIcon].iconType) { // if its in the same icon category
-          if (cur == self.menuIcons[overIcon]) { // if its the clicked icon
+        if (cur.iconType == self.menuIcons[overIcon].iconType) { // If its in the same icon category
+          if (cur == self.menuIcons[overIcon]) { // If its the clicked icon
             if (cur.iconType == 'gameMode' || cur.iconType == 'gameOperation') cur.curFrame = 1;
             else if (cur.iconType == 'difficulty') cur.fillColor = colors.blue;
           } else {
@@ -426,11 +436,11 @@ const customMenuState = {
         }
       });
 
-      self.func_load(self.menuIcons[overIcon]);
+      self.load(self.menuIcons[overIcon]);
 
     } else document.body.style.cursor = 'auto';
 
-    navigationIcons.func_onInputDown(x, y);
+    navigationIcons.onInputDown(x, y);
 
     game.render.all();
 
@@ -441,7 +451,7 @@ const customMenuState = {
    * 
    * @param {object} mouseEvent contains the mouse move coordinates
    */
-  func_onInputOver: function (mouseEvent) {
+  onInputOver: function (mouseEvent) {
     const x = mouseEvent.offsetX, y = mouseEvent.offsetY;
     let overIcon;
 
@@ -454,11 +464,11 @@ const customMenuState = {
     }
 
     // Update gui
-    if (overIcon) { // if pointer is over icon
+    if (overIcon) { // If pointer is over icon
       document.body.style.cursor = 'pointer';
       self.menuIcons.forEach(cur => {
-        if (cur.iconType == self.menuIcons[overIcon].iconType) { // if its in the same icon category
-          if (cur == self.menuIcons[overIcon]) { // if its the icon the pointer is over 
+        if (cur.iconType == self.menuIcons[overIcon].iconType) { // If its in the same icon category
+          if (cur == self.menuIcons[overIcon]) { // If its the icon the pointer is over 
             if (cur.iconType == 'enter') self.enterText.style = textStyles.h3__white;
             cur.scale = cur.originalScale * 1.1;
           } else {
@@ -466,17 +476,17 @@ const customMenuState = {
           }
         }
       });
-    } else { // if pointer is not over icon
-      self.enterText.style = textStyles.h4_white;
+    } else { // If pointer is not over icon
+      if (self.enterText) self.enterText.style = textStyles.h4_white;
       self.menuIcons.forEach(cur => { cur.scale = cur.originalScale; });
       document.body.style.cursor = 'auto';
     }
 
     // Check navigation icons
-    navigationIcons.func_onInputOver(x, y);
+    navigationIcons.onInputOver(x, y);
 
     game.render.all();
 
-  },
+  }
 
 }

ファイルの差分が大きいため隠しています
+ 506 - 432
js/gameMechanics.js


+ 143 - 135
js/globals.js

@@ -1,66 +1,64 @@
-// LInE - Free Education, Private Data.
-
-/*
-
-Generating game levels in menu:
-
-..................................................... 
-...............square....................circle...... }				        	} (gameShape)
-.........../...........\....................|........ } game (gameType)
-........One.............Two................One....... }
-......./...\.........../...\............./....\...... 
-......A.....B.........A.....B...........A......B..... } game mode (gameMode)
-.(floor)..(stack)..(top)..(bottom)..(floor)..(stack).
-.......\./.............\./................\./........ 
-........|...............|..................|......... 
-......./.\..............|................/.|.\....... 
-...Plus...Minus.......Equals........Plus.Minus.Mixed. } game math operation (gameOperation)
-.......\./..............|................\.|./....... 
-........|...............|..................|......... 
-......1,2,3.........1,2,3,4,5..........1,2,3,4,5..... } difficulty level (gameDifficulty)
-..................................................... 
-
-About levels in map:
-
-..................(game.levels)......................
-......................__|__..........................
-.....................|.|.|.|.........................
-...................0,1,2,3,4,5....................... } mapPositions (mapPosition)
-...................|.........|.......................
-................(start)....(end).....................
-*/
+/**************************************************************
+ * LInE - Free Education, Private Data - http://www.usp.br/line
+ * 
+ * This file holds all global elements to the game.
+ * 
+ * Generating game levels in menu:
+ * ..................................................... 
+ * ...............square....................circle...... }                   = gameShape
+ * .........../...........\....................|........ } = gameType (game)
+ * ........One.............Two................One....... }
+ * ......./...\.........../...\............./....\...... 
+ * ......A.....B.........A.....B...........A......B..... = gameMode (game mode)
+ * .(floor)..(stack)..(top)..(bottom)..(floor)..(stack).
+ * .......\./.............\./................\./........ 
+ * ........|...............|..................|......... 
+ * ......./.\..............|................/.|.\....... 
+ * ...Plus...Minus.......Equals........Plus.Minus.Mixed. = gameOperation (game math operation)
+ * .......\./..............|................\.|./....... 
+ * ........|...............|..................|......... 
+ * ......1,2,3.........1,2,3,4,5..........1,2,3,4,5..... = gameDifficulty (difficulty level)
+ * ..................................................... 
+ * 
+ * About levels in map:
+ * 
+ * ..................(game.levels)......................
+ * ......................__|__..........................
+ * .....................|.|.|.|.........................
+ * ...................0,1,2,3,4,5....................... = mapPosition (map positions)
+ * ...................|.........|.......................
+ * ................(start)....(end).....................
+ **************************************************************/
 
 /**
  * Turns console messages ON/OFF (for debug purposes only)
  * @type {boolean}
  */
 const debugMode = false;
-// FOR MOODLE
-/**
- * defines if the game is suposed to run online or on moodle <br>
+
+/** FOR MOODLE <br>
+ * 
+ * iFractions can run on a server or inside moodle through iAssign. <br>
+ * This variable should be set according to where it is suposed to run: <br>
  * - if true, on moodle <br>
- * - if false, online
+ * - if false, on a server
  */
 const moodle = false;
 
-const medSrc = 'assets/img/'; // Base directory for media
-const defaultWidth = 900; // Default width for the Canvas
-const defaultHeight = 600; // Default height for the Canvas
-
 /**
  * HTMLCanvasElement : Canvas where all the game elements are rendered.
- * 
  * @type {object}
  */
 let canvas;
 
 /**
- * Selected game object.<br>
+ * Selected game.<br>
  * Can be the objects: squareOne, squareTwo or circleOne.
  * 
  * @type {object}
  */
 let gameType;
+
 /**
  * Name of the selected game.<br>
  * Can be: 'squareOne', 'squareTwo' or 'circleOne'.
@@ -68,6 +66,7 @@ let gameType;
  * @type {string}
  */
 let gameTypeString;
+
 /**
  * Used for text and game information.<br>
  * Shape that makes the name of the game - e.g in 'squareOne' it is 'square'.<br>
@@ -76,6 +75,7 @@ let gameTypeString;
  * @type {string}
  */
 let gameShape;
+
 /**
  * Holds selected game mode.<br>
  * In squareOne/circleOne   can be: 'A' (click on the floor) or 'B' (click on the amount to go/stacked figures).<br>
@@ -84,15 +84,17 @@ let gameShape;
  * @type {string}
  */
 let gameMode;
+
 /**
  * Holds game math operation.<br>
- * In squareOne     can be: 'Plus' (green tractor goes right) or 'Minus' (red tractor goes left).<br>
- * In circleOne     can be: 'Plus' (green tractor goes right), 'Minus' (red tractor goes left) or 'Mixed' (green tractor goes both sides).<br>
- * In squareTwo     can be: 'Equals' (compares two rectangle subdivisions).
+ * In squareOne   can be: 'Plus' (green tractor goes right) or 'Minus' (red tractor goes left).<br>
+ * In circleOne   can be: 'Plus' (green tractor goes right), 'Minus' (red tractor goes left) or 'Mixed' (green tractor goes both sides).<br>
+ * In squareTwo   can be: 'Equals' (compares two rectangle subdivisions).
  * 
  * @type {string}
  */
 let gameOperation;
+
 /**
  * Holds game overall difficulty. 1 (easier) -> n (harder).<br> 
  * In squareOne             can be: 1..3.<br>
@@ -101,21 +103,25 @@ let gameOperation;
  * @type {number}
  */
 let gameDifficulty;
+
 /**
  * Turns displaying the fraction labels on levels ON/OFF
  * @type {boolean}
  */
 let fractionLabel = true;
-/**
- * Character position on the map, aka game levels (1..4: valid; 5: end)
- * @type {number}
- */
-let mapPosition;
+
 /**
  * When true, the character can move to next position in the map
  * @type {boolean}
  */
 let mapMove;
+
+/**
+ * Character position on the map, aka game levels (1..4: valid; 5: end)
+ * @type {number}
+ */
+let mapPosition;
+
 /**
  * Number of finished levels in the map
  * @type {number}
@@ -127,17 +133,20 @@ let completedLevels;
  * @type {boolean}
  */
 let audioStatus = false;
+
 /**
  * Player's name
  * @type {string}
  */
 let playerName;
+
 /**
  * String that contains the selected language.<br>
  * It is the name of the language file.
  * @type {string}
  */
-let langstring;
+let langString;
+
 /**
  * Holds the current state.<br>
  * Is used as if it was a 'this' inside state functions.
@@ -145,6 +154,8 @@ let langstring;
  */
 let self;
 
+const medSrc = 'assets/img/'; // Base directory for media
+
 /**
  * Metadata for all games
  * @type {object}
@@ -189,41 +200,33 @@ const info = {
   gameOperation: [],
   gameDifficulty: [],
 
-  /**
-   * Load values
-   */
+  // When game starts, update values
   start: function () {
-
     info.gameShape = [
       info.squareOne.gameShape,
       info.circleOne.gameShape,
       info.squareTwo.gameShape
     ];
-
     info.gameType = [
       info.squareOne.gameType,
       info.circleOne.gameType,
       info.squareTwo.gameType
     ];
-
     info.gameTypeUrl = [
       info.squareOne.gameTypeUrl,
       info.circleOne.gameTypeUrl,
       info.squareTwo.gameTypeUrl
     ];
-
-    info.gameMode = info.squareOne.gameMode.concat(info.circleOne.gameMode, info.squareTwo.gameMode);
-
-    info.gameModeUrl = info.squareOne.gameModeUrl.concat(info.circleOne.gameModeUrl, info.squareTwo.gameModeUrl);
-
-    info.gameOperation = info.squareOne.gameOperation.concat(info.circleOne.gameOperation, info.squareTwo.gameOperation);
-
     info.gameDifficulty = [
       info.squareOne.gameDifficulty,
       info.circleOne.gameDifficulty,
       info.squareTwo.gameDifficulty
     ];
+    info.gameMode = info.squareOne.gameMode.concat(info.circleOne.gameMode, info.squareTwo.gameMode);
+    info.gameModeUrl = info.squareOne.gameModeUrl.concat(info.circleOne.gameModeUrl, info.squareTwo.gameModeUrl);
+    info.gameOperation = info.squareOne.gameOperation.concat(info.circleOne.gameOperation, info.squareTwo.gameOperation);
   }
+
 };
 
 /**
@@ -289,6 +292,7 @@ const textStyles = {
  * @type {object}
  */
 const url = {
+  //src: 'assets/img/', // Base directory for media
   boot: {
     image: [
       // Scene
@@ -429,7 +433,7 @@ const url = {
       ['kid_run', medSrc + 'character/kid/run.png', 12]
     ],
     audio: []
-  },
+  }
 };
 
 /**
@@ -443,60 +447,68 @@ const navigationIcons = {
    *  * The icons on the left are ordered from left to right. <br>
    *  * The icons on the right are ordered from right to left.
    * 
-   * @param {boolean} leftIcon0 1st left icon 
-   * @param {boolean} leftIcon1 2nd left icon
-   * @param {boolean} leftIcon2 3rd left icon
-   * @param {boolean} rightIcon0 1st right icon
-   * @param {boolean} rightIcon1 2nd right icon
-   * @param {string} state state to be called by the 'back' button
-   * @param {function} help function in the current game state that display correct answer
+   * @param {boolean} leftIcon0 1st left icon (back)
+   * @param {boolean} leftIcon1 2nd left icon (main menu)
+   * @param {boolean} leftIcon2 3rd left icon (solve game)
+   * @param {boolean} rightIcon0 1st right icon (audio)
+   * @param {boolean} rightIcon1 2nd right icon (lang)
+   * @param {undefined|string} state state to be called by the 'back' button (must exist if param 'leftIcon0' is true)
+   * @param {undefined|function} help function in the current game state that display correct answer
    */
-  func_addIcons: function (leftIcon0, leftIcon1, leftIcon2, rightIcon0, rightIcon1, state, help) {
-    this.state = state;
-    this.help = help;
+  add: function (leftIcon0, leftIcon1, leftIcon2, rightIcon0, rightIcon1, state, help) {
 
     let left_x = 10;
     let right_x = defaultWidth - 50 - 10;
-
     this.iconsList = [];
 
     // 'Descriptive labels' for the navigation icons
-    this.left_text = game.add.text(left_x, 73, '', textStyles.h4_brown, 'left');
-    this.right_text = game.add.text(right_x + 50, 73, '', textStyles.h4_brown, 'right');
-
-    // 'Icons' on the LEFT side of the page
-    if (leftIcon0) { // Return to select difficulty screen
-      const icon_back = game.add.image(left_x, 10, 'back');
-      this.iconsList.push(icon_back);
-      left_x += 50; // Offsets value of x for next icon
+    this.left_text = game.add.text(left_x, 73, '', textStyles.h4_brown);
+    this.left_text.align = 'left';
+
+    this.right_text = game.add.text(right_x + 50, 73, '', textStyles.h4_brown);
+    this.right_text.align = 'right';
+
+    // Left icons
+
+    if (leftIcon0) { // Return to previous screen
+      if (!state) {
+        console.error('Game error: You tried to add a \'back\' icon without the \'state\' parameter.');
+      } else {
+        this.state = state;
+        this.iconsList.push(game.add.image(left_x, 10, 'back'));
+        left_x += 50;
+      }
     }
 
     if (leftIcon1) { // Return to main menu screen
-      const icon_list = game.add.image(left_x, 10, 'menu');
-      this.iconsList.push(icon_list);
-      left_x += 50; // Offsets value of x for next icon
+      this.iconsList.push(game.add.image(left_x, 10, 'menu'));
+      left_x += 50;
     }
 
-    if (leftIcon2) { // In some levels, shows solution to the game
-      const icon_help = game.add.image(left_x, 10, 'help');
-      this.iconsList.push(icon_help);
-      left_x += 50; // Offsets value of x for next icon
+    if (leftIcon2) { // Shows solution to the game
+      if (!help) {
+        console.error('Game error: You tried to add a \'game solution\' icon without the \'help\' parameter.');
+      } else {
+        this.help = help;
+        this.iconsList.push(game.add.image(left_x, 10, 'help'));
+        left_x += 50;
+      }
     }
 
-    // 'Icons' on the RIGHT side of the page
+    // Right icons
 
     if (rightIcon0) { // Turns game audio on/off
-      this.icon_audio = game.add.sprite(right_x, 10, 'audio', 1);
-      audioStatus ? this.icon_audio.curFrame = 0 : this.icon_audio.curFrame = 1;
-      this.iconsList.push(this.icon_audio);
-      right_x -= 50; // Offsets value of x for next icon
+      this.audioIcon = game.add.sprite(right_x, 10, 'audio', 1);
+      this.audioIcon.curFrame = audioStatus ? 0 : 1;
+      this.iconsList.push(this.audioIcon);
+      right_x -= 50;
     }
 
     if (rightIcon1) { // Return to select language screen
-      icon_world = game.add.image(right_x, 10, 'language');
-      this.iconsList.push(icon_world);
-      right_x -= 50; // Offsets value of x for next icon
+      this.iconsList.push(game.add.image(right_x, 10, 'language'));
+      right_x -= 50;
     }
+
   },
 
   /**
@@ -504,7 +516,7 @@ const navigationIcons = {
    * 
    * @param {string} state name of the next state
    */
-  func_CallState: function (state) {
+  callState: function (state) {
     if (audioStatus) game.audio.beepSound.play();
 
     game.event.clear(self);
@@ -517,28 +529,28 @@ const navigationIcons = {
    * @param {number} x contains the mouse x coordinate
    * @param {number} y contains the mouse y coordinate
    */
-  func_onInputDown: function (x, y) {
+  onInputDown: function (x, y) {
 
     navigationIcons.iconsList.forEach(cur => {
       if (game.math.isOverIcon(x, y, cur)) {
         const name = cur.name;
         switch (name) {
-          case 'back': navigationIcons.func_CallState(navigationIcons.state); break;
-          case 'menu': navigationIcons.func_CallState('menu'); break;
+          case 'back': navigationIcons.callState(navigationIcons.state); break;
+          case 'menu': navigationIcons.callState('menu'); break;
           case 'help': navigationIcons.help(); break;
-          case 'language': navigationIcons.func_CallState('lang'); break;
+          case 'language': navigationIcons.callState('lang'); break;
           case 'audio':
             if (audioStatus) {
               audioStatus = false;
-              navigationIcons.icon_audio.curFrame = 1;
+              navigationIcons.audioIcon.curFrame = 1;
             } else {
               audioStatus = true;
               if (audioStatus) game.audio.beepSound.play();
-              navigationIcons.icon_audio.curFrame = 0;
+              navigationIcons.audioIcon.curFrame = 0;
             }
             game.render.all();
             break;
-          default: console.log('Game error: error in navigation icon');
+          default: console.error('Game error: error in navigation icon');
         }
       }
     });
@@ -550,20 +562,21 @@ const navigationIcons = {
    * @param {number} x contains the mouse x coordinate
    * @param {number} y contains the mouse y coordinate
    */
-  func_onInputOver: function (x, y) {
+  onInputOver: function (x, y) {
 
     let flag = false;
 
     navigationIcons.iconsList.forEach(cur => {
       if (game.math.isOverIcon(x, y, cur)) {
         flag = true;
-
-        if (cur.name == 'back') navigationIcons.left_text.name = game.lang.nav_back;
-        else if (cur.name == 'menu') navigationIcons.left_text.name = game.lang.nav_menu;
-        else if (cur.name == 'help') navigationIcons.left_text.name = game.lang.nav_help;
-
-        else if (cur.name == 'language') navigationIcons.right_text.name = game.lang.nav_lang;
-        else if (cur.name == 'audio') navigationIcons.right_text.name = game.lang.audio;
+        let name = cur.name;
+        switch (name) {
+          case 'back': navigationIcons.left_text.name = game.lang.nav_back; break;
+          case 'menu': navigationIcons.left_text.name = game.lang.nav_menu; break;
+          case 'help': navigationIcons.left_text.name = game.lang.nav_help; break;
+          case 'language': navigationIcons.right_text.name = game.lang.nav_lang; break;
+          case 'audio': navigationIcons.right_text.name = game.lang.audio; break;
+        }
       }
     });
 
@@ -574,6 +587,7 @@ const navigationIcons = {
       document.body.style.cursor = 'pointer';
     }
   }
+
 };
 
 /**
@@ -598,30 +612,24 @@ const sendToDB = function (extraData) {
     // @see php/save.php
     const data = 'line_ip=143.107.45.11' // INSERT database server IP
       + '&line_name=' + playerName
-      + '&line_lang=' + langstring
+      + '&line_lang=' + langString
       + extraData;
 
     const url = 'php/save.php';
 
-    const xhr = new XMLHttpRequest();
-
-    xhr.open('POST', url, true);
-
-    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
-
-    xhr.onreadystatechange = function () {
-      if (debugMode) console.log(xhr);
-      if (xhr.readyState == 4 && xhr.status == 200) {
-        if (debugMode) console.log(xhr.responseText);
-      }
-    }
-
-    xhr.send(data); // Actually execute the request
-
-    if (debugMode) {
-      console.log('processing...');
-      console.log(data);
-    }
-
+    const init = { method: 'POST', body: data, headers: { 'Content-type': 'application/x-www-form-urlencoded' } };
+    fetch(url, init)
+      .then(response => {
+        if (response.ok) {
+          if (debugMode) console.log("Processing...");
+          response.text().then(text => { if (debugMode) { console.log(text); } })
+        } else {
+          console.error("Game error: Network response was not ok.");
+        }
+      })
+      .catch(error => {
+        console.error('Game error: problem with fetch operation - ' + error.message + '.');
+      });
   }
+
 };

+ 142 - 101
js/integrationFunctions.js

@@ -1,51 +1,39 @@
-/* FOR MOODLE
+/*************************************************************************************
+ * LInE - Free Education, Private Data - http://www.usp.br/line
  * 
- * These functions are used exclusively when iFractions is runnign inside Moodle as an iAssign module. <br>
- * In this case, the global variable 'moodle' must be 'true' (globals.js) <br> 
- * More about iAssign functions and parameters : https://www.ime.usp.br/~igormf/ima-tutorial/ (pt-br)
- */
-
-const iLMparameters = {
-  /* Este parâmetro serve para distinguir a situação em que o iMA está sendo manuseado, 
-  * - Quando elaboração de atividade (professor), o valor é: true, 
-  * - Quando resolução do exercício (aluno), o valor é false. */
-  iLM_PARAM_SendAnswer: getParameterByName("iLM_PARAM_SendAnswer"), // Checks if you're student (false) or professor (true)
-  /* Este parâmetro é informado quando o usuário está abrindo uma atividade interativa para resolvê-la. 
-   * Tem como valor uma URL, que serve para acessar o exercício criado pelo professor.
-   * Exemplo de valor: http://myschool.edu/moodle/mod/iassign/ilm_security.php?id=3&token=b3660dd4de0b0e9bb01fea6cc8f02ccd&view=1
-   * Observe que o conteúdo do parâmetro token, presente na URL é acessível uma única vez, 
-   * ou seja, quando o iMA acessá-la (via AJAX), 
-   * obterá a informação a respeito da atividade, 
-   * não podendo acessá-la novamente, pois a token será destruída por razões de segurança. */
-  iLM_PARAM_Assignment: getParameterByName("iLM_PARAM_Assignment"),
-  /* Este parâmetro informa ao iMA em que idioma o Moodle está sendo utilizado, 
-   * permitindo a internacionalização. Exemplos de valores: en para inglês; pt para português.*/
-  lang: getParameterByName("lang"),
-  iLM_PARAM_ServerToGetAnswerURL: getParameterByName("iLM_PARAM_ServerToGetAnswerURL"),
-  iLM_PARAM_ServerToGetAnswerURL: getParameterByName("iLM_PARAM_ServerToGetAnswerURL")
-};
+ * This code is used EXCLUSIVELY when iFractions is runnign inside Moodle via iAssign 
+ * as an iLM (interactive learning module) and the global variable moodle=true.
+ * 
+ * More about iAssign: 
+ * http://200.144.254.107/git/LInE/iassign
+ * 
+ * More about creating iLM for iAssign (and the functions in this file): 
+ * https://www.ime.usp.br/~igormf/ima-tutorial/ (in pt-BR)
+ * 
+ *************************************************************************************/
 
-/* [iAssign function]
- * The iLM will be included in the HTML page as an iFrame, 
- * therefore some parameters are going to be passed by the iAssign to the iLM via URL.
+/** [Functions used by iAssign]
  * 
- * This method is used to read the informed parameters.
+ * The iLM will be included in the HTML page as an iFrame,
+ * therefore some parameters are going to be passed by the iAssign to the iLM via URL. <br>
+ * This method will read these parameters.
  */
 function getParameterByName(name) {
   var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
   return match ? decodeURIComponent(match[1].replace(/\+/g, ' ')) : null;
-}
+};
 
-/* [iAssign function]
- * Este método é invocado automaticamente pelo iTarefa, em dois diferentes momentos:
- *
- * - Quando o PROFESSOR está elaborando a atividade e a finaliza, clicando no botão "Salvar". 
- *   Retorna: dados da atividade criada;
+/** [Functions used by iAssign]
+ * 
+ * This function is automaticaly called by iAssign in two different times: <br>
  * 
- * - Quando o ESTUDANTE está resolvendo um exercício e aciona o botão "Salvar", 
- *   Retorna: resposta do estudante para a atividade.
+ * - When a PROFESSOR finishes creating an new iLM and clicks "save". <br>
+ *   Returns: the iLM created (aka the values set by the professor in text form). <br>
  * 
- * Para ambos os casos, o retorno deste método será recebido pelo iTarefa e será armazenado no banco de dados.
+ * - When a STUDENT finishes solving an assignment and clicks "send". <br>
+ *   Returns: data about the student's progress (aka the student's progress in text form). <br>
+ * 
+ *  @returns {string} the data that will be received by iAssign and stored on the database (a game file with extension .frc)
  */
 function getAnswer() {
   let str = '';
@@ -81,94 +69,110 @@ function getAnswer() {
   }
 
   return str;
-}
-
-/* [iAssign function]
- * Esta função deve estar presente nos iMA que possuem avaliador automático. 
- * Esta função é invocada pelo iTarefa, quando o aluno submete sua solução para avaliação, 
- * portanto, toda a avaliação deve ser realizada quando o método getEvaluation() é chamado. 
- * O retorno deve ser um número real entre 0.0 e 1.0, que significa a nota atribuída ao aluno.
- * Esta nota será armazenada no banco de dados.
+};
+
+/** [Functions used by iAssign]
+ * 
+ * This function must be present if the iMA uses automatic evaluation. <br>
+ * It is is called by iAssign after the student submits a solution
+ * and the data is sent to the moodle database.
+ *
+ * @returns {number} student's grade for the current assignment : real number between 0.0 and 1.0
  */
 function getEvaluation() {
   if (iLMparameters.iLM_PARAM_SendAnswer == 'false') { // Student
     let i;
     for (i = 0; i < moodleVar.hits.length && moodleVar.hits[i] == 1; i++);
     const grade = i / 4;
-    parent.getEvaluationCallback(grade); // Sends grade to moodle database
+    parent.getEvaluationCallback(grade); // Sends grade to moodle db
     return grade;
   }
-}
+};
 
-function getiLMContent() {
+/** [Functions used by iAssign]
+ * 
+ * Holds the parameters passed by the iAssign to the iLM via URL.
+ */
+const iLMparameters = {
+  /**
+   * This parameter gets the role in which the iLM is being used: <br>
+   * - if true, the user is creating a new iLM (as professor) <br>
+   * - if false, the user is solving the iLM (as student), value=false
+   */
+  iLM_PARAM_SendAnswer: getParameterByName("iLM_PARAM_SendAnswer"), // Checks if you're student (false) or professor (true)
+  /** 
+   * This parameter is used when the user is opening an iLM to solve it. <br>
+   * It holds a URL with the path to the game file (assignment/iLM) created by the professor. <br>
+   * Example: http://myschool.edu/moodle/mod/iassign/ilm_security.php?id=3&token=b3660dd4de0b0e9bb01fea6cc8f02ccd&view=1
+   * 
+   * The first parameter, 'token', can be used only once.
+   * Once the iLM gets the game file, the token is destroied (for security).
+   */
+  iLM_PARAM_Assignment: getParameterByName("iLM_PARAM_Assignment"),
+  /**
+   * Gets current moodle language.
+   */
+  lang: getParameterByName("lang"),
+  iLM_PARAM_ServerToGetAnswerURL: getParameterByName("iLM_PARAM_ServerToGetAnswerURL"),
+  iLM_PARAM_ServerToGetAnswerURL: getParameterByName("iLM_PARAM_ServerToGetAnswerURL")
+};
 
+/**
+ * Makes a GET request for the assignment file 
+ * and sends it to breakString() to treat its content.
+ */
+const getiLMContent = function () {
   const url = iLMparameters.iLM_PARAM_Assignment;
-
   if (url == null) {
-    console.error("[integrationFunctions.js] getiLMContent(): iLMparameters.iLM_PARAM_Assignment " + game.lang.error_moodle_file_ext);
-    return;
-  }
-
-  let xhr = new XMLHttpRequest();
-  xhr.open("GET", url, true);
-  xhr.send();
-  xhr.responseType = "text";
-  xhr.onreadystatechange = function () {
-    if (xhr.readyState === 4 && xhr.status === 200) {
-      const txt = xhr.responseText;
-      breakString(txt);
-    }
-  }
-
-}
-
-function updateGlobalVariables(info, infoResults) {
-  // Update new values
-  gameTypeString = info['gameTypeString'];
-  gameShape = info['gameShape'];
-  gameMode = info['gameMode'];
-  gameOperation = info['gameOperation'];
-  gameDifficulty = parseInt(info['gameDifficulty']);
-  fractionLabel = info['fractionLabel'];
-
-  // Update default values
-  mapPosition = 0;
-  mapMove = true;
-  completedLevels = 0;
-
-  // Calls custom menu after updating game variables 
-  if (infoResults) {
-    for (let i = 0; i < moodleVar.hits.length; i++) {
-      moodleVar.hits[i] = infoResults['l' + (i + 1)].hits;
-      moodleVar.errors[i] = infoResults['l' + (i + 1)].errors;
-      moodleVar.time[i] = infoResults['l' + (i + 1)].timeElapsed;
-    }
-    game.state.start('studentReport');
+    console.error("Game error: iLMparameters.iLM_PARAM_Assignment empty (File with extension .frc not found).");
   } else {
-    game.state.start('customMenu');
+    const init = { method: 'GET' };
+    fetch(url, init)
+      .then(response => {
+        if (response.ok) {
+          if (debugMode) console.log('Processing...');
+          response.text().then(text => { breakString(text); }); // Sends text to be treated
+        } else {
+          console.error("Game error: Network response was not ok.")
+        }
+      })
+      .catch(error => {
+        console.error('Game error: problem with fetch operation - ' + error.message + '.');
+      });
   }
+};
 
-}
+/**
+ * Receives the text from the assignment file,
+ * breaks the string into a key/value
+ * and sends it to updateGlobalVariables() 
+ * to update game variables before showing the screen.
+ * 
+ * @param {string} text content of the .frc file
+ */
+const breakString = function (text) {
 
-function breakString(text) {
   let gameInfo = {}, results;
-  let lines = text.split('\n'); // Break by line
+  const lines = text.split('\n'); // Break by line
+
   lines.forEach(cur => {
     try {
       let line = cur.split(':'); // Break by key:value
       if (line[0] != 'results') {
         const key = line[0].replace(/^\s+|\s+$/g, ''); // Removes end character
         const value = line[1].replace(/^\s+|\s+$/g, '');
-        gameInfo[key.trim()] = value.trim(); // Removes extra space
+        gameInfo[key] = value;
       } else {
         results = line[1].replace(/^\s+|\s+$/g, '');
       }
-    } catch (Error) { console.error('Sintax error'); }
+    } catch (Error) { console.error('Game error: sintax error.'); }
   });
+
   if (results) {
-    let curLevel = results.split('}');
-    results = { l1: {}, l2: {}, l3: {}, l4: {} };
     let i = 1;
+    const curLevel = results.split('}'); // Remove }
+    results = { l1: {}, l2: {}, l3: {}, l4: {} };
+
     curLevel.forEach(cur => {
       cur = cur.slice(1); // Remove {
       cur = cur.split(','); // Break by line
@@ -178,18 +182,55 @@ function breakString(text) {
             let line = cur.split('='); // Break by key=value
             const key = line[0].replace(/^\s+|\s+$/g, ''); // Removes end char
             const value = line[1].replace(/^\s+|\s+$/g, ''); // Removes end char
-            results['l' + i][key] = parseInt(value); // Removes extra space    
+            results['l' + i][key] = parseInt(value);
           }
-        } catch (Error) { console.error('Sintax error'); }
+        } catch (Error) { console.error('Game error: sintax error.'); }
       });
       i++;
     });
+
   }
+
   updateGlobalVariables(gameInfo, results);
-}
+
+};
+
+/**
+ * Updates game variables before starting the activity, then: <br>
+ * - calls state 'customMenu' if the assignment WAS NOT previously completed. <br>
+ * - calls state 'studentReport' otherwise.
+ * 
+ * @param {object} info game information
+ * @param {undefined|object} infoResults student answer (if there is any)
+ */
+const updateGlobalVariables = function (info, infoResults) {
+  // Update game variables to content received from game file
+  gameTypeString = info['gameTypeString'];
+  gameShape = info['gameShape'];
+  gameMode = info['gameMode'];
+  gameOperation = info['gameOperation'];
+  gameDifficulty = parseInt(info['gameDifficulty']);
+  fractionLabel = info['fractionLabel'];
+  // Update default values
+  mapPosition = 0;
+  mapMove = true;
+  completedLevels = 0;
+  // If the assignment WAS previously completed calls 'studentReport' after all is loaded.
+  if (infoResults) {
+    // Adds data about the student's report
+    for (let i = 0; i < moodleVar.hits.length; i++) {
+      moodleVar.hits[i] = infoResults['l' + (i + 1)].hits;
+      moodleVar.errors[i] = infoResults['l' + (i + 1)].errors;
+      moodleVar.time[i] = infoResults['l' + (i + 1)].timeElapsed;
+    }
+    game.state.start('studentReport');
+  } else { // If assignment WAS NOT previously completed, calls 'customMenu' after all is loaded.
+    game.state.start('customMenu');
+  }
+};
 
 const moodleVar = {
   hits: [0, 0, 0, 0],
   errors: [0, 0, 0, 0],
   time: [0, 0, 0, 0]
-}
+};

+ 36 - 40
js/map.js

@@ -1,5 +1,8 @@
-/**
- * MAP STATE: game map with game levels - shows how many levels the player have finished / current level he is in
+/******************************
+ * This file holds game states.
+ ******************************/
+
+/** [MAP STATE] Screen that shows the 4 generated levels in a map (and the level where the player is currently in).
  * 
  * @namespace
  */
@@ -20,12 +23,12 @@ const mapState = {
 
     // FOR MOODLE
     if (moodle) {
-      navigationIcons.func_addIcons(
+      navigationIcons.add(
         false, false, false, // Left icons
         false, false,        // Right icons
         false, false);
     } else {
-      navigationIcons.func_addIcons(
+      navigationIcons.add(
         true, true, false, // Left icons
         false, false,      // Right icons
         'customMenu', false);
@@ -34,12 +37,12 @@ const mapState = {
     // Progress bar
     const percentText = 4 * 25;
 
-    if (completedLevels == 4) game.add.geom.rect(660, 10, completedLevels * 37.5, 35, undefined, 0, colors.intenseGreen, 0.5);
+    if (completedLevels >= 4) game.add.geom.rect(660, 10, 4 * 37.5, 35, undefined, 0, colors.intenseGreen, 0.5);
     else game.add.geom.rect(660, 10, completedLevels * 37.5, 35, undefined, 0, colors.yellow, 0.9);
 
-    game.add.geom.rect(661, 11, 149, 34, colors.blue, 3, undefined, 1);
-    game.add.text(820, 38, percentText + '%', textStyles.h2_blue, 'left');
-    game.add.text(650, 38, game.lang.difficulty + ' ' + gameDifficulty, textStyles.h2_blue, 'right');
+    game.add.geom.rect(661, 11, 149, 34, colors.blue, 3, undefined, 1); // Box
+    game.add.text(820, 38, percentText + '%', textStyles.h2_blue).align = 'left';
+    game.add.text(650, 38, game.lang.difficulty + ' ' + gameDifficulty, textStyles.h2_blue).align = 'right';
 
     // Map positions
     this.points = {
@@ -132,8 +135,8 @@ const mapState = {
     self.speedX = (xB - xA) / speed;
     self.speedY = (yA - yB) / speed;
 
-    game.event.add('click', this.func_onInputDown);
-    game.event.add('mousemove', this.func_onInputOver);
+    game.event.add('click', this.onInputDown);
+    game.event.add('mousemove', this.onInputOver);
 
   },
 
@@ -168,22 +171,30 @@ const mapState = {
 
     if (endUpdate) {
       game.animation.stop(self.character.animation[0]);
-      self.func_loadGame();
+      self.loadGame();
     }
 
   },
 
+  /**
+   * Calls game state
+   */
+  loadGame: function () {
 
+    if (audioStatus) game.audio.beepSound.play();
 
-  /* EVENT HANDLER */
+    if (mapPosition <= 4) game.state.start('' + gameTypeString);
+    else game.state.start('end');
+
+  },
 
   /**
    * Called by mouse click event
    * 
    * @param {object} mouseEvent contains the mouse click coordinates
    */
-  func_onInputDown: function (mouseEvent) {
-    navigationIcons.func_onInputDown(mouseEvent.offsetX, mouseEvent.offsetY);
+  onInputDown: function (mouseEvent) {
+    navigationIcons.onInputDown(mouseEvent.offsetX, mouseEvent.offsetY);
   },
 
   /**
@@ -191,30 +202,13 @@ const mapState = {
    * 
    * @param {object} mouseEvent contains the mouse move coordinates
    */
-  func_onInputOver: function (mouseEvent) {
-    navigationIcons.func_onInputOver(mouseEvent.offsetX, mouseEvent.offsetY);
-  },
-
-
-
-  /* GAME FUNCTIONS */
-
-  /**
-   * Calls game state
-   */
-  func_loadGame: function () {
-
-    if (audioStatus) game.audio.beepSound.play();
-
-    if (mapPosition <= 4) game.state.start('' + gameTypeString + '');
-    else game.state.start('end');
-
-  },
+  onInputOver: function (mouseEvent) {
+    navigationIcons.onInputOver(mouseEvent.offsetX, mouseEvent.offsetY);
+  }
 
 };
 
-/**
- * ENDING STATE: animation after a full game is completed (4 levels)
+/** [ENDING STATE] Ending screen shown when the player has completed all 4 levels and therefore completed the game.
  * 
  * @namespace
  */
@@ -245,8 +239,8 @@ const endState = {
     // Progress bar
     game.add.geom.rect(660, 10, 4 * 37.5, 35, undefined, 0, colors.intenseGreen, 0.5); // Progress
     game.add.geom.rect(661, 11, 149, 34, colors.blue, 3, undefined, 1); // Box
-    game.add.text(820, 38, '100%', textStyles.h2_blue, 'left');
-    game.add.text(650, 38, game.lang.difficulty + ' ' + gameDifficulty, textStyles.h2_blue, 'right');
+    game.add.text(820, 38, '100%', textStyles.h2_blue).align = 'left';
+    game.add.text(650, 38, game.lang.difficulty + ' ' + gameDifficulty, textStyles.h2_blue).align = 'right';
 
     game.add.image(360, 545, 'tree4', 0.7).anchor(0, 1);
 
@@ -350,11 +344,13 @@ const endState = {
       } else {
 
         self.animate = false;
-        completedLevels = 0;
         game.animation.stop(self.character.animation[0]);
 
         // FOR MOODLE
-        if (!moodle) game.state.start('menu');
+        if (!moodle) {
+          completedLevels = 0;
+          game.state.start('menu');
+        }
 
       }
 
@@ -362,6 +358,6 @@ const endState = {
 
     game.render.all();
 
-  },
+  }
 
 };

+ 28 - 26
js/menu.js

@@ -1,5 +1,8 @@
-/**
- * MAIN MENU STATE: main menu - player can select the game he wants to play 
+/******************************
+ * This file holds game states.
+ ******************************/
+
+/** [MAIN MENU STATE] Screen where the user can select a game.
  * 
  * @namespace
  */
@@ -22,15 +25,15 @@ const menuState = {
   create: function () {
 
     // FOR MOODLE
-    if (moodle && iLMparameters.iLM_PARAM_SendAnswer == 'false') {
+    if (moodle && iLMparameters.iLM_PARAM_SendAnswer == 'false') { // Student role
 
-      playerName = 'Aluno'; // TODO pegar o nome do aluno no bd do moodle
+      playerName = game.lang.student; // TODO pegar o nome do aluno no bd do moodle
       getiLMContent();
 
     } else {
 
       // FOR MOODLE
-      if (moodle && iLMparameters.iLM_PARAM_SendAnswer == 'true') playerName = 'Professor';
+      if (moodle && iLMparameters.iLM_PARAM_SendAnswer == 'true') playerName = game.lang.professor;
 
       // Background color
       game.add.geom.rect(0, 0, defaultWidth, defaultHeight, undefined, 0, colors.blueBckg, 1);
@@ -45,7 +48,7 @@ const menuState = {
       this.lbl_game = game.add.text(defaultWidth / 2, 110, '', textStyles.h2_blue_2);
 
       // Loads navigation icons
-      navigationIcons.func_addIcons(
+      navigationIcons.add(
         false, false, false,
         true, true,
         false, false);
@@ -115,8 +118,8 @@ const menuState = {
 
       // ------------- EVENTS
 
-      game.event.add('click', this.func_onInputDown);
-      game.event.add('mousemove', this.func_onInputOver);
+      game.event.add('click', this.onInputDown);
+      game.event.add('mousemove', this.onInputOver);
 
     }
 
@@ -125,7 +128,7 @@ const menuState = {
   /**
    * Displays game menu information boxes.
    */
-  func_showInfoBox: function (icon) {
+  showInfoBox: function (icon) {
     self.infoBox.style.display = 'block';
 
     let msg = '<h3>' + self.infoBoxContent[icon.id].title + '</h3>'
@@ -140,12 +143,12 @@ const menuState = {
    * 
    * @param {object} icon clicked icon
    */
-  func_load: function (icon) {
+  load: function (icon) {
 
     if (audioStatus) game.audio.beepSound.play();
 
     switch (icon.iconType) {
-      case 'infoIcon': self.func_showInfoBox(icon); break;
+      case 'infoIcon': self.showInfoBox(icon); break;
       case 'game':
         gameShape = icon.gameShape;
         gameTypeString = icon.gameType;
@@ -153,12 +156,11 @@ const menuState = {
           case 'squareOne': gameType = squareOne; break;
           case 'squareTwo': gameType = squareTwo; break;
           case 'circleOne': gameType = circleOne; break;
-          default: console.error('Game error: the name of the game is not valid');
+          default: console.error('Game error: the name of the game is not valid.');
         }
         self.menuIcons = self.lbl_game.name;
         game.state.start('customMenu');
         break;
-      default: console.error("Game error: Problem with selected icon.");
     }
 
   },
@@ -168,7 +170,7 @@ const menuState = {
    * 
    * @param {object} icon icon for the game
    */
-  func_showTitle: function (icon) {
+  showTitle: function (icon) {
 
     const number = (icon.gameType.slice(-3) == 'One') ? 'I' : 'II';
 
@@ -179,7 +181,7 @@ const menuState = {
   /**
    * Remove the name of the game from screen
    */
-  func_clearTitle: function () {
+  clearTitle: function () {
     self.lbl_game.name = '';
   },
 
@@ -188,7 +190,7 @@ const menuState = {
    * 
    * @param {object} mouseEvent contains the mouse click coordinates
    */
-  func_onInputDown: function (mouseEvent) {
+  onInputDown: function (mouseEvent) {
     const x = mouseEvent.offsetX, y = mouseEvent.offsetY;
 
     // Check menu icons
@@ -196,13 +198,13 @@ const menuState = {
       // If mouse is within the bounds of an icon
       if (game.math.isOverIcon(x, y, self.menuIcons[i])) {
         // Click first valid icon
-        self.func_load(self.menuIcons[i]);
+        self.load(self.menuIcons[i]);
         break;
       }
     }
 
     // Check navigation icons
-    navigationIcons.func_onInputDown(x, y);
+    navigationIcons.onInputDown(x, y);
 
     game.render.all();
   },
@@ -212,7 +214,7 @@ const menuState = {
    * 
    * @param {object} mouseEvent contains the mouse move coordinates
    */
-  func_onInputOver: function (mouseEvent) {
+  onInputOver: function (mouseEvent) {
     const x = mouseEvent.offsetX, y = mouseEvent.offsetY;
     let overIcon;
 
@@ -225,26 +227,26 @@ const menuState = {
     }
 
     // Update gui
-    if (overIcon) { // if pointer is over icon
+    if (overIcon) { // If pointer is over icon
       document.body.style.cursor = 'pointer';
-      if (self.menuIcons[overIcon].iconType == 'game') self.func_showTitle(self.menuIcons[overIcon]);
+      if (self.menuIcons[overIcon].iconType == 'game') self.showTitle(self.menuIcons[overIcon]);
       self.menuIcons.forEach(cur => {
-        if (cur.iconType == self.menuIcons[overIcon].iconType) { // if its in the same icon category
-          if (cur == self.menuIcons[overIcon]) { // if its the icon the pointer is over 
+        if (cur.iconType == self.menuIcons[overIcon].iconType) { // If its in the same icon category
+          if (cur == self.menuIcons[overIcon]) { // If its the icon the pointer is over 
             cur.scale = cur.originalScale * 1.1;
           } else {
             cur.scale = cur.originalScale;
           }
         }
       });
-    } else { // if pointer is not over icon
-      self.func_clearTitle();
+    } else { // If pointer is not over icon
+      self.clearTitle();
       self.menuIcons.forEach(cur => { cur.scale = cur.originalScale; });
       document.body.style.cursor = 'auto';
     }
 
     // Check navigation icons
-    navigationIcons.func_onInputOver(x, y);
+    navigationIcons.onInputOver(x, y);
 
     game.render.all();
   }

+ 70 - 79
js/preMenu.js

@@ -1,5 +1,9 @@
-/**
- * BOOT STATE: First state called. Loads main media
+/******************************
+ * This file holds game states.
+ ******************************/
+
+/** [BOOT STATE] First state called. Loads media. <br>
+ * 
  * @namespace
  */
 const bootState = {
@@ -38,10 +42,10 @@ const bootState = {
       game.state.start('lang');
     }
   }
+
 };
 
-/**
- * LANGUAGE STATE: the player can choose a preferred language for the text to be displayed in the game
+/** [LANGUAGE STATE] Screen that asks the user to select the language for the game text.
  * 
  * @namespace
  */
@@ -68,7 +72,7 @@ const langState = {
     // Create elements on screen  
     for (let i in this.langs.flag) {
       // Add text for language names
-      game.add.text(defaultWidth / 2 + this.langs.x[i], defaultHeight / 2 + this.langs.y[i], this.langs.text[i], textStyles.h2_green, 'right');
+      game.add.text(defaultWidth / 2 + this.langs.x[i], defaultHeight / 2 + this.langs.y[i], this.langs.text[i], textStyles.h2_green).align = 'right';
 
       // Add icons for flags
       const flag = game.add.image(defaultWidth / 2 + this.langs.x[i] + 100, defaultHeight / 2 + this.langs.y[i], this.langs.flag[i]);
@@ -77,19 +81,28 @@ const langState = {
       this.listOfFlags.push(flag);
     }
 
-    game.event.add('click', this.func_onInputDown);
-    game.event.add('mousemove', this.func_onInputOver);
+    game.event.add('click', this.onInputDown);
+    game.event.add('mousemove', this.onInputOver);
   },
 
-
-  /* EVENT HANDLER*/
-
   /**
-   * Called by mouse click event
+   * Calls state that loads selected language
    * 
-   * @param {object} mouseEvent contains the mouse click coordinates
+   * @param {string} selectedLang language selected by player
    */
-  func_onInputDown: function (mouseEvent) {
+  setLang: function (selectedLang) {
+    // Saves language name e.g 'pt_BR'
+    langString = selectedLang;
+    // Calls loading screen
+    game.state.start('loadLang');
+  },
+
+  /**
+ * Called by mouse click event
+ * 
+ * @param {object} mouseEvent contains the mouse click coordinates
+ */
+  onInputDown: function (mouseEvent) {
     const x = mouseEvent.offsetX;
     const y = mouseEvent.offsetY;
 
@@ -97,7 +110,7 @@ const langState = {
       if (game.math.isOverIcon(x, y, cur)) {
         for (let i in self.langs.flag) {
           if (self.langs.flag[i] == cur.name) {
-            self.func_setLang(self.langs.lang[i]);
+            self.setLang(self.langs.lang[i]);
             break;
           }
         }
@@ -110,7 +123,7 @@ const langState = {
    * 
    * @param {object} mouseEvent contains the mouse move coordinates
    */
-  func_onInputOver: function (mouseEvent) {
+  onInputOver: function (mouseEvent) {
     const x = mouseEvent.offsetX;
     const y = mouseEvent.offsetY;
     let flag = false;
@@ -128,27 +141,11 @@ const langState = {
     else document.body.style.cursor = 'auto';
 
     game.render.all();
-  },
-
-
-
-  /* GAME FUNCTIONS */
-
-  /**
-   * Calls state that loads selected language
-   * 
-   * @param {string} selectedLang language selected by player
-   */
-  func_setLang: function (selectedLang) {
-    // Saves language name e.g 'pt_BR'
-    langString = selectedLang;
-    // Calls loading screen
-    game.state.start('loadLang');
   }
+
 };
 
-/**
- * LOADING LANGUAGE STATE: Loads selected language to be able to translate the game text 
+/** [LOADING LANGUAGE STATE] Loads the selected language.
  * 
  *  @namespace
  */
@@ -176,10 +173,10 @@ const loadLangState = {
       game.state.start('menu'); // If changing language during the game ('language' >> >> 'menu')         
     }
   }
+
 };
 
-/**
- * NAME STATE: asks for player's name
+/** [NAME STATE] Screen that asks for the user's name.
  * 
  * @namespace
  */
@@ -212,32 +209,62 @@ const nameState = {
     document.getElementById('textbox-content').addEventListener('keypress', function (e) {
       const keycode = e.key || e.code;
       if (keycode == 'Enter') {
-        if (self.func_checkEmptyName()) self.func_saveName();
+        if (self.checkEmptyName()) self.saveName();
         game.render.all(); // Can show empty name
       }
     });
 
-    game.event.add('click', this.func_onInputDown);
-    game.event.add('mousemove', this.func_onInputOver);
+    game.event.add('click', this.onInputDown);
+    game.event.add('mousemove', this.onInputOver);
 
   },
 
+  /**
+   * Checks if player entered name in text box
+   * 
+   * @returns {boolean} false is textBox is emptys
+   */
+  checkEmptyName: function () {
+    // If text field is empty displays error message
+    if (document.getElementById('textbox-content').value == '') {
+      self.warningEmptyName.name = game.lang.empty_name;
+      return false;
+    }
+    return true;
+  },
+
+  /**
+   * Saves player name and calls next state
+   */
+  saveName: function () {
+    // Saves player's input in global variable 'playerName'
+    playerName = document.getElementById('textbox-content').value;
+
+    // Hides and clears text field
+    document.getElementById('textbox').style.visibility = 'hidden';
+    document.getElementById('textbox-content').value = '';
+
+    if (audioStatus) game.audio.beepSound.play();
+    if (debugMode) console.log('Username: ' + playerName);
 
-  /* EVENT HANDLER*/
+    // FOR MOODLE
+    // Calls 'menu' state
+    if (!moodle) game.state.start('menu');
+  },
 
   /**
    * Called by mouse click event
    * 
    * @param {object} mouseEvent contains the mouse click coordinates
    */
-  func_onInputDown: function (mouseEvent) {
+  onInputDown: function (mouseEvent) {
     const x = mouseEvent.offsetX;
     const y = mouseEvent.offsetY;
     const cur = self.okBtn;
 
     if (game.math.isOverIcon(x, y, cur)) {
-      if (self.func_checkEmptyName()) {
-        self.func_saveName();
+      if (self.checkEmptyName()) {
+        self.saveName();
       }
     }
     game.render.all();
@@ -248,7 +275,7 @@ const nameState = {
    * 
    * @param {object} mouseEvent contains the mouse move coordinates
    */
-  func_onInputOver: function (mouseEvent) {
+  onInputOver: function (mouseEvent) {
     const x = mouseEvent.offsetX;
     const y = mouseEvent.offsetY;
     const cur = self.okBtn;
@@ -262,42 +289,6 @@ const nameState = {
     }
 
     game.render.all();
-  },
-
-
-  /* GAME FUNCTIONS */
-
-  /**
-   * Checks if player entered name in text box
-   * 
-   * @returns {boolean}
-   */
-  func_checkEmptyName: function () {
-    // If text field is empty displays error message
-    if (document.getElementById('textbox-content').value == '') {
-      self.warningEmptyName.name = game.lang.empty_name;
-      return false;
-    }
-    return true;
-  },
-
-  /**
-   * Saves player name and calls next state
-   */
-  func_saveName: function () {
-    // Saves player's input in global variable 'playerName'
-    playerName = document.getElementById('textbox-content').value;
-
-    // Hides and clears text field
-    document.getElementById('textbox').style.visibility = 'hidden';
-    document.getElementById('textbox-content').value = '';
-
-    if (audioStatus) game.audio.beepSound.play();
-    if (debugMode) console.log('Username: ' + playerName);
-
-    // FOR MOODLE
-    // Calls 'menu' state
-    if (!moodle) game.state.start('menu');
   }
 
 };

+ 109 - 113
js/squareOne.js

@@ -1,28 +1,36 @@
-/**
- * LInE - Free Education, Private Data
+/******************************
+ * This file holds game states.
+ ******************************/
+
+/** [GAME STATE]
  * 
- * iFractions GAME STATE
+ * ..squareOne...	= gameType
+ * ..../...\..... 
+ * ...A.....B.... = gameMode
+ * .....\./......
+ * ......|.......
+ * ...../.\......
+ * .Plus...Minus. = gameOperation
+ * .....\./......
+ * ......|.......
+ * ....1,2,3..... = gameDifficulty
  *
- * Name of game state : squareOne 
- * Shape : square
  * Character : tractor
  * Theme : farm
  * Concept : Player associates 'blocks carried by the tractor' and 'floor spaces to be filled by them'
- * Represent fractions as : blocks
- *
- * # of different difficulties : 3
+ * Represent fractions as : blocks/rectangles
  *
- * Game modes can be : 'A' or 'B' (in variable 'gameMode')
+ * Game modes can be :
  *
  *   A : Player can select # of 'floor blocks' (hole in the ground)
  *       Selects size of hole to be made in the ground (to fill with the blocks in front of the truck)
  *   B : Player can select # of 'stacked blocks' (in front of the truck)
  *       Selects number of blocks in front of the truck (to fill the hole on the ground)
  *
- * Operations can be : 'Plus' or 'Minus' (in variable 'gameOperation')
+ * Operations can be :
  *
  *   Plus : addition of fractions
- *     Represented by : tractor going to the right (floor positions 0..8
+ *     Represented by : tractor going to the right (floor positions 0..8)
  *   Minus : subtraction of fractions
  *     Represented by: tractor going to the left (floor positions 8..0)
  *
@@ -70,16 +78,16 @@ const squareOne = {
 
     // FOR MOODLE
     if (moodle) {
-      navigationIcons.func_addIcons(
+      navigationIcons.add(
         false, false, false, // Left icons
         true, false,         // Right icons
         false, false
       );
     } else {
-      navigationIcons.func_addIcons(
+      navigationIcons.add(
         true, true, true,   // Left icons
         true, false,        // Right icons
-        'customMenu', this.func_viewHelp
+        'customMenu', this.viewHelp
       );
     }
 
@@ -126,10 +134,10 @@ const squareOne = {
     };
 
     // CREATING STACKED BLOCKS
-    this.restart = this.func_createStckBlocks();
+    this.restart = this.createStckBlocks();
 
     // CREATING FLOOR BLOCKS
-    this.func_createFloorBlocks();
+    this.createFloorBlocks();
 
     // SELECTION ARROW
 
@@ -146,8 +154,8 @@ const squareOne = {
 
     if (!this.restart) {
       game.timer.start(); // Set a timer for the current level (used in postScore())
-      game.event.add('click', this.func_onInputDown);
-      game.event.add('mousemove', this.func_onInputOver);
+      game.event.add('click', this.onInputDown);
+      game.event.add('mousemove', this.onInputOver);
     }
   },
 
@@ -249,7 +257,7 @@ const squareOne = {
         if (audioStatus) game.audio.okSound.play();
 
         completedLevels++; // Increases number os finished levels
-        if (debugMode) console.log('completedLevels = ' + completedLevels);
+        if (debugMode) console.log('Completed Levels: ' + completedLevels);
       } else { // Incorrect answer
         // Displays feedback image and sound
         game.add.image(defaultWidth / 2, defaultHeight / 2, 'error').anchor(0.5, 0.5);
@@ -284,87 +292,12 @@ const squareOne = {
     game.render.all();
   },
 
-
-  /* EVENT HANDLER */
-
-  /**
-   * Called by mouse click event
-   * 
-   * @param {object} mouseEvent contains the mouse click coordinates
-   */
-  func_onInputDown: function (mouseEvent) {
-    const x = mouseEvent.offsetX;
-    const y = mouseEvent.offsetY;
-
-    if (gameMode == 'A') {
-      self.floor.blocks.forEach(cur => {
-        if (game.math.isOverIcon(x, y, cur)) self.func_clickSquare(cur);
-      });
-    } else {
-      self.stck.blocks.forEach(cur => {
-        if (game.math.isOverIcon(x, y, cur)) self.func_clickSquare(cur);
-      });
-    }
-
-    navigationIcons.func_onInputDown(x, y);
-
-    game.render.all();
-  },
-
-  /**
-   * Called by mouse move event
-   * 
-   * @param {object} mouseEvent contains the mouse move coordinates
-   */
-  func_onInputOver: function (mouseEvent) {
-    const x = mouseEvent.offsetX;
-    const y = mouseEvent.offsetY;
-    let flagA = false;
-    let flagB = false;
-
-    if (gameMode == 'A') {
-      // Make arrow follow mouse
-      if (!self.hasClicked && !self.animateEnding) {
-        if (game.math.distanceToPointer(self.arrow.x, x, self.arrow.y, y) > 8) {
-          self.arrow.x = (x < 250) ? 250 : x; // Limits the arrow left position to 250
-        }
-      }
-
-      self.floor.blocks.forEach(cur => {
-        if (game.math.isOverIcon(x, y, cur)) {
-          flagA = true;
-          self.func_overSquare(cur);
-        }
-      });
-
-      if (!flagA) self.func_outSquare('A');
-    }
-
-    if (gameMode == 'B') {
-      self.stck.blocks.forEach(cur => {
-        if (game.math.isOverIcon(x, y, cur)) {
-          flagB = true;
-          self.func_overSquare(cur);
-        }
-      });
-
-      if (!flagB) self.func_outSquare('B');
-    }
-
-    navigationIcons.func_onInputOver(x, y);
-
-    game.render.all();
-  },
-
-
-  /* CALLED BY EVENT HANDLER */
-
   /**
-   * Function called when cursor is over a valid rectangle
+   * Function called by self.onInputOver() when cursor is over a valid rectangle
    * 
    * @param {object} cur rectangle the cursor is over
    */
-  func_overSquare: function (cur) {
+  overSquare: function (cur) {
     if (!self.hasClicked) {
       document.body.style.cursor = 'pointer';
 
@@ -390,9 +323,9 @@ const squareOne = {
   },
 
   /**
-   * Function called when cursos is out of a valid rectangle
+   * Function called by self.onInputOver() when cursos is out of a valid rectangle
    */
-  func_outSquare: function () {
+  outSquare: function () {
     if (!self.hasClicked) {
       document.body.style.cursor = 'auto';
 
@@ -415,9 +348,9 @@ const squareOne = {
   },
 
   /**
-   * Function called when player clicks on a valid rectangle
+   * Function called by self.onInputDown() when player clicks on a valid rectangle.
    */
-  func_clickSquare: function () {
+  clickSquare: function () {
     if (!self.hasClicked && !self.animateEnding) {
       document.body.style.cursor = 'auto';
 
@@ -468,14 +401,12 @@ const squareOne = {
     }
   },
 
-
-  /* GAME FUNCTIONS */
-
   /**
-   * Create stacked blocks for the level (called in create())
+   * Create stacked blocks for the level in create()
+   * 
    * @returns {boolean}
    */
-  func_createStckBlocks: function () {
+  createStckBlocks: function () {
     let hasBaseDifficulty = false; // Will be true after next for loop if level has at least one '1/difficulty' fraction (if false, restart)
     const max = (gameMode == 'B') ? 10 : mapPosition + 4; // Maximum number of stacked blocks for the level
 
@@ -556,9 +487,9 @@ const squareOne = {
   },
 
   /**
-   * Create floor blocks for the level (called in create())
+   * Create floor blocks for the level in create()
    */
-  func_createFloorBlocks: function () { // For each floor block
+  createFloorBlocks: function () { // For each floor block
     const divisor = (gameDifficulty == 3) ? 4 : gameDifficulty; // Make sure valid divisors are 1, 2 and 4 (not 3)
 
     let total = 8 * divisor; // Number of floor blocks
@@ -633,7 +564,7 @@ const squareOne = {
   /**
    * Display correct answer
    */
-  func_viewHelp: function () {
+  viewHelp: function () {
     if (!self.hasClicked) {
       // On gameMode A
       if (gameMode == 'A') {
@@ -651,14 +582,80 @@ const squareOne = {
     }
   },
 
+  /**
+   * Called by mouse click event
+   * 
+   * @param {object} mouseEvent contains the mouse click coordinates
+   */
+  onInputDown: function (mouseEvent) {
+    const x = mouseEvent.offsetX;
+    const y = mouseEvent.offsetY;
+
+    if (gameMode == 'A') {
+      self.floor.blocks.forEach(cur => {
+        if (game.math.isOverIcon(x, y, cur)) self.clickSquare(cur);
+      });
+    } else {
+      self.stck.blocks.forEach(cur => {
+        if (game.math.isOverIcon(x, y, cur)) self.clickSquare(cur);
+      });
+    }
 
+    navigationIcons.onInputDown(x, y);
 
-  /* METADATA FOR GAME */
+    game.render.all();
+  },
+
+  /**
+   * Called by mouse move event
+   * 
+   * @param {object} mouseEvent contains the mouse move coordinates
+   */
+  onInputOver: function (mouseEvent) {
+    const x = mouseEvent.offsetX;
+    const y = mouseEvent.offsetY;
+    let flagA = false;
+    let flagB = false;
+
+    if (gameMode == 'A') {
+      // Make arrow follow mouse
+      if (!self.hasClicked && !self.animateEnding) {
+        if (game.math.distanceToPointer(self.arrow.x, x, self.arrow.y, y) > 8) {
+          self.arrow.x = (x < 250) ? 250 : x; // Limits the arrow left position to 250
+        }
+      }
+
+      self.floor.blocks.forEach(cur => {
+        if (game.math.isOverIcon(x, y, cur)) {
+          flagA = true;
+          self.overSquare(cur);
+        }
+      });
+
+      if (!flagA) self.outSquare('A');
+    }
+
+    if (gameMode == 'B') {
+      self.stck.blocks.forEach(cur => {
+        if (game.math.isOverIcon(x, y, cur)) {
+          flagB = true;
+          self.overSquare(cur);
+        }
+      });
+
+      if (!flagB) self.outSquare('B');
+    }
+
+    navigationIcons.onInputOver(x, y);
+
+    game.render.all();
+  },
 
   /**
    * Saves players data after level ends - to be sent to database <br>
    *
-   * Attention: the "line_" prefix data table must be compatible to data table fields (MySQL server)
+   * Attention: the 'line_' prefix data table must be compatible to data table fields (MySQL server)
+   * 
    * @see /php/save.php
    */
   postScore: function () {
@@ -677,8 +674,7 @@ const squareOne = {
       + ', floorIndex: ' + self.floor.index;
 
     // FOR MOODLE  
-    if (moodle) sendToDB(data, self.result, game.timer.elapsed);
-    else sendToDB(data);
-  },
+    sendToDB(data);
+  }
 
 };

+ 99 - 103
js/squareTwo.js

@@ -1,25 +1,31 @@
-/**
- * LInE - Free Education, Private Data
- *
- * iFractions GAME STATE
+/******************************
+ * This file holds game states.
+ ******************************/
+
+/** [GAME STATE]
  *  
- * Name of game state : 'squareTwo' 
- * Shape : square
+ * .squareTwo. = gameType
+ * .../...\...
+ * ..A.....B.. = gameMode
+ * ....\./....
+ * .....|.....
+ * ...Equals.. = gameOperation
+ * .....|.....
+ * .1,2,3,4,5. = gameDifficulty
+ * 
  * Character : kid
  * Theme : (not themed)
  * Concept : player select equivalent dividends for fractions with different divisors
- * Represent fractions as : subdivided blocks
- *
- * # of different difficulties : 5
+ * Represent fractions as : subdivided rectangles
  *
- * Game modes can be : 'A' or 'B' (in variable 'gameMode')
+ * Game modes can be :
  * 
  *   A : equivalence of fractions 
  *       top has more subdivisions
  *   B : equivalence of fractions
  *       bottom has more subdivisions
  * 
- * Operations : 'Equals' (in variable 'gameOperation')
+ * Operations :
  *
  *   Equals : Player selects equivalent fractions of both blocks 
  * 
@@ -74,12 +80,12 @@ const squareTwo = {
 
     // FOR MOODLE
     if (moodle) {
-      navigationIcons.func_addIcons(
+      navigationIcons.add(
         false, false, false, // Left buttons
         true, false,         // Right buttons
         false, false);
     } else {
-      navigationIcons.func_addIcons(
+      navigationIcons.add(
         true, true, false, // Left buttons
         true, false,       // Right buttons
         'customMenu', false);
@@ -118,10 +124,9 @@ const squareTwo = {
     const totalBlocksB = game.math.randomDivisor(totalBlocksA);
 
     if (debugMode) {
-      console.log('----------');
-      console.log('Difficulty ' + gameDifficulty + ', ini ' + ((gameDifficulty - 1) * 2 + 1) + ', end ' + ((gameDifficulty - 1) * 2 + 3));
-      console.log('Rpoint ' + randomIndex + ', val ' + totalBlocksA);
-      console.log('total blocks A ' + totalBlocksA + ', total blocks B ');
+      console.log('Difficulty: ' + gameDifficulty +
+        '\ncur index: ' + randomIndex + ', (min index: ' + ((gameDifficulty - 1) * 2 + 1) + ', max index: ' + ((gameDifficulty - 1) * 2 + 3) + ')' +
+        '\ntotal blocks A: ' + totalBlocksA + ', total blocks B: ' + totalBlocksB);
     }
 
     // CREATING TOP FIGURE (A)
@@ -209,8 +214,8 @@ const squareTwo = {
 
     game.timer.start(); // Set a timer for the current level (used in postScore)
 
-    game.event.add('click', this.func_onInputDown);
-    game.event.add('mousemove', this.func_onInputOver);
+    game.event.add('click', this.onInputDown);
+    game.event.add('mousemove', this.onInputOver);
   },
 
   /**
@@ -252,7 +257,7 @@ const squareTwo = {
           mapMove = true; // Allow character to move to next level in map state
           completedLevels++;
 
-          if (debugMode) console.log('completedLevels = ' + completedLevels);
+          if (debugMode) console.log('Completed Levels: ' + completedLevels);
 
           // Fractions are not equivalent : INCORRECT
         } else {
@@ -281,80 +286,12 @@ const squareTwo = {
     game.render.all();
   },
 
-
-  /* EVENT HANDLER */
-
-  /**
-   * Called by mouse click event
-   * 
-   * @param {object} mouseEvent contains the mouse click coordinates
-   */
-  func_onInputDown: function (mouseEvent) {
-    const x = mouseEvent.offsetX;
-    const y = mouseEvent.offsetY;
-
-    // Click block in A
-    self.A.blocks.forEach(cur => {
-      if (game.math.isOverIcon(x, y, cur)) self.func_clickSquare(cur);
-    });
-
-    // Click block in B
-    self.B.blocks.forEach(cur => {
-      if (game.math.isOverIcon(x, y, cur)) self.func_clickSquare(cur);
-    });
-
-    // Click navigation icons
-    navigationIcons.func_onInputDown(x, y);
-
-    game.render.all();
-  },
-
   /**
-   * Called by mouse move event
+   * Function called by self.onInputOver() when cursor is over a valid rectangle.
    * 
-   * @param {object} mouseEvent contains the mouse move coordinates
+   * @param {object} curBlock rectangle the cursor is over : can be self.A.blocks[i] or self.B.blocks[i]
    */
-  func_onInputOver: function (mouseEvent) {
-    const x = mouseEvent.offsetX;
-    const y = mouseEvent.offsetY;
-    let flagA = false;
-    let flagB = false;
-
-    // Mouse over A : show fraction
-    self.A.blocks.forEach(cur => {
-      if (game.math.isOverIcon(x, y, cur)) {
-        flagA = true;
-        self.func_overSquare(cur);
-      }
-    });
-    if (!flagA) self.func_outSquare('A');
-
-    // Mouse over B : show fraction
-    self.B.blocks.forEach(cur => {
-      if (game.math.isOverIcon(x, y, cur)) {
-        flagB = true;
-        self.func_overSquare(cur);
-      }
-    });
-    if (!flagB) self.func_outSquare('B');
-
-    if (!flagA && !flagB) document.body.style.cursor = 'auto';
-
-    // Mouse over navigation icons : show name
-    navigationIcons.func_onInputOver(x, y);
-
-    game.render.all();
-  },
-
-
-  /* CALLED BY EVENT HANDLER */
-
-  /**
-   * Function called when cursor is over a valid rectangle
-   * 
-   * @param {object} curBlock rectangle the cursor is over
-   */
-  func_overSquare: function (curBlock) { // curBlock : self.A.blocks[i] || self.B.blocks[i]
+  overSquare: function (curBlock) {
     const curSet = curBlock.figure; // 'A' || 'B'
 
     if (!self[curSet].hasClicked) { // self.A.hasClicked || self.B.hasClicked 
@@ -365,7 +302,7 @@ const squareTwo = {
         self[curSet].warningText.name = game.lang.s2_error_msg;
         self[otherSet].warningText.name = '';
 
-        self.func_outSquare(curSet);
+        self.outSquare(curSet);
       } else {
         document.body.style.cursor = 'pointer';
 
@@ -391,11 +328,11 @@ const squareTwo = {
   },
 
   /**
-   * Function called when cursor is out of a valid rectangle
+   * Function called (by self.onInputOver() and self.overSquare()) when cursor is out of a valid rectangle.
    * 
-   * @param {object} curSet set of rectangles (top or bottom)
+   * @param {object} curSet set of rectangles : can be top (self.A) or bottom (self.B)
    */
-  func_outSquare: function (curSet) { // curSet : self.A || self.B
+  outSquare: function (curSet) {
     if (!self[curSet].hasClicked) {
       self[curSet].fractions[0].alpha = 0;
       self[curSet].fractions[1].alpha = 0;
@@ -408,11 +345,11 @@ const squareTwo = {
   },
 
   /**
-   * Function called when player clicked a valid rectangle
+   * Function called by self.onInputDown() when player clicked a valid rectangle.
    * 
-   * @param {object} curBlock clicked rectangle
+   * @param {object} curBlock clicked rectangle : can be self.A.blocks[i] or self.B.blocks[i]
    */
-  func_clickSquare: function (curBlock) { // curBlock : self.A.blocks[i] || self.B.blocks[i]
+  clickSquare: function (curBlock) {
     const curSet = curBlock.figure; // 'A' || 'B'
 
     if (!self[curSet].hasClicked && curBlock.index != self[curSet].blocks.length - 1) {
@@ -447,13 +384,73 @@ const squareTwo = {
     game.render.all();
   },
 
+  /**
+   * Called by mouse click event
+   * 
+   * @param {object} mouseEvent contains the mouse click coordinates
+   */
+  onInputDown: function (mouseEvent) {
+    const x = mouseEvent.offsetX;
+    const y = mouseEvent.offsetY;
+
+    // Click block in A
+    self.A.blocks.forEach(cur => {
+      if (game.math.isOverIcon(x, y, cur)) self.clickSquare(cur);
+    });
+
+    // Click block in B
+    self.B.blocks.forEach(cur => {
+      if (game.math.isOverIcon(x, y, cur)) self.clickSquare(cur);
+    });
+
+    // Click navigation icons
+    navigationIcons.onInputDown(x, y);
 
-  /* METADATA FOR GAME */
+    game.render.all();
+  },
 
   /**
-   * Saves players data after level ends - to be sent to database <br>
+   * Called by mouse move event
+   * 
+   * @param {object} mouseEvent contains the mouse move coordinates
+   */
+  onInputOver: function (mouseEvent) {
+    const x = mouseEvent.offsetX;
+    const y = mouseEvent.offsetY;
+    let flagA = false;
+    let flagB = false;
+
+    // Mouse over A : show fraction
+    self.A.blocks.forEach(cur => {
+      if (game.math.isOverIcon(x, y, cur)) {
+        flagA = true;
+        self.overSquare(cur);
+      }
+    });
+    if (!flagA) self.outSquare('A');
+
+    // Mouse over B : show fraction
+    self.B.blocks.forEach(cur => {
+      if (game.math.isOverIcon(x, y, cur)) {
+        flagB = true;
+        self.overSquare(cur);
+      }
+    });
+    if (!flagB) self.outSquare('B');
+
+    if (!flagA && !flagB) document.body.style.cursor = 'auto';
+
+    // Mouse over navigation icons : show name
+    navigationIcons.onInputOver(x, y);
+
+    game.render.all();
+  },
+
+  /**
+   * Saves players data after level ends - to be sent to database. <br>
    *
-   * Attention: the "line_" prefix data table must be compatible to data table fields (MySQL server)
+   * Attention: the 'line_' prefix data table must be compatible to data table fields (MySQL server)
+   * 
    * @see /php/save.php
    */
   postScore: function () {
@@ -472,8 +469,7 @@ const squareTwo = {
       + ', valueB: ' + self.B.selected;
 
     // FOR MOODLE
-    if (moodle) sendToDB(data, self.result, game.timer.elapsed);
-    else sendToDB(data);
+    sendToDB(data);
   }
 
 };

+ 32 - 45
js/stundentReport.js

@@ -1,80 +1,67 @@
-// FOR MOODLE 
-
-/**
- * To be show on moodle
+/************************************************************************************
+ * This code is used EXCLUSIVELY when iFractions is runnign inside Moodle via iAssign 
+ * as an iLM (interactive learning module) and the global variable moodle=true.
+ * 
+ * This file holds game states. 
+ ************************************************************************************/
+
+/** 
+ * [STUDENT REPORT STATE] Screen that shows the stats of a previously played game (exclusive to moodle).
+ * 
+ * FOR MOODLE
+ * 
+ * @namespace
  */
 const studentReport = {
 
+  /** FOR MOODLE
+   * Main code
+   */
   create: function () {
 
     const offsetW = defaultWidth / 4;
     let x = offsetW / 2;
     let y = defaultHeight / 2 - 50;
 
+    // Background
     game.add.geom.rect(0, 0, defaultWidth, defaultHeight, undefined, 0, colors.blueBckg, 1);
     game.add.image(300, 100, 'cloud');
     game.add.image(660, 80, 'cloud');
     game.add.image(110, 85, 'cloud', 0.8);
-
     for (let i = 0; i < 9; i++) { game.add.image(i * 100, defaultHeight - 100, 'floor'); }
 
+    // Title
     game.add.text(defaultWidth / 2, 80, game.lang.results, textStyles.h1_green);
     game.add.image(x - 40, y - 70, info[gameTypeString].gameTypeUrl, 0.8);
 
+    // Game info
     text = game.lang[gameShape].charAt(0).toUpperCase() + game.lang[gameShape].slice(1);
     text = game.lang.game + ': ' + text + ((gameTypeString.slice(-3) == 'One') ? ' I' : ' II');
-
     game.add.text(190, y - 50, text, textStyles.h4_brown).align = 'left';
     game.add.text(190, y - 25, game.lang.game_mode + ': ' + gameMode, textStyles.h4_brown).align = 'left';
     game.add.text(190, y, game.lang.operation + ': ' + gameOperation, textStyles.h4_brown).align = 'left';
     game.add.text(190, y + 25, game.lang.difficulty + ': ' + gameDifficulty, textStyles.h4_brown).align = 'left';
 
+    // Student info
     y = defaultHeight - 200;
-
     for (let i = 0; i < 4; i++, x += offsetW) {
-
+      // If level wasnt completed, show broken sign
       if (moodleVar.hits[i] == 0) {
         const sign = game.add.image(x, defaultHeight - 100, 'broken_sign', 0.7);
         sign.anchor(0.5, 0.5);
-        continue;
-      }
-
-      const sign = game.add.image(x, defaultHeight - 100, 'sign', 0.7);
-      sign.anchor(0.5, 0.5);
-      game.add.text(x, defaultHeight - 100, '' + (i + 1), textStyles.h2_white);
-
-      game.add.geom.rect(x - 55, y - 40, 5, 135, undefined, 0, colors.blueMenuLine);
-      game.add.text(x - 40, y - 25, game.lang.time + ': ' + convertTime(moodleVar.time[i]), textStyles.h4_brown).align = 'left';
-      game.add.text(x - 40, y, game.lang.hits + ': ' + moodleVar.hits[i], textStyles.h4_brown).align = 'left';
-      game.add.text(x - 40, y + 25, game.lang.errors + ': ' + moodleVar.errors[i], textStyles.h4_brown).align = 'left';
-    }
-
-  },
-
-  convertTime: function (s) {
-
-    let h = 0, m = 0;
-
-    if (s > 1200) {
-      h = s / 1200;
-      s = s % 1200;
-    }
+      } else {
+        // If level was completed shows sign with level number and student report
+        const sign = game.add.image(x, defaultHeight - 100, 'sign', 0.7);
+        sign.anchor(0.5, 0.5);
+        game.add.text(x, defaultHeight - 100, '' + (i + 1), textStyles.h2_white);
 
-    if (s > 60) {
-      m = s / 60;
-      s = s % 60;
+        game.add.geom.rect(x - 55, y - 40, 5, 135, undefined, 0, colors.blueMenuLine);
+        game.add.text(x - 40, y - 25, game.lang.time + ': ' + game.math.convertTime(moodleVar.time[i]), textStyles.h4_brown).align = 'left';
+        game.add.text(x - 40, y, game.lang.hits + ': ' + moodleVar.hits[i], textStyles.h4_brown).align = 'left';
+        game.add.text(x - 40, y + 25, game.lang.errors + ': ' + moodleVar.errors[i], textStyles.h4_brown).align = 'left';
+      }
     }
 
-    h = '' + h;
-    m = '' + m;
-    s = '' + s;
-
-    if (h.length < 2) h = '0' + h;
-    if (m.length < 2) m = '0' + m;
-    if (s.length < 2) s = '0' + s;
-
-    return h + ':' + m + ':' + s;
-
   }
 
-}
+};