exercices/3e/3I10-1.js

import Exercice from '../Exercice.js'
import { choice, combinaisonListes, contraindreValeur, deuxColonnes, lampeMessage, lettreMinusculeDepuisChiffre, listeQuestionsToContenu, randint, texteGras } from '../../modules/outils.js'
import { point } from '../../modules/2d.js'
import { noteLaCouleur, plateau2dNLC } from '../../modules/noteLaCouleur.js'
import { colorToLatexOrHTML, fixeBordures, mathalea2d } from '../../modules/2dGeneralites.js'
import { ajouterAx, ajouterAy, allerA, angleScratchTo2d, attendre, baisseCrayon, creerLutin, leveCrayon, orienter } from '../../modules/2dLutin.js'
import { context } from '../../modules/context.js'
import { propositionsQcm } from '../../modules/interactif/questionQcm.js'
import { scratchblock } from '../../modules/scratchblock.js'

export const titre = 'Analyser des scripts Scratch'
export const interactifReady = true
export const interactifType = 'qcm'
export const amcReady = true
export const amcType = 'AMCHybride'

export const dateDePublication = '27/09/2022'

/**
 * Analyser un programme scratch utilisant NoteLaCouleur
 * @author Jean-Claude Lhote
 */
export const uuid = '2ecd9'
export const ref = '3I10-1'
export default function ScratchMultiScript () {
  'use strict'
  Exercice.call(this) // Héritage de la classe Exercice()
  function nombreDeNegatifs (arr) {
    const initialValue = 0
    return arr.reduce((previousValue, currentValue) => previousValue + (currentValue < 0 ? 1 : 0), initialValue)
  }
  this.spacing = 2
  this.nbQuestions = 1
  this.titre = titre
  this.typeExercice = 'Scratch'
  this.nbCols = 1
  this.nbColsCorr = 1
  this.sup = '1-2-3'
  this.correctionDetailleeDisponible = true
  this.correctionDetaille = false
  this.listePackages = ['scratch3', 'bclogo']
  this.nouvelleVersion = function () {
    this.introduction = lampeMessage({
      titre: scratchblock(`\\begin{scratch}[${context.issortieNB ? 'print,' : ''}fill,blocks,scale=0.5]\n\\blockmoreblocks{Note la couleur}\\end{scratch}`),
      texte: 'Cette brique donne la couleur de la case sur laquelle est positionné le lutin.',
      couleur: 'nombres'
    })
    const lePlateau = plateau2dNLC(1, false, 0.5, true)
    let listeQuestions = []
    const listeCouleurs = ['Blanc', 'Vert', 'Bleu', 'Rouge', 'Noir', 'Rose', 'Orange', 'Jaune', 'Gris']
    let choixQuestions = []
    this.consigne = 'Donner la série de couleurs affichées par ce' + (this.nbQuestions > 1 ? 's' : '') + ' programme' + (this.nbQuestions > 1 ? 's.' : '.')
    this.listeQuestions = [] // Liste de questions
    this.listeCorrections = [] // Liste de questions corrigées
    this.autoCorrection = []
    const mesQcm = []
    let indexReponse = 0
    if (!this.sup) { // Si aucune liste n'est saisie
      listeQuestions = [1, 2, 3]
    } else {
      if (Number(this.sup) > 1 && Number(this.sup) < 3) {
        this.sup = contraindreValeur(1, 3, Number(this.sup), 1)
        listeQuestions = new Array(this.nbQuestions).fill(this.sup)
      } else {
        const optionsQuestions = this.sup.split('-')// Sinon on créé un tableau à partir des valeurs séparées par des -
        for (let i = 0; i < optionsQuestions.length; i++) { // on a un tableau avec des strings : ['1', '1', '2']
          listeQuestions[i] = contraindreValeur(1, 3, Number(optionsQuestions[i]), 1)
        }
        if (listeQuestions.length === 0) {
          listeQuestions = [1, 2, 3]
        }
      }
    }
    choixQuestions = combinaisonListes(listeQuestions, this.nbQuestions)
    const noteLesCouleurs = []
    const lutins = []
    const couleurs = []
    context.unitesLutinParCm = 20 // avancer de 10 pour le lutin lui fait parcourir 1cm (en fait 0,5cm car j'ai ajouté un scale=0.5 pour la sortie latex)
    context.pixelsParCm = 20 // 20 pixels d'écran représentent 1cm (enfin ça dépend du zoom, donc c'est juste un réglage par défaut)

    // choixQuestions = shuffle(choixQuestions) // pour mélanger
    for (let i = 0, cpt = 0; i < this.nbQuestions && cpt < 50;) {
      const objetsCorrection = []
      couleurs[i] = []
      const x = []
      const y = []
      const touchePressee = lettreMinusculeDepuisChiffre(i + 1)
      const choixBriqueInitiale = [
        ['\\blockinit{quand \\greenflag est cliqué}\n', 'Quand le drapeau vert est cliqué'],
        ['\\blockinit{quand ce sprite est cliqué}\n', 'Quand ce sprite est cliqué'],
        [`\\blockinit{quand la touche \\selectmenu{${touchePressee}} est pressée}\n`, `Quand la touche ${touchePressee} est pressée`],
        ['\\blockinit{quand la touche \\selectmenu{n\'importe laquelle} est pressée}\n', "Quand n'importe quelle touche est pressée"]
      ]

      let texteScratch = `\\begin{scratch}[${context.issortieNB ? 'print,' : ''}fill,blocks,scale=0.8]\n`
      const rotations = ['\\turnright{}', '\\turnleft{}']
      const orientations = [0, 90, 180]
      texteScratch += choixBriqueInitiale[2][0]
      noteLesCouleurs[i] = noteLaCouleur({ x: 0, y: 0, plateau: lePlateau.plateauNLC, relatif: true })
      const pion = noteLesCouleurs[i]
      lutins[i] = creerLutin()
      lutins.color = colorToLatexOrHTML('green') // la couleur de la trace
      lutins.epaisseur = 3 // son epaisseur
      lutins.pointilles = false // le type de pointillés (on peut mettre false pour avoir un trait plein)
      allerA(0, 0, lutins[i]) // ça c'est pour faire bouger le lutin (écrire le programme ne le fait pas exécuter !)
      baisseCrayon(lutins[i]) // à partir de là, le lutin laissera une trace (ses positions successives sont enregistrées dans lutins[i].listeTraces)

      switch (choixQuestions[i]) {
        case 1:
          x[0] = 0
          y[0] = 0
          do {
            for (let j = 1; j <= 3; j++) {
              x[j] = randint(-6, 5, x)
              y[j] = randint(-4, 3, [...y, ...x])
            }
            for (let j = 1; j <= 3; j++) {
              x[j] = x[j] * 30 + 15
              y[j] = y[j] * 30 + 15
            }
          } while (nombreDeNegatifs([...x, ...y]) < 3)
          leveCrayon(lutins[i])
          texteScratch += '\\blockpen{effacer tout}\n'
          texteScratch += `\\blockmove{aller à x: \\ovalnum{${x[0]}} y: \\ovalnum{${y[0]}}}\n`
          texteScratch += "\\blockmove{s'orienter à \\ovalnum{90}}\n"
          orienter(angleScratchTo2d(90), lutins[i])
          texteScratch += '\\blockpen{stylo en position d\'écriture}\n'
          baisseCrayon(lutins[i])
          for (let j = 1; j <= 3; j++) {
            texteScratch += `\\blockmove{aller à x: \\ovalnum{${x[j]}} y: \\ovalnum{${y[j]}}}\n`
            allerA(x[j], y[j], lutins[i])
            pion.currentPos = { x: x[j], y: y[j] }
            texteScratch += '\\blockmoreblocks{Note la couleur}\n'
            couleurs[i].push(pion.nlc())
            attendre(5, lutins[i])
          }
          texteScratch += '\\blockpen{relever le stylo}\n'
          leveCrayon(lutins[i])
          texteScratch += '\\blockstop{stop \\selectmenu{tout}}'

          break

        case 2:
          x.push(randint(-5, 0) * 30 + 15)
          y.push(randint(0, 1) * 30 + 15)
          if (choice([true, false])) {
            x.push(60, 60, 60) // ça marche avec ces valeurs sans sortir du plateau...
            y.push(30, 30, 30) // on peut éventuellement changer à condition de vérifier si ça ne sort pas...
          } else {
            x.push(30, 30, 30) // ça marche avec ces valeurs sans sortir du plateau...
            y.push(60, 60, 60) // on peut éventuellement changer à condition de vérifier si ça ne sort pas...
          }
          leveCrayon(lutins[i])
          texteScratch += '\\blockpen{effacer tout}\n'
          texteScratch += `\\blockmove{aller à x: \\ovalnum{${x[0]}} y: \\ovalnum{${y[0]}}}\n`
          texteScratch += `\\blockmove{s'orienter à \\ovalnum{${i % 3 < 2 ? 90 : 180}}}\n`
          pion.currentPos = { x: x[0], y: y[0] }
          allerA(x[0], y[0], lutins[i])
          pion.currentOrientation = i % 3 < 2 ? 90 : 180
          orienter(angleScratchTo2d(i % 3 < 2 ? 90 : 180), lutins[i])
          texteScratch += '\\blockpen{stylo en position d\'écriture}\n'
          baisseCrayon(lutins[i])
          texteScratch += `\\blockrepeat{répéter \\ovalnum{2} fois}{
\\blockrepeat{répéter \\ovalnum{2} fois}{
\\blockmove{avancer de \\ovalnum{${x[i % 3 + 1]}} pas}
\\blockmove{tourner ${rotations[i % 2]} de \\ovalnum{90} degrés}
\\blockmove{avancer de \\ovalnum{${y[i % 3 + 1]}} pas}
\\blockmove{tourner ${rotations[(i + 1) % 2]} de \\ovalnum{90} degrés}
\\blockmoreblocks{Note la couleur}
}
\\blockmove{tourner ${rotations[(i % 3 === 2 ? 1 : 0)]} de \\ovalnum{90} degrés}
}\n`
          for (let k = 0; k < 2; k++) {
            for (let l = 0; l < 2; l++) {
              const test = pion.testInstruction(`AV${x[i % 3 + 1]}`, lutins[i])
              if (test[0]) {
                pion.currentPos.x = test[1]
                pion.currentPos.y = test[2]
                pion.currentOrientation = test[3]
                lutins[i] = test[5]
              } else {
                //   throw Error('Le mouvement n\'est pas valide : sortie de plateau')
              }
              if (rotations[i % 2] === '\\turnright{}') {
                const test = pion.testInstruction('TD90', lutins[i])
                if (test[0]) {
                  pion.currentPos.x = test[1]
                  pion.currentPos.y = test[2]
                  pion.currentOrientation = test[3]
                  lutins[i] = test[5]
                } else {
                  //  throw Error('Le mouvement n\'est pas valide : sortie de plateau')
                }
              } else {
                const test = pion.testInstruction('TG90', lutins[i])
                if (test[0]) {
                  pion.currentPos.x = test[1]
                  pion.currentPos.y = test[2]
                  pion.currentOrientation = test[3]
                  lutins[i] = test[5]
                } else {
                //  throw Error('Le mouvement n\'est pas valide : sortie de plateau')
                }
              }
              const test2 = pion.testInstruction(`AV${y[i % 3 + 1]}`, lutins[i])
              if (test2[0]) {
                pion.currentPos.x = test2[1]
                pion.currentPos.y = test2[2]
                pion.currentOrientation = test2[3]
                lutins[i] = test2[5]
              } else {
              //  throw Error('Le mouvement n\'est pas valide : sortie de plateau')
              }
              if (rotations[(i + 1) % 2] === '\\turnright{}') {
                const test3 = pion.testInstruction('TD90', lutins[i])
                pion.currentPos.x = test3[1]
                pion.currentPos.y = test3[2]
                pion.currentOrientation = test3[3]
                lutins[i] = test3[5]
              } else {
                const test3 = pion.testInstruction('TG90', lutins[i])
                if (test3[0]) {
                  pion.currentPos.x = test3[1]
                  pion.currentPos.y = test3[2]
                  pion.currentOrientation = test3[3]
                  lutins[i] = test3[5]
                } else {
                //  throw Error('Le mouvement n\'est pas valide : sortie de plateau')
                }
              }
              attendre(5, lutins[i])
              couleurs[i].push(pion.nlc())
            }
            let test4
            if (rotations[(i % 3 === 2 ? 1 : 0)] === '\\turnright{}') {
              test4 = pion.testInstruction('TD90', lutins[i])
            } else {
              test4 = pion.testInstruction('TG90', lutins[i])
            }
            pion.currentPos.x = test4[1]
            pion.currentPos.y = test4[2]
            pion.currentOrientation = test4[3]
            lutins[i] = test4[5]
          }
          texteScratch += '\\blockpen{relever le stylo}\n'
          leveCrayon(lutins[i])
          texteScratch += '\\blockstop{stop \\selectmenu{tout}}\n'

          break

        case 3:
          x.push(randint(-4, 2) * 30 + 15)
          y.push(randint(-4, 2) * 30 + 15)
          x.push(-120, 30, 30, 60, 30, 30)
          y.push(30, -120, -30, 30, 60, 60)
          leveCrayon(lutins[i])
          texteScratch += '\\blockpen{effacer tout}\n'
          texteScratch += `\\blockmove{aller à x: \\ovalnum{${x[0]}} y: \\ovalnum{${y[0]}}}\n`
          pion.currentPos = { x: x[0], y: y[0] }
          allerA(x[0], y[0], lutins[i])
          texteScratch += `\\blockmove{s'orienter à \\ovalnum{${orientations[i % 3]}}}\n`
          pion.currentOrientation = orientations[i % 3]
          orienter(angleScratchTo2d(orientations[i % 3]), lutins[i])
          texteScratch += '\\blockpen{stylo en position d\'écriture}\n'
          baisseCrayon(lutins[i])
          texteScratch += `\\blockrepeat{répéter \\ovalnum{4} fois}{
\\blockifelse{si \\booloperator{\\ovalmove{${i % 3 < 1 ? 'abscisse x' : 'ordonnée y'}} > \\ovalnum{${i % 3 < 1 ? 120 : 30}}} alors}
{\\blockmove{ajouter \\ovalnum{${x[i % 3 + 1]}} à x}\n\\blockmove{ajouter \\ovalnum{${y[i % 3 + 1]}} à y}\n}
{\\blockmove{ajouter \\ovalnum{${x[i % 3 + 4]}} à x}\n\\blockmove{ajouter \\ovalnum{${y[i % 3 + 4]}} à y}\n}
\\blockmoreblocks{Note la couleur}\n}\n`
          for (let k = 0; k < 4; k++) {
            if (i % 3 < 1) {
              if (lutins[i].x > (i % 3 < 1 ? 120 : 30) / context.unitesLutinParCm) {
                ajouterAx(x[i % 3 + 1], lutins[i])
                ajouterAy(y[i % 3 + 1], lutins[i])
                if (pion.testCoords(lutins[i].x * context.unitesLutinParCm, lutins[i].y * context.unitesLutinParCm)) {
                  pion.currentPos = { x: lutins[i].x * context.unitesLutinParCm, y: lutins[i].y * context.unitesLutinParCm }
                } else {
                //  throw Error('Le mouvement n\'est pas valide : sortie de plateau')
                }
              } else {
                ajouterAx(x[i % 3 + 4], lutins[i])
                ajouterAy(y[i % 3 + 4], lutins[i])
                if (pion.testCoords(lutins[i].x * context.unitesLutinParCm, lutins[i].y * context.unitesLutinParCm)) {
                  pion.currentPos = { x: lutins[i].x * context.unitesLutinParCm, y: lutins[i].y * context.unitesLutinParCm }
                } else {
                  //     throw Error('Le mouvement n\'est pas valide : sortie de plateau')
                }
              }
            } else {
              if (lutins[i].y > (i % 3 < 1 ? 120 : 30) / context.unitesLutinParCm) {
                ajouterAx(x[i % 3 + 1], lutins[i])
                ajouterAy(y[i % 3 + 1], lutins[i])
                if (pion.testCoords(lutins[i].x * context.unitesLutinParCm, lutins[i].y * context.unitesLutinParCm)) {
                  pion.currentPos = { x: lutins[i].x * context.unitesLutinParCm, y: lutins[i].y * context.unitesLutinParCm }
                } else {
                //  throw Error('Le mouvement n\'est pas valide : sortie de plateau')
                }
              } else {
                ajouterAx(x[i % 3 + 4], lutins[i])
                ajouterAy(y[i % 3 + 4], lutins[i])
                if (pion.testCoords(lutins[i].x * context.unitesLutinParCm, lutins[i].y * context.unitesLutinParCm)) {
                  pion.currentPos = { x: lutins[i].x * context.unitesLutinParCm, y: lutins[i].y * context.unitesLutinParCm }
                } else {
                  //   throw Error('Le mouvement n\'est pas valide : sortie de plateau')
                }
              }
            }
            attendre(5, lutins[i])
            couleurs[i].push(pion.nlc())
          }
          texteScratch += '\\blockpen{relever le stylo}\n'
          leveCrayon(lutins[i])
          texteScratch += '\\blockstop{stop \\selectmenu{tout}}'

          break
      }
      texteScratch += '\\end{scratch}'
      let texte = `${(this.interactif || context.isAmc) ? '' : 'Noter la séquence de couleurs produite.<br>'}`
      texte += deuxColonnes(scratchblock(texteScratch), mathalea2d(Object.assign({}, fixeBordures([lePlateau]), { scale: 0.4, style: 'display: inline' }), lePlateau), 35)

      let texteCorr = 'On obtient la série de couleurs suivante :<br> '
      texteCorr += `${texteGras(couleurs[i][0])} `
      for (let k = 1; k < couleurs[i].length; k++) {
        texteCorr += `- ${texteGras(couleurs[i][k])} `
      }
      texteCorr += '<br>'
      lutins[i].animation = `<radialGradient id="Ball" cx="8" cy="-3" r="20" gradientUnits="userSpaceOnUse">
    <stop offset="0" style="stop-color:#FFFF99"/>
    <stop offset="1" style="stop-color:#FF9400"/>
  </radialGradient> <circle fill="url(#Ball)"  r="12" stroke-width="1"
   x="${lutins[i].listeTraces[0][0] * context.pixelsParCm}"
    y="${-lutins[i].listeTraces[0][1] * context.pixelsParCm}">\n
    <animateMotion path="M ${lutins[i].listeTraces[0][0] * context.pixelsParCm} ${-lutins[i].listeTraces[0][1] * context.pixelsParCm} L`

      for (let k = 0; k < lutins[i].listeTraces.length; k++) {
        const B = point(lutins[i].listeTraces[k][2], lutins[i].listeTraces[k][3])
        lutins[i].animation += ` ${B.xSVG(context.pixelsParCm)} ${B.ySVG(context.pixelsParCm)} `
      }
      lutins[i].animation += '" begin="10s" dur="10s" repeatCount="indefinite" />; </circle>'

      objetsCorrection.push(lePlateau, lutins[i])
      texteCorr += mathalea2d(Object.assign({}, fixeBordures(objetsCorrection), { style: 'display: inline', scale: 0.4 }), objetsCorrection)
      if (!context.isAmc) { // on prépare les
        for (let k = 0; k < couleurs[i].length; k++) {
          this.autoCorrection[indexReponse + k] = {}
          this.autoCorrection[indexReponse + k].options = { ordered: true, vertical: false, nbCols: 9 }

          this.autoCorrection[indexReponse + k].propositions = []
          for (let j = 0; j < listeCouleurs.length; j++) {
            this.autoCorrection[indexReponse + k].propositions.push({
              texte: listeCouleurs[j],
              statut: couleurs[i][k] === listeCouleurs[j]
            })
          }
        }
      } else {
        this.autoCorrection[i] = {}
        this.autoCorrection[i].enonce = `${deuxColonnes(scratchblock(texteScratch), mathalea2d(Object.assign({}, fixeBordures([lePlateau]), { scale: 0.4, style: 'display: inline' }), lePlateau), 35)}`
        this.autoCorrection[i].propositions = []
        this.autoCorrection[i].propositions.push(
          {
            type: 'AMCOpen',
            propositions: [{
              enonce: 'Tracé',
              texte: texteCorr,
              statut: 0,
              sanscadre: true
            }]
          })
        for (let k = 0; k < couleurs[i].length; k++) {
          this.autoCorrection[i].propositions.push(
            {
              type: 'qcmMono',
              propositions: [],
              options: { ordered: true }
            })
        }
        for (let k = 0; k < couleurs[i].length; k++) {
          this.autoCorrection[i].propositions[k + 1].propositions = []
          for (let j = 0; j < listeCouleurs.length; j++) {
            this.autoCorrection[i].propositions[k + 1].propositions.push({
              texte: listeCouleurs[j],
              statut: listeCouleurs[j] === couleurs[i][k],
              reponse: j === 0 ? { texte: `couleur N° ${k + 1} : ` } : {}
            })
          }
        }
      }
      if (!context.isAmc && this.interactif) {
        for (let k = 0; k < couleurs[i].length; k++) {
          mesQcm[indexReponse + k] = propositionsQcm(this, indexReponse + k)
          texte += `Couleur N° ${k + 1} ? ` + mesQcm[indexReponse + k].texte
          texteCorr += `Couleur N° ${k + 1} : ` + mesQcm[indexReponse + k].texteCorr
        }
      }
      if (!context.isHtml && i !== this.nbQuestions - 1) {
        texte += '\\columnbreak'
        texteCorr += '\\columnbreak'
      }
      if (this.questionJamaisPosee(i, ...couleurs[i])) {
        this.listeQuestions.push(texte)
        this.listeCorrections.push(texteCorr)
        if (!context.isAmc) {
          indexReponse += couleurs[i].length
        } else {
          indexReponse++
        }
        i++
      }
      cpt++
    }
    listeQuestionsToContenu(this)
  }
  this.besoinFormulaireTexte = [
    'Compétence évaluée',
    'Nombres séparés par des tirets\n1 : Repérage dans le plan\n2 : Boucles répéter n fois imbriquées\n3 : Conditionnelles'
  ]
}