import { j3pAddContent, j3pAddElt, j3pArrondi, j3pElement, j3pEmpty, j3pGetNewId, j3pGetRandomInt, j3pIsHtmlElement, j3pShowError, j3pShuffle } from 'src/legacy/core/functions'
import { j3pAffiche } from 'src/lib/mathquill/functions'
import 'src/legacy/outils/memory/memory.css'
import { j3pCreeSVG } from 'src/legacy/core/functionsSvg'
import { getMtgCore } from 'src/lib/outils/mathgraph'
import foncarte from './foncarte.jpg'
import foncarte2 from './foncarte2.jpg'
import { placeJouerSon } from 'src/legacy/outils/zoneStyleMathquill/functions'
const typesValides = ['string', 'mathgraph', 'image', 'son']
/**
* @typedef Carte
* @property {string} type string|image|mathgraph
* @property {string} content string|image|mathgraph Pour le type string c’est le contenu à afficher (qui peut être passé directement sans fournir un objet), pour image c’est son url, et pour mathgraph le code base64 de la figure (pas encore implémenté)
*/
/**
* @typedef CoupleCartes
* @property {string|Carte} 0
* @property {string|Carte} 1
*/
class Memory {
/**
* @param conteneur div qui contient le memory
* @param {CoupleCartes[]} couples tableau qui contient les couples (avec éventuellement leur type si c’est une figure mg32 mais j’ai pas fait )
* @param onComplete fonction appelée quand le memory est terminé
* @param {object} [options]
* @param {boolean} [options.antymemory=false] Si true les cartes sont visibles des le départ
*/
constructor (conteneur, couples, onComplete, options = {}) {
if (typeof conteneur === 'string') conteneur = j3pElement(conteneur)
if (!j3pIsHtmlElement(conteneur)) throw Error('Conteneur invalide')
/**
* Le conteneur
* @type {HTMLElement}
*/
this.conteneur = conteneur
// check de couples
if (!Array.isArray(couples) || couples.some(couple => !Array.isArray(couple) || couple.length !== 2)) {
throw Error('couples fournis invalides (il faut un tableau de tableaux à deux éléments)')
}
for (const couple of couples) {
for (const elt of couple) {
if (typeof elt === 'string' || typeof elt === 'number') continue
if (typeof elt !== 'object') throw Error(`couples contient un élément invalide ${typeof elt}`)
// if (!elt.content || typeof elt.content !== 'string') throw Error(`couples contient un élément invalide (propriété content de type ${typeof elt.content} qui vaut ${elt.content})`)
if (!typesValides.includes(elt.type)) throw Error(`couples contient un élément invalide (propriété type qui n’est pas dans ${typesValides.join('|')}`)
}
}
/**
* Les couples de contenus.
* @type {CoupleCartes}
*/
this.couples = [...couples]
this.nbCouples = this.couples.length
this.antimemory = Boolean(options.antimemory)
if (typeof onComplete !== 'function') throw Error('Paramètre onComplete invalide')
this.onComplete = onComplete
this.ok = false
this.nbCoups = 0
this.disabled = false
this.score = 0
this.okLance = false
this.petikeblo = false
this.clicPretListener = this.clicPret.bind(this)
}
_afficheCartes () {
this.nbLignes = Math.ceil(Math.sqrt(this.nbCouples * 2))
this.PourCompt = j3pAddElt(this.conteneur, 'div', null, {})
const ty = (this.antimemory) ? 2 * this.nbCouples : (2 * this.nbCouples)
const ty1 = (this.antimemory) ? ' coups (attendus: ' + ty + ' , max: ' + 2 * ty + ')' : ' coups (attendus: ' + ty + ')'
j3pAddContent(this.PourCompt, 'Compteur: ' + this.nbCoups + ty1)
this.PourCompt.style.color = '#0a0'
this.plateau = j3pAddElt(this.conteneur, 'div', null, {
style: {
display: 'grid',
gridTemplateColumns: 'repeat(' + this.nbLignes + ', 1fr)',
gridTemplateRows: 'repeat(' + Math.ceil(this.nbCouples * 2 / this.nbLignes) + ', 1fr)',
rowGap: '10px',
columnGap: '10px',
perspective: '1000px',
gridAutoRows: '100px',
userSelect: 'none'
}
})
/**
* @typedef Carte
* @property {number} [indexCouple] L’index du couple de cartes concernées dans this.couples, affecté par _setContent
* @property {HTMLElement} faceT
* @property {HTMLElement} fond
* @property {HTMLElement} face
* @property {string} etat peut valoir cachée|bloquée|visible
* @property {function} onClick listener du clic sur le fond (appelle _retourne)
*/
/**
* Les cartes du memory
* @type {Carte[]}
*/
this.cartes = []
// on crée toutes les cartes, sans contenu pour le moment
for (let index = 0; index < this.nbCouples * 2; index++) {
const amf = (this.antimemory) ? 'memoryCarteFaceMontre2' : 'memoryCarteFaceRevient'
const amfond = (this.antimemory) ? 'memoryCarteDosCache2' : 'memoryCarteDosRevient'
const faceT = j3pAddElt(
this.plateau, 'div', null, {
className: amf,
style: {
gridColumn: (index % this.nbLignes) + 1,
gridRow: Math.ceil((index + 1) / this.nbLignes),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
minHeight: '100px',
minWidth: '100px',
width: '100%',
height: '100%',
borderRadius: '10px'
}
})
const fond = j3pAddElt(
this.plateau, 'div', null, {
className: amfond,
style: {
gridColumn: (index % this.nbLignes) + 1,
gridRow: Math.ceil((index + 1) / this.nbLignes)
}
})
const fonfond = (index < this.nbCouples) ? 'url(' + foncarte + ')' : 'url(' + foncarte2 + ')'
const divSon = j3pAddElt(
this.plateau, 'div', null, {
style: {
gridColumn: (index % this.nbLignes) + 1,
gridRow: Math.ceil((index + 1) / this.nbLignes),
borderRadius: '5px',
border: '1px solid black',
background: '#aeef59',
zIndex: 100,
width: '112px',
height: '41px',
paddingLeft: '5px',
display: 'none'
}
})
placeJouerSon(divSon, this.cartes[index], this, 'sonContent')
fond.style.backgroundImage = fonfond
const onClick = this._retourne.bind(this, index)
const onClick2 = this._range.bind(this, index)
const fclret = (this.antimemory) ? faceT : fond
faceT.addEventListener('click', onClick2, false)
fond.addEventListener('click', onClick2, false)
fclret.addEventListener('click', onClick, false)
this.cartes.push({
faceT,
fond,
etat: 'cachée',
onClick,
divSon
})
}
this.nbBloquees = 0
this.nbBloquees = 0
this.dejaRetourne = []
// on génère une liste mélangée des index des cartes
const ff = Array.from(this.cartes.keys())
const ff1 = j3pShuffle(ff.slice(0, ff.length / 2))
const ff2 = j3pShuffle(ff.slice(ff.length / 2))
const randomIndexes = []
for (let i = 0; i < ff1.length; i++) {
randomIndexes.push(ff1[i], ff2[i])
}
// et on affecte nos contenus à chaque carte
for (const [indexCouple, couple] of this.couples.entries()) {
for (const cardContent of couple) {
let content, type
if (typeof cardContent === 'string') {
content = cardContent
type = 'string'
} else {
content = cardContent.content
type = cardContent.type
}
const indexCarte = randomIndexes.pop()
this._setContent({ content, type, indexCouple, indexCarte })
if (!this.antimemory) {
this.cartes[indexCarte].fond.classList.remove('memoryCarteDosRevient')
this.cartes[indexCarte].faceT.classList.remove('memoryCarteFaceRevient')
this.cartes[indexCarte].fond.classList.add('memoryCarteDosCache')
this.cartes[indexCarte].faceT.classList.add('memoryCarteFaceMontre')
}
}
}
this.couplesAplace = []
}
// toutes les cartes sont maintenant complètes
clicPret () {
this._hideTout()
}
_setContent ({ content, indexCarte, indexCouple, type }) {
const carte = this.cartes[indexCarte]
switch (type) {
case 'string':
j3pAffiche(carte.faceT, null, content)
carte.faceT.style.backgroundOld = ''
break
case 'mathgraph':
{
const sgvId = j3pGetNewId('mtgmemo')
j3pCreeSVG(carte.faceT, { id: sgvId, width: 4 / 3 * content.width - 32, height: 8 / 9 * content.height + 7 })
carte.faceT.margin = '5px'
// carte.faceT.style.width = (4 / 3 * content.width - 32 + 10) + 'px'
carte.faceT.style.background = '#fff'
carte.faceT.style.backgroundOld = '#fff'
carte.faceT.style.display = 'flex'
this.mtgAppLecteur.addDoc(sgvId, content.txtFigure, true)
if (content.A) this.mtgAppLecteur.giveFormula2(sgvId, 'A', content.A)
if (content.ALEA) this.mtgAppLecteur.giveFormula2(sgvId, 'ALEA', content.ALEA)
this.mtgAppLecteur.calculateAndDisplayAll(true)
// this.mtgAppLecteur.display(sgvId)
}
break
case 'image': {
const immm = j3pAddElt(carte.faceT, 'img', '', { src: content.txtFigure, width: content.width, height: content.height })
immm.style.width = content.width + 'px'
immm.style.height = content.height + 'px'
carte.faceT.style.background = '#fff'
carte.faceT.style.backgroundOld = '#fff'
}
break
case 'son':
carte.divSon.style.display = ''
carte.yaSon = true
carte.sonContent = content.txtFigure
carte.faceT.style.backgroundOld = ''
break
default:
throw Error(`type ${type} inconnu`)
}
carte.indexCouple = indexCouple
}
_retourne (indexCarte) {
if (this.petikeblo) return
// Suivant le nb de cartes déjà retournées
// si 0 ça la _retourne
// si 1 ça la _retourne puis cache les deux 3 secondes plus tard
// si 2 rien
if (this.dejaRetourne.length < 2) this._show(indexCarte)
}
_range (indexCarte) {
if (this.petikeblo) return
if (this.dejaRetourne.length === 1 && this.antimemory) {
if (this.dejaRetourne[0].indexCarte === indexCarte) {
this._hide()
this.petikeblo = true
setTimeout(this._remetkeblo.bind(this), 10)
return
}
}
if (this.dejaRetourne.length === 2) {
clearTimeout(this.funcHide)
this._hide()
this.petikeblo = true
setTimeout(this._remetkeblo.bind(this), 10)
}
}
_remetkeblo () {
this.petikeblo = false
}
_testEgal () {
if (this.dejaRetourne[0].indexCouple === this.dejaRetourne[1].indexCouple) {
this.nbBloquees += 2
for (const { indexCarte } of this.dejaRetourne) {
const carte = this.cartes[indexCarte]
carte.faceT.classList.remove('memoryCarteFaceMontre')
carte.fond.classList.remove('memoryCarteDosCache')
carte.faceT.classList.remove('memoryCarteFaceRevient')
carte.fond.classList.remove('memoryCarteDosRevient')
carte.faceT.classList.add('memoryCarteFaceBloquee')
carte.fond.classList.add('memoryCarteDosBloquee')
carte.faceT.style.pointerEvents = 'none'
carte.faceT.style.background = '#75e579'
carte.etat = 'bloquée'
}
this.dejaRetourne = []
if (this.nbBloquees === this.nbCouples * 2) {
this.ok = true
if (!this.antimemory) {
const dep = Math.max(0, this.nbCoups - this.nbCouples * 2 - 2)
this.score = Math.max(0, 1 - dep * 0.05)
} else {
this.score = Math.max(0, Math.min(1, 2 - this.nbCoups / (this.nbCouples * 2) + 0.1))
}
this.onComplete(this.score)
}
} else {
for (const { indexCarte } of this.dejaRetourne) {
const carte = this.cartes[indexCarte]
carte.faceT.style.background = '#e30a0a'
carte.faceT.style.transition = 'background 1.5s'
setTimeout(() => {
carte.faceT.style.background = carte.faceT.style.backgroundOld
carte.faceT.style.transition = ''
}, 2000)
}
const temps = (this.antimemory) ? 2000 : 15000
this.funcHide = setTimeout(this._hide.bind(this), temps)
this.petikeblo = true
setTimeout(this._remetkeblo.bind(this), 2000)
}
}
/**
* Cache les cartes retournées
*/
_hide () {
if (this.disabled) return
for (const { indexCarte } of this.dejaRetourne) {
const carte = this.cartes[indexCarte]
const amfond = (!this.antimemory) ? 'memoryCarteDosRevient' : 'memoryCarteDosCache2'
const avfond = (!this.antimemory) ? 'memoryCarteDosCache' : 'memoryCarteDosCache2'
if (!this.antimemory) {
carte.divSon.style.display = 'none'
}
const amf = (!this.antimemory) ? 'memoryCarteFaceRevient' : 'memoryCarteFaceMontre2'
const avf = (!this.antimemory) ? 'memoryCarteFaceMontre' : 'memoryCarteFaceRevient2'
carte.faceT.classList.remove(avf)
carte.fond.classList.remove(avfond)
carte.faceT.classList.add(amf)
carte.fond.classList.add(amfond)
carte.faceT.style.background = carte.faceT.style.backgroundOld
carte.etat = 'cachée'
}
this.dejaRetourne = []
}
_hideTout () {
this.cartes.forEach(carte => {
if (!this.antimemory) {
carte.divSon.style.display = 'none'
}
carte.faceT.classList.remove('memoryCarteFaceMontre')
carte.fond.classList.remove('memoryCarteDosCache')
carte.faceT.classList.add('memoryCarteFaceRevient')
carte.fond.classList.add('memoryCarteDosRevient')
})
}
_show (indexCarte) {
if (this.disabled) return
const carte = this.cartes[indexCarte]
if (carte.etat === 'visible') return
let indexCouple = carte.indexCouple
if (carte.indexCouple === undefined) {
j3pEmpty(carte.faceT)
let ic, oksort
let cmpt = 0
do {
cmpt++
ic = j3pGetRandomInt(0, this.couplesAplace.length - 1)
oksort = (cmpt < 100) && ((this.dejaRetourne.length === 0) ? false : this.couplesAplace[ic].indexCouple === this.dejaRetourne[0].indexCouple)
} while (oksort)
indexCouple = this.couplesAplace[ic].indexCouple
this._setContent({ content: this.couplesAplace[ic].content, indexCarte, indexCouple, type: this.couplesAplace[ic].type })
this.couplesAplace.splice(ic, 1)
}
this.nbCoups++
j3pEmpty(this.PourCompt)
const ty = (this.antimemory) ? 2 * this.nbCouples : (2 * this.nbCouples)
const ty1 = (this.antimemory) ? ' coups (attendus: ' + ty + ' , max: ' + 2 * ty + ')' : ' coups (attendus: ' + ty + ')'
j3pAddContent(this.PourCompt, 'Compteur: ' + this.nbCoups + ty1)
if (!this.antimemory) {
if (carte.yaSon) carte.divSon.style.display = ''
}
if (this.nbCoups > ty) this.PourCompt.style.color = '#F00'
if (this.antimemory && this.nbCoups >= 2 * ty) this.onComplete()
carte.etat = 'visible'
this.dejaRetourne.push({ indexCouple, indexCarte })
const avfond = (!this.antimemory) ? 'memoryCarteDosRevient' : 'memoryCarteDosCache2'
const amfond = (!this.antimemory) ? 'memoryCarteDosCache' : 'memoryCarteDosCache2'
const avf = (!this.antimemory) ? 'memoryCarteFaceRevient' : 'memoryCarteFaceMontre2'
const amf = (!this.antimemory) ? 'memoryCarteFaceMontre' : 'memoryCarteFaceRevient2'
carte.fond.classList.remove(avfond)
carte.faceT.classList.remove(avf)
carte.fond.classList.add(amfond)
carte.faceT.classList.add(amf)
if (this.antimemory) carte.faceT.style.background = '#2573b4'
// si on avait 1, c’est passé à 2 ligne précédente, on teste si on a une paire
if (this.dejaRetourne.length === 2) this._testEgal()
}
disable () {
if (!this.okLance) {
setTimeout(() => {
this.disable()
}, 10)
return
}
this.disabled = true
for (const carte of this.cartes) {
carte.fond.removeEventListener('click', carte.onClick, false)
if (carte.indexCouple === undefined) {
const cp = this.couplesAplace.pop()
this._setContent({ content: cp.content, indexCouple: cp.indexCouple, indexCarte: this.cartes.indexOf(carte), type: cp.type })
}
if (carte.etat !== 'bloquée') {
carte.etat = 'cachée'
carte.fond.classList.remove('memoryCarteDosRevient')
carte.faceT.classList.remove('memoryCarteFaceRevient')
carte.faceT.classList.remove('memoryCarteFaceMontre')
carte.fond.classList.remove('memoryCarteDosCache')
carte.fond.classList.add('memoryCarteDosCache')
carte.faceT.classList.add('memoryCarteFaceDisable')
carte.faceT.style.background = ''
carte.faceT.addEventListener('click', this._showPeer.bind(this, carte.indexCouple))
}
}
}
isOk () {
return this.ok === true
}
getScore () {
return this.score
}
getScorePourcent () {
return j3pArrondi(this.nbBloquees / (this.nbCouples * 2), 1)
}
_showPeer (indexCouple) {
for (const carte of this.cartes) {
if (carte.indexCouple === indexCouple) {
if (carte.etat === 'cachée') {
carte.faceT.style.border = '2px solid blue'
carte.faceT.style.background = '#1bc5d9'
carte.etat = 'visible'
} else {
carte.faceT.style.border = ''
carte.faceT.style.background = ''
carte.etat = 'cachée'
}
} else {
carte.faceT.style.border = ''
carte.faceT.style.background = ''
carte.etat = 'cachée'
}
}
}
/**
* Créé un Memory, l’affiche dans le DOM puis le _retourne
* @param {HTMLElement} conteneur
* @param {CoupleCartes[]} couples La liste des couples, chacun est un tableau de deux éléments, chaque élément peut être une string (qui sera passée à j3pAffiche) ou un objet {content, type} où type peut valoir image|mathgraph
* @param {function} onComplete la callback qui sera appelée quand le memory sera complété
* @return {Memory}
*/
static create (conteneur, couples, onComplete, param) {
const memory = new Memory(conteneur, couples, onComplete, param)
getMtgCore({ withMathJax: true })
.then(
// success
(mtgAppLecteur) => {
memory.mtgAppLecteur = mtgAppLecteur
memory.mtgAppLecteur.removeAllDoc()
memory._afficheCartes()
memory.okLance = true
},
// failure
(error) => {
j3pShowError(error, { message: 'mathgraph n’est pas correctement chargé', mustNotify: true })
})
// plantage dans enonceMain
.catch(j3pShowError)
return memory
}
}
export default Memory