Compare commits

...

378 Commits

Author SHA1 Message Date
Thomas Chazot 6005281801 Image qui fonctionne
continuous-integration/drone/push Build is passing Details
6 months ago
Thomas CHAZOT 547f02227f revert 53ac430987
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 53ac430987 fix /
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 450123404e icon pour download
7 months ago
Thomas Chazot fc11d0a0e5 évite doublons
7 months ago
Thomas Chazot 6a106608ba évite doublons
7 months ago
Thomas Chazot a01003d3c8 correct background dans le endgame
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 752fbaacb2 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL bb6e6c6cba modif page home
7 months ago
Pierre FERREIRA 32bc994b41 Merge pull request 'EngGamePage' (#115) from EngGamePage into master
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL e4df530d95 modif page présentation
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL d8c5af20ba Merge branch 'master' into EngGamePage
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 669ca42bd1 modif page end game
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre FERREIRA 32e3f1ce2e Merge pull request 'fixRT10' (#114) from fixRT10 into master
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 1f00476f7f Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into fixRT10
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 294cd87672 commit pour merge le fix de thomas 🔧
7 months ago
Thomas Chazot 7d994ad89f indice afficher même après changement du graph
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 8ef1fb05bf fix pour pas qu'on puisse mettre plus de noeuds que voulu
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira fe2836b636 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into fixRT10
7 months ago
Pierre Ferreira d23c1277cd changement du nom du bouton ✏️
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot e561118af7 fix pour pas que l'on puisse appuyer plusieurs fois sur le même noeuds
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 2d5ca7ae70 nombre de noeuds et d'indice dans la session
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira d6bc06bcba fix de tout le scoreboard
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira dab6ab2755 fix de plein de bug de rt10, nouvelle gestion de paramètre, problème de trad etc. 🚑
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT de58aff695 Mise à jour de 'cryptide_project/src/Pages/Lobbies.tsx'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT af89e69255 Mise à jour de 'cryptide_project/Scripts/social_graph.sh'
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre FERREIRA e94001ce1d Merge pull request 'sonarSmells' (#110) from sonarSmells into master
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira f88e8e66b4 fix du css, et du pb dans lobby 🚑
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 5d89f8e1bd 🚑
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira 927bb1ff6b fix bug sonar 1
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre FERREIRA 50092b31d6 Merge pull request 'trad' (#109) from trad into master
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 5cdc99b168 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into trad
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 9e22cefef3 téléchargement au pdf fini
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 20822a2311 changement de tout les tostring fr pour qu'ils aient la langue dynamique
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 19d95804dd trad suite au merge 🔧
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira f2d6b89d03 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into trad
7 months ago
Pierre Ferreira 0a6e5bba9a traduction de l'historique en fr et en 📝
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 8efeaca9d6 finitions de la traduction du tuto en fr 🌐
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira e7b0dfb9b4 mise en place de la traducton sur les pages de jeu et sur la première partie du tuto 📝
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT df666c2fe6 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 712e85ed97 permet la modification du nombre d'indice
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira edb90ea926 🌐 toujours plus de trad 🌐
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 94219e01d3 enigme intermédiaire + overlay qui se barre
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 34a53d016f ajout des traduction fr et en sur le lobby 💥
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira d65ec81b59 ajout de la traduction pour toute la page NewPlay pour fr et en
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot c5aa5ea930 choix du nombre de noeuds pour les modes solos
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre FERREIRA 5032e57bab Merge pull request 'infos' (#108) from infos into master
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 7ff10c7fac Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into infos
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre FERREIRA 9204879022 Merge pull request 'fixPDgt' (#107) from fixPDgt into master
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira 74270afe4d Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into fixPDgt
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira e5fd800163 ajout de différentes langues pour la page info 🌐
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira e9226dcfdd page d'information fonctionnelle a 100%, avec toute la traduction fr et en 📝
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT de143642b2 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT b6a41d2307 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 6664096f9b Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT ef0325cd93 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira 5bf10caee7 descriptions des modes infos
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 80ba299587 changement de l'affichage en cas d'absence de lobby 🎨
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 4199e9fdd0 fix du bug pour le build
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre FERREIRA 26b164792d Merge pull request 'soundEffect :loud_volume:' (#106) from soundEffect into master
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 2467297ef2 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into soundEffect
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira ca54bf97ff Ajout de l'option de desactivé les soundeffects, dans les paramètres avec le switch ! 💥
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 6d93bfb180 changement selon les indication de thomas, et ajout du soundeffect pour la win 🔊
7 months ago
Thomas Chazot 15deb0bef0 fix de la fuite mémoire ?
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot ed394ad4d6 fix de la fuite mémoire ?
7 months ago
Pierre Ferreira 3e4233d33e Ajout de la gestion des soundeffects, et de deux sons, pour le turn et la win 🔊
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre FERREIRA 925cc2c61c Merge pull request 'tuto' (#104) from tuto into master
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 441c980866 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into tuto
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 3a11370afc correction pour la demo 🚑
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 09c648a914 "merge avec master"
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL ae53743188 Merge branch 'master' into weeklyDaily
7 months ago
Pierre FERREIRA ab96c12126 Merge pull request 'tuto' (#103) from tuto into master
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira f319283234 Merge branch 'tuto' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into tuto
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 258c01153f fin du tutorial 💥
7 months ago
Thomas Chazot 6fe508e094 pp en ligne
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 08b56ebe58 "Merge master + correctif session info"
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 3a1a5c87fe Merge branch 'master' into weeklyDaily
7 months ago
Thomas Chazot faa5408f17 new seed pour tuto + nom pour les bots pour l'immersion + autres fix
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 8d547734d8 changement du style suite à la sélection d'un joueur 💄
7 months ago
Pierre Ferreira aea3de423f changement du tuto suite aux retour de louison 📝
7 months ago
Pierre Ferreira 177a75d5ed finission du tuto et gestion du bouton d'aide. 🍱
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 9a8c49d18a Ajout de toutes les composantes pour avoir l'ensemble des stats daily et weekly
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 8f7d61b4a5 Amélioration affichages stats + modif navbar (finito le dropdown menu)
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL c376013dfe Suppression des dépendances inutiles des anciens test carousel
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 472d94ebe1 Modif bouton pour jouer + Carousel pour les stats sur les différentes parties (Nous, Daily, Weekly)
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira e478ebd963 fix de petit probleme de compilation :ambuance:
7 months ago
Pierre Ferreira 4f91b9ae10 Merge branch 'tuto' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into tuto
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira c7ecd20501 implémentation de la plupart du tutoriel pour le joueur novice 💥
7 months ago
Thomas Chazot 2c574a5d72 fix du bug pour les modes solos
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot b6582c8d16 Wouhou tuto fini
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 5bbb61080a fin du code du tuto :) <3
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 076cd70cee Merge branch 'tuto' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into tuto
7 months ago
Pierre Ferreira b258d84bff implémentation de la première partie du tuto 📝
7 months ago
Thomas Chazot 14b2bb398c fin code du tuto manque juste partie pif
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 1785c9f46b Merge branch 'tuto' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into tuto
7 months ago
Pierre Ferreira 98338431b5 affichage de carte dans un modal pour le debut du tuto
7 months ago
Thomas Chazot 4c53dfe324 step 1
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 6428533a27 Merge remote-tracking branch 'origin/debugRU' into tuto
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 49f4beb205 début tuto2
7 months ago
Pierre Ferreira d97ed83ae4 changement de l'interface du joueur actuel 🎨
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 355ef62f7f début tuto
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira abbcdef165 fix de l'emoji qui s'affiche pour les pseudos trop courts
7 months ago
Pierre Ferreira c08223fbb5 mise de localhost pour les serv 🎉
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 8b41bb5dbe tueur -> coupable ✏️
continuous-integration/drone/push Build is failing Details
7 months ago
Baptiste MARCEL e878d6e281 Modif affichage stats scoreboard + modif DESC to ASC dans DBService
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas Chazot ae0e390eee ?
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas Chazot aa102b42bb changement de dernière minute
7 months ago
Baptiste MARCEL 10edfb1f4f page info contient l'état de la session pour navbar dynamique
continuous-integration/drone/push Build is failing Details
7 months ago
Baptiste MARCEL b9f1f99e0b Correctif apporté pour le fonctionnement du temps lors d'une partie
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira c0db02283e r'ajout de la bdd dans le gitignore 🙈
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 424fb9ec7d ajout du fichier socialgraph.db et maj gitignore pour le mettre sur master
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 78dd03cefa ajout dans le scoreboard des stats daily/weekly multijoueur + correctif pour affichage quand pas de data
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot b4184eb221 Oups dernier fix
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT dde816f6da Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT fa12f266cc Mise à jour de 'cryptide_project/src/App.tsx'
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 8a1946fa09 Scoreboard Service + Controller qui permet récupérer DailyMastermind + Affichage du dailyMastermind
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre FERREIRA 766ae6c8f0 Merge pull request 'playerIcon' (#99) from playerIcon into master
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 8cbe03043c Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into playerIcon
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre FERREIRA 0c665413e2 Merge pull request 'deduc2' (#98) from deduc2 into master
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 216c11905b Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into deduc2
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre FERREIRA 771522ce59 Merge pull request 'histostyle' (#97) from histostyle into master
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 9e558d0811 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot baa38f4849 la vraiment vraiment
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas Chazot 1b27508a8d la ça devrait vraiment complétement marcher
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas Chazot 680d4681eb ça devrait marcher
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 93aa011b1d test variable d'envi
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT 89f376801b Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas Chazot bc24e060e5 variable d'environnement pour le path avec le /
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 6d390d0341 variable d'environnement pour le path avec le /
7 months ago
Pierre Ferreira 953c79ac2a Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into playerIcon
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 4c5715ac40 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into deduc2
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira 566856374a 📝 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into histostyle
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira ba9115555c ajout du nouvelle affichage de joueur pendant la partie, ainsi que la gestion de cette derniere. de plus, gestion des noms trop grand pour celle ci 💥
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT e179471924 Mise à jour de 'cryptide_project/src/App.tsx'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 7aa05747df Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 2c298aca47 variable d'environnement pour le path
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT a05f07f80c Mise à jour de '.drone.yml'
continuous-integration/drone/push Build was killed Details
7 months ago
Thomas CHAZOT 38b75a6ca4 Mise à jour de 'cryptide_project/src/App.tsx'
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 259e196162 Merge branch 'master' into weeklyDaily
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 19e163cd4f modification du password possible
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 4f262401ce Mise à jour de 'cryptide_project/nginx.conf'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 97de73e6b2 Mise à jour de 'cryptide_project/nginx.conf'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 616114185d Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 2ea4ea1d31 Mise à jour de 'cryptide_project/nginx.conf'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT d5f74860ef Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 134724b82c Mise à jour de 'cryptide_project/package.json'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT b485cd49dc Mise à jour de 'cryptide_project/nginx.conf'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT dd2a0e4215 Mise à jour de 'cryptide_project/nginx.conf'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT d6dedc636e Mise à jour de 'cryptide_project/nginx.conf'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT fcb3594201 Ajouter 'cryptide_project/nginx.conf'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 6f3f01bbb5 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira 67f61ffa7d mise en place de la gestion de joueur dynamic dans la deducGrid 2
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira d4e1f28c64 tentative de passage par lien
7 months ago
Baptiste MARCEL ca15611e0f correctif suppression du compte (correctif et déconnexion)
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 80a4bb0743 correctif sur les stats si null, modif navbar, modif temps après insc et login
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 6fbfb8ee4b Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 23a22a17c4 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 9baf6ca59d Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 67891cf1ea Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 0adb5a1b0e Mise à jour de 'cryptide_project/server.js'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 5aef655460 Mise à jour de 'cryptide_project/server.js'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 7abafb9d81 Mise à jour de 'cryptide_project/server.js'
continuous-integration/drone/push Build was killed Details
7 months ago
Thomas CHAZOT 05d1a7bf6b Mise à jour de 'cryptide_project/server.js'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 2305a00d57 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build was killed Details
7 months ago
Pierre Ferreira 54f63188f0 ajout de la page avec la nouvelle grille de deduction 💥
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT b5992b2ae6 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT a39fcadf2f Mise à jour de 'cryptide_project/server.js'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT b94edb22e0 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT eedaea1b91 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT f34a7102c7 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT a4c1e882c8 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT d940cc1f1c Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT d506edcaf2 Mise à jour de 'cryptide_project/server.js'
continuous-integration/drone/push Build was killed Details
7 months ago
Thomas CHAZOT 6f388ee55e Mise à jour de 'cryptide_project/server.js'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT cbf548d9a0 Mise à jour de 'cryptide_project/server.js'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 8b7775ecca Mise à jour de 'cryptide_project/server.js'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 5a6e49b679 Mise à jour de 'cryptide_project/server.js'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT fcae78552b Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 9f3f3c8a61 "jsp"
continuous-integration/drone/push Build is failing Details
7 months ago
Baptiste MARCEL cf10c87d73 Merge branch 'master' into weeklyDaily
7 months ago
Pierre Ferreira 4213e7f7f4 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into histostyle
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 5d30ea47fc changement du style de l'historique 💄
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 1de57a2e9b ajout stats enigme facile et difficile mais sur les anciens critères
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 1428c432cb modif temps après inscr et connexion
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 279012197e ajout des stats mastermind en fin de partie
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre FERREIRA f23e9c3090 Merge pull request 'newProfil' (#96) from newProfil into master
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 97f3d45472 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into newProfil
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL e6447c1975 ajout des stats après partie online
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL 6501a134f5 Affichage des stats à 2 chiffres après la virgule + % pour les ratio
continuous-integration/drone/push Build is passing Details
7 months ago
Baptiste MARCEL f7532890a6 ajout des stats facile medium et hard en BDD + Persistance
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT c686ca78bc Mise à jour de 'cryptide_project/server.js'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 31676ca2ac Mise à jour de 'cryptide_project/server.js'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT fcb23c625a Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build was killed Details
7 months ago
Thomas CHAZOT 42b28c29b6 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 7e99866108 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT ec50a8b3e1 Ajouter 'cryptide_project/server.js'
continuous-integration/drone/push Build was killed Details
7 months ago
Thomas CHAZOT be7f1e1e19 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 711f3ecd48 Ajouter 'cryptide_project/serve.json'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT f919ca1a39 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira ba02c43f68 ajout de style pcq c'est bo 💄
continuous-integration/drone/push Build was killed Details
7 months ago
Thomas CHAZOT 11f4c43cca Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT ad29ab9163 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 5e27fb368f Mise à jour de 'cryptide_project/htaccess'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 8e7ae8b59a Mise à jour de 'cryptide_project/htaccess'
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 929dd29a3a tentative de auth pour le update mdp ⚰️
7 months ago
Thomas CHAZOT 5add06f8b0 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 756477471e Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT d023aeb5ea Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 354c9ed43c création de la gestion de modification de mdp, elle ne marche qu'avec 'coucou' mais il faut faire la gestion avec la bdd (baptou aled) 💥
7 months ago
Thomas CHAZOT 8ccc9d6923 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 0187700b88 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT f0258382bc Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 85aecad0d6 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 600d26179d Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT 1094980242 Mise à jour de 'cryptide_project/htaccess'
continuous-integration/drone/push Build was killed Details
7 months ago
Thomas CHAZOT 1a9a4f2d01 Supprimer '. dockerignore'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT fd8deb29e2 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT 0e168349b4 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT 5269d8bb30 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 8a2cc449b2 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 6a99980e5f Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT a60c4ffe35 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT 55471595c5 Ajouter 'cryptide_project/.htaccess'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 87b37e75d7 Supprimer 'cryptide_project/nginx.conf'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 0bb2e2f226 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT df5da2acae Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 5e7d337bf8 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT 0d7064c1ae Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 809fab5b03 Ajouter 'cryptide_project/nginx.conf'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 0667c0e702 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build was killed Details
7 months ago
Thomas CHAZOT c0894b004e Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 851f9f99d5 Mise à jour de 'cryptide_project/package.json'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 019fdd1eaa Mise à jour de 'cryptide_project/package.json'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT a9d7a7e020 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT a5d5ce915c Ajouter '. dockerignore'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 985fe7bf4e Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 31a397e6da Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT bc4986bf2b Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 9c5eb99894 Push pour créer la branche (+ suppression du console log dans server)
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 43132a416d Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT b7a3116d30 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 77decf02d7 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build was killed Details
7 months ago
Thomas CHAZOT 092e9e37b9 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 04032349e3 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT ffd99b04a3 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 3d0855ace1 ajout de la gestion quand il n'y a aucun lobby
7 months ago
Thomas CHAZOT cbe2388af2 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 353572942f Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 2e55fb1620 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 59428e2665 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 0ec19cf2f4 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build was killed Details
7 months ago
Thomas CHAZOT 5fa95390db Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT 32b811a559 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT 04b1f84f72 Mise à jour de 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT 7b0d111cd1 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT d5dfecd8e2 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT ea37b201ef Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT 941933fe78 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT 24ffe9e137 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build was killed Details
7 months ago
Thomas CHAZOT bafdcc4195 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT dc4263f7b7 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT edda25b6f6 Ajouter 'cryptide_project/Dockerfile'
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas CHAZOT 2af201d74e Mise à jour de '.drone.yml'
continuous-integration/drone/push Build was killed Details
7 months ago
Pierre Ferreira 9559a69e7e ajout du bouton pour revenir sur la page de jeu apres une deconnexion
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira d88c8281b3 création de la page 'newplay' avec la fusion de la page play et des lobby comme demandé par le prof.. 🍱
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira ebcc13b8bf Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into newProfil
7 months ago
Pierre Ferreira a5851654df pour merge
7 months ago
Pierre Ferreira b95a2f44c3 🔥🔥:fire
7 months ago
Pierre FERREIRA 471b627656 Merge pull request 'RejoinButton' (#95) from RejoinButton into master
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira fe1629b845 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into RejoinButton
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira b973704fc9 ajout de la section information pour rejoindre une partie
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 393a493678 export du graph au pdf
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre FERREIRA 40a95c619e Suppression de l'erreur volontaire pour le test des errorHandling 🚑🚑🚑
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre FERREIRA f1e43f9d46 Merge pull request 'lobbyfix 🎨' (#93) from lobbyfix into master
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 4ec1c67e67 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into lobbyfix
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 15a7611b4e Mise à jour de '.gitignore'
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira d9c22353e8 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into lobbyfix
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 3ba2d0e5e3 Mise à jour de 'cryptide_project/yarn.lock'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 5134b65021 Mise à jour de 'cryptide_project/yarn.lock'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas CHAZOT 0f3bdcfd2d Mise à jour de '.gitignore'
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 7e961e22d5 Merge branch 'lobbyDestroy'
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 3e298ebd3e ajout d'un slider personnalisé pour le trie des lobby 🍱
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 2cbdc01869 retourner jouer sans avoir besoin de recharger la page
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas Chazot 9c9402c3bb les bots peuvent jouer après que quelqu'un ait quitté
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira 3be98b3d7a gestion des salles déjà lancée
7 months ago
Thomas Chazot 36b48ff6bf Bouton permettant à l'utilisateur qui a quitté une partie de revenir dedans
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira d139c451dc Merge branch 'lobbyDestroy' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into lobbyfix
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 6cc16a1448 ajout du swiper pour trier les lobby (pour linstant sur les membre odd/even
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 4e5a743862 On peut quitter et être remplacé par un bot + si on est connecté et qu'on revient et bah pouf on est de nouveau dedans
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira d6ddd7cd76 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into lobbyfix
7 months ago
Pierre FERREIRA 1ba27fc4ab 🙈🙈🙈
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre FERREIRA 9a2a39197e Merge pull request 'deducGrid' (#92) from deducGrid into master
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira a00931376f Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into deducGrid
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre FERREIRA 764691f405 Merge pull request 'errorHandle :alert:' (#91) from errorHandle into master
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 1193e8ee93 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into errorHandle
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 6b7698dd46 modification du style de la page d'erreur 💄
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira 9be7326e66 ajout de la notion de serveur de prod + script qui lance le mode prod, l'error handleing fonctionne avec le mode production gad! 🎉
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira 7ff15977f8 minor changes 🙈 suite au merge, comment faire pour la gestion de la validité ? faire une liste de booléan pour chaque joueur avec autant de valeur que d'indice ?
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 663ba3be9f Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into deducGrid
7 months ago
Pierre Ferreira 5647bf15b3 debug pour que la CI passe 🐛 💚
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 4b0ecfac59 Bot prend la place des joueurs qui quittent + quand bot bug timer de 5 sc et demande au serveur à qui c'est de jouer
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira 5dbc0653d6 l'error handleing marche peut etre, est-ce du au mode developer env ??? 🔥
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira e45fe3e7ca ajout de l'error handeling (non finis) et rediretion vers une page d'erreur en cas de page not found 🐛:
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas Chazot 79a6a18f10 lobbies dynamique + change à chaque mouvement dans un lobby + variable pour savoir s'ils ont commencé
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas Chazot 7c8a5d1f4a lobbies
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas Chazot 40ab52fd21 fix setupAddress
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas Chazot a8adf7374f remove bots on empty lobby
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas Chazot 94f9c7f79f script pour setUp + fix pour localhost
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas Chazot 4d5dea1ed9 script pour setUp les adresses
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas Chazot a06e02d40e Les bots ne peuvent plus le faire non plus
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas Chazot a232fb7c0e Utilisateur ne peut plus poser un carré lorsqu'il y a des déjà un carré et s'ils ne peuvent plus en poser passe son tour
continuous-integration/drone/push Build is failing Details
7 months ago
Thomas Chazot a3fa6351db Pdp qui ne devrait pas bugger
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 42fbaf16fe fix setupAddress
continuous-integration/drone/push Build is passing Details
7 months ago
Thomas Chazot 3b09b39a12 fix lorsque l'on doit poser un carré
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre FERREIRA b380f7d927 Merge pull request 'CICD 💚' (#90) from CICD into master
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira 93aebd27f7 fix error any type ✏️
continuous-integration/drone/push Build is passing Details
7 months ago
Pierre Ferreira cce7ee7f86 fix error any type ✏️
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira 2c021e1056 fix error any type 🚑
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira 58e0e944df Merge branch 'CICD' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into CICD
continuous-integration/drone/push Build is failing Details
7 months ago
Pierre Ferreira 4071ef3d39 big merge avec master
7 months ago
Pierre Ferreira f56ee8b0e4 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into CICD
7 months ago
Pierre FERREIRA e37cee056e Merge pull request 'ajout de l'affichage du graphe en fin de jeu (comme sur demo) et fix du problème de tostring quand le joueur retourné à la page de jeu ' (#89) from mergefix into master
7 months ago
Pierre Ferreira 1dd590dd77 ajout de l'affichage du graphe en fin de jeu (comme sur demo) et fix du problème de tostring quand le joueur retourné à la page de jeu
7 months ago
Pierre FERREIRA 049e3034d2 Merge pull request 'lobbyFornite' (#88) from lobbyFornite into master
7 months ago
Pierre Ferreira f111da6c00 merge avec master 📦
7 months ago
Pierre Ferreira 0d7a434dd1 fix du code dupliqué ._.
7 months ago
Pierre Ferreira 2d31c1ddd2 ajout du modal et de la navigation refusé si jamais la salle est pleinne
7 months ago
Pierre Ferreira dddbaffe47 ajout de la navigation vers les lobby
7 months ago
Pierre Ferreira 011ac0210b Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into lobbyFornite
7 months ago
Pierre Ferreira 781904f63b ajout de la page lobby de lobby pour rejoindre un jeu 🚀
7 months ago
Pierre FERREIRA 2f6db622ec Merge pull request 'mergefix' (#87) from mergefix into master
7 months ago
Pierre Ferreira d056e790f4 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into mergefix
7 months ago
Pierre Ferreira 0040a6dd53 merge avec master 🚧
7 months ago
Pierre Ferreira 1152ea7942 Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into mergefix
7 months ago
Thomas Chazot e1eb59aac7 plus de problème pour les undefined
7 months ago
Pierre Ferreira 3fb5a514b2 merge manuel de demo et master
7 months ago
Pierre Ferreira 7451857b0b ajout du lien d'invitation et selecteur de nombre de noeud dans lobby
7 months ago
Pierre FERREIRA fc52ae1cf3 Merge pull request 'todoS5' (#85) from todoS5 into master
7 months ago
Pierre Ferreira 058b23157f Merge branch 'master' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into todoS5
7 months ago
Pierre Ferreira 5bdeb22490 Merge branch 'todoS5' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into todoS5
7 months ago
Pierre Ferreira fca70dd981 ajout de la selection de noeud du graphe par entré utilisateur 🍱
7 months ago
Thomas CHAZOT 8f7188a2a5 Mise à jour de 'cryptide_project/social_graph.sh'
7 months ago
Thomas CHAZOT b130a42633 Mise à jour de 'cryptide_project/social_graph.sh'
7 months ago
Thomas Chazot 213d8097f0 joueur choisi affiché
7 months ago
Pierre Ferreira c01aacde54 Merge branch 'todoS5' of https://codefirst.iut.uca.fr/git/Crypteam/Cryptid into todoS5
7 months ago
Pierre Ferreira 0e88befa3e gestion des la liste de player
7 months ago
Thomas Chazot 9e928e624a Latex pour enigme facile et difficile + zip
7 months ago
Pierre Ferreira 148ae1f52f minor changes 🍱
7 months ago
Pierre Ferreira d761d0706a ajout de la coloration des lignes suite au clique de l'utilisateur 🎨
8 months ago
Pierre Ferreira 6b80d87d2e ajout de la page de deduction sans logique, avec des tableau accordion sur différentes tabs, le nombre de tabes est dynamic en fonction des joueurs
8 months ago
Pierre FERREIRA 620b2b97f8 Mise à jour de '.drone.yml'
continuous-integration/drone/push Build is passing Details
8 months ago
Pierre Ferreira 4996a78cea nettoyage des fichiers de config jest, je laisse cependant les dependancesde jest et de babel lié 📦
continuous-integration/drone/push Build is passing Details
8 months ago
Pierre Ferreira bdbc6dfe4d SONAR FONCTIONNE ! jest a été retiré car a besoin de test unitaire, son implémentation est toujours présente :greenheart:
continuous-integration/drone/push Build is passing Details
8 months ago
Pierre Ferreira bcf5cbaf7b sonar sans jest 🧪
continuous-integration/drone/push Build is passing Details
8 months ago
Pierre Ferreira 3261efd5b7 sonar sans jest 🧪
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira 8b06605351 sonar sans jest 🧪
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira dd4d5f5e62 sonar sans jest 🧪
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira 7c46757825 sonar sans jest 🧪
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira 7595edf989 maj drone.yml
continuous-integration/drone/push Build was killed Details
8 months ago
Pierre Ferreira a5fc5a2dd8 fix jest 🧪
continuous-integration/drone/push Build was killed Details
8 months ago
Pierre Ferreira 02b8ecd8b2 fix jest 🧪
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira 3f3eb0995b fix jest 🧪
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira 6677ebfc6b fix jest 🧪
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira 78e27ad2ac fix jest 🧪
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira e0b4297484 fix jest 🧪
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira 807cb772e6 fix jest 🧪
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira 1f745935b1 fix jest 🧪
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira 2a955dbbc1 fix jest 🧪
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira 16de4b4be7 ajout de jest.config.ts et de dependance 📦
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira e954dc8ac8 changement du script de test rect
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira 6e89b12b6d fix CI 💚
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira 18756ea5e0 le build marche, fix de sonar avec jest apres le conflit ;test_tube:
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira 7f07a54301 Revert "ajout de jest 🧪"
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira 3ff51d75bb revert previous commit
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira 292352e0f4 ajout de jest 🧪
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira ace0ceef04 fix ci build 💚
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira dd4eb3aa06 fix ci build 💚
continuous-integration/drone/push Build is failing Details
8 months ago
Pierre Ferreira d1e47d994e ajout de la nouvelle ci 👷
continuous-integration/drone/push Build is failing Details
8 months ago

@ -0,0 +1,92 @@
kind: pipeline
type: docker
name: default
trigger:
event:
- push
steps:
- name: build
image: node:20
commands:
- cd cryptide_project
- npm install --legacy-peer-deps
- CI=false npm run build
- name: code-analysis
image: node:20
environment:
SONAR_TOKEN:
from_secret: SECRET_SONAR_LOGIN_CRYPTIDE
commands:
- export SONAR_SCANNER_VERSION=4.7.0.2747
- export SONAR_SCANNER_HOME=$HOME/.sonar/sonar-scanner-$SONAR_SCANNER_VERSION-linux
- curl --create-dirs -sSLo $HOME/.sonar/sonar-scanner.zip https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-$SONAR_SCANNER_VERSION-linux.zip
- unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/
- export PATH=$SONAR_SCANNER_HOME/bin:$PATH
- export SONAR_SCANNER_OPTS="-server"
- sonar-scanner -D sonar.projectKey=Cryptid -D sonar.sources=. -D sonar.host.url=https://codefirst.iut.uca.fr/sonar
secrets: [SECRET_SONAR_LOGIN_CRYPTIDE]
settings:
sonar_host: https://codefirst.iut.uca.fr/sonar/
sonar_token:
from_secret: SECRET_SONAR_LOGIN_CRYPTIDE
#
- name: deploy-container-mysql
image: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientdrone:latest
build:
context: .
environment:
IMAGENAME: mysql:latest
CONTAINERNAME: mysql
COMMAND: create
#OVERWRITE: true
PRIVATE: false
CODEFIRST_CLIENTDRONE_ENV_MYSQL_ROOT_PASSWORD:
from_secret: MYSQL_ROOT_PASSWORD
CODEFIRST_CLIENTDRONE_ENV_MYSQL_DATABASE:
from_secret: MYSQL_DATABASE
CODEFIRST_CLIENTDRONE_ENV_MYSQL_USER:
from_secret: MYSQL_USER
CODEFIRST_CLIENTDRONE_ENV_MYSQL_PASSWORD:
from_secret: MYSQL_PASSWORD
ADMINS: thomaschazot2,pierreferreira,baptistemarcel
when:
branch:
- CI/CD
- name: container-web
image: plugins/docker
settings:
dockerfile: ./cryptide_project/Dockerfile
context: ./cryptide_project
registry: hub.codefirst.iut.uca.fr
repo: hub.codefirst.iut.uca.fr/thomas.chazot2/cryptide/web
username:
from_secret: SECRET_REGISTRY_USERNAME
password:
from_secret: SECRET_REGISTRY_PASSWORD
environment:
BASEPATH: "/containers/Crypteam-website"
#depends_on: [ build ]
when:
branch:
- CI/CD
#container deployment
- name: deploy-server-containers
image: hub.codefirst.iut.uca.fr/thomas.bellembois/codefirst-dockerproxy-clientdrone:latest
environment:
IMAGENAME: hub.codefirst.iut.uca.fr/thomas.chazot2/cryptide/web:latest
CONTAINERNAME: website
COMMAND: create
OVERWRITE: true
#PRIVATE: true
ADMINS: thomaschazot2,pierreferreira,baptistemarcel
depends_on: [ container-web ]
when:
branch:
- master

4
.gitignore vendored

@ -48,5 +48,7 @@ yarn.lock
package-lock.json
# db
socialgraph.db
# build
cryptide_project/build/*

@ -0,0 +1,33 @@
# Utilisez l'image Node.js LTS comme base
FROM node:14-alpine
# Définissez le répertoire de travail dans le conteneur
WORKDIR /app
# Copiez le package.json et le package-lock.json dans le conteneur
COPY package*.json ./
# Installez les dépendances du projet
RUN npm install
# Copiez le reste des fichiers de l'application dans le conteneur
COPY . .
# Construisez l'application React
RUN npm run build
# Utilisez l'image légère Nginx pour servir l'application construite
FROM nginx:alpine
# Copiez les fichiers construits de l'étape précédente dans le répertoire de travail de Nginx
COPY --from=0 /app/build /usr/share/nginx/html
# Copiez la configuration Nginx personnalisée
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Exposez le port 80 pour que l'application puisse être accessible
EXPOSE 80
# Définissez ENTRYPOINT pour démarrer Nginx lorsque le conteneur est lancé
ENTRYPOINT ["nginx", "-g", "daemon off;"]
#

@ -0,0 +1,33 @@
#!/bin/bash
# Récupérer le répertoire du script (où que le script soit appelé)
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Aller au répertoire du projet
#cd "$SCRIPT_DIR/../"
#* Lancement des serveurs
node $SCRIPT_DIR/../server/server.js &
# Attendre un court instant pour laisser le serveur démarrer
sleep 2
node $SCRIPT_DIR/../src/server/server.js &
# Attendre un court instant pour laisser le serveur démarrer
sleep 2
cd $SCRIPT_DIR/..
#* Génération de version de production
npm run build
#* Installation d'un serveur http simple
npm install -g serve
#* Execution du serveur sur le répertoire de build
serve -s build

@ -0,0 +1,17 @@
#!/bin/bash
# Vérifier si l'adresse IP est fournie en tant que paramètre
if [ -z "$1" ]; then
echo "Usage: $0 <adresse_ip>"
exit 1
fi
# Stocker l'adresse IP fournie en tant que variable
adresse_ip="$1"
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Utiliser l'adresse IP dans la commande find avec Perl
find $SCRIPT_DIR/../ -type f -exec perl -pi -e 's|http://[0-9.]+:([0-9]+)|http://localhost:$1|g' {} +
find $SCRIPT_DIR/../ -type f -exec perl -pi -e "s|http://localhost:([0-9]+)|http://$adresse_ip:\$1|g" {} +

@ -1,5 +1,8 @@
#!/bin/sh
npm install --force
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
if lsof -Pi :3000 -sTCP:LISTEN -t >/dev/null; then
# Tuer le processus associé au port
@ -23,8 +26,8 @@ fi
npm start &
node server/server.js &
node $SCRIPT_DIR/../server/socket_io/server.js &
node src/server/server.js
node $SCRIPT_DIR/../server/api/server.js

@ -0,0 +1,15 @@
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
error_page 404 /index.html;
location ~ /\. {
deny all;
}
}

File diff suppressed because it is too large Load Diff

@ -13,14 +13,14 @@
"bcrypt": "^5.1.1",
"body-parser": "^1.20.2",
"bootstrap": "^5.3.2",
"cookie-parser": "^1.4.6",
"cookie-session": "^2.0.0",
"cors": "^2.8.5",
"express": "^4.18.2",
"express-session": "^1.17.3",
"file-saver": "^2.0.5",
"jspdf": "^2.5.1",
"jszip": "^3.10.1",
"lodash": "^4.17.21",
"mysql": "^2.18.1",
"nuka-carousel": "^7.0.0",
"react": "^18.2.0",
"react-bootstrap": "^2.9.1",
"react-country-flag": "^3.1.0",
@ -31,9 +31,9 @@
"react-router-hash-link": "^2.4.3",
"react-scripts": "5.0.1",
"react-switch": "^7.0.0",
"sqlite3": "^5.1.6",
"socket.io": "^4.7.2",
"socket.io-client": "^4.7.2",
"sqlite3": "^5.1.6",
"typescript": "^5.2.2",
"vis-network": "^9.1.9",
"web-vitals": "^2.1.4"
@ -41,7 +41,7 @@
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"test": "jest --passWithNoTests",
"eject": "react-scripts eject"
},
"eslintConfig": {
@ -65,6 +65,15 @@
"devDependencies": {
"@types/file-saver": "^2.0.7",
"@types/react-router-hash-link": "^2.4.9",
"@types/uuid": "^9.0.7"
"@types/uuid": "^9.0.7",
"babel-jest": "^29.7.0",
"depcheck": "^1.4.7"
},
"babel": {
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
]
}
}

@ -0,0 +1,6 @@
{
"headers": [
{ "source": "**/*.js", "headers": [{ "key": "Content-Type", "value": "application/javascript; charset=utf-8" }] }
]
}

@ -0,0 +1,31 @@
const express = require('express');
const path = require('path');
const app = express();
const port = process.env.PORT || 80;
// Servir les fichiers statiques depuis le dossier 'build'
app.use(express.static(path.join(__dirname, 'build')));
// Définir le type MIME pour les fichiers JavaScript
app.use('/static/js', (req, res, next) => {
res.type('application/javascript; charset=utf-8');
next();
}, express.static(path.join(__dirname, 'build/static/js')));
// Définir le type MIME pour les fichiers CSS
app.use('/static/css', (req, res, next) => {
res.type('text/css; charset=utf-8');
next();
}, express.static(path.join(__dirname, 'build/static/css')));
// Route par défaut pour servir l'application React
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
// Démarrer le serveur
app.listen(port, () => {
console.log(`Serveur en cours d'exécution sur le port ${port}`);
console.log(path.join(__dirname, 'build'))
});

@ -0,0 +1,198 @@
const bcrypt = require('bcrypt');
const path = require('path');
const DatabaseService = require(path.resolve(__dirname, '../services/DatabaseService.js'));
const UserService = require(path.resolve(__dirname, '../services/UserService.js'));
class AuthController {
static async signUp(req, res) {
const databaseService = new DatabaseService();
const pseudo = req.body.pseudo;
const date = new Date();
const hour = date.getHours();
const minutes = date.getMinutes();
try {
await databaseService.connect();
// Vérifier que le pseudo n'existe pas déjà
const verif = await databaseService.getUserByPseudo(pseudo);
if (verif) {
res.status(400).json({ error: 'Le pseudo est déjà utilisé.' });
return;
}
// Créer un nouvel utilisateur
const currentUser = await UserService.createUser(req.body);
const insertedUser = await databaseService.insertUser(currentUser);
const user = await databaseService.getUserByPseudo(pseudo);
console.log("[" + hour + ":" + minutes + "] " + user.pseudo + " have been registered.");
res.status(201).json({ message: 'Inscription réussie', user: insertedUser});
}
catch (error) {
// Gérer les erreurs
console.error(error);
res.status(500).json({ error: 'Erreur lors de l\'inscription.' });
}
finally {
await databaseService.disconnect();
}
}
static async signIn(req, res) {
const databaseService = new DatabaseService();
const date = new Date();
const hour = date.getHours();
const minutes = date.getMinutes();
try{
await databaseService.connect();
// Vérifier que le pseudo existe
const pseudo = req.body.pseudo;
const user = await databaseService.getUserByPseudo(pseudo);
if (!user) {
res.status(400).json({ error: 'Le pseudo n\'existe pas.' });
return;
}
// Vérifier que le mot de passe est correct
const password = req.body.password;
const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword) {
res.status(400).json({ error: 'Le mot de passe est incorrect.' });
return;
}
// Stocker l'utilisateur dans la session)
req.session.user = user;
req.session.user.nbNodes = 25
req.session.user.nbIndices = 3
// Envoyer une réponse réussie
console.log("[" + hour + ":" + minutes + "] " + user.pseudo + " have been connected.");
res.status(200).json({ message: 'Connexion réussie', user: user });
}
catch(error){
// Gérer les erreurs
console.error(error);
res.status(500).json({ error: 'Erreur lors de la connexion.' });
}
finally{
await databaseService.disconnect();
}
}
static async UpdateNbNodesIndices(req, res){
try{
if (req.session.user){
req.session.user.nbNodes = req.body.nbNodes;
req.session.user.nbIndices = req.body.nbIndices;
res.status(200).json({ message: 'Nombre de noeuds mis à jour.' });
}
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la mise à jour du nombre de noeuds.' });
}
}
static async logout(req, res) {
const pseudo = req.session.user.pseudo;
const date = new Date();
const hour = date.getHours();
const minutes = date.getMinutes();
// Détruire la session pour déconnecter l'utilisateur
req.session.destroy((err) => {
if (err) {
console.error(err);
res.status(500).json({ error: 'Erreur lors de la déconnexion.' });
} else {
console.log("[" + hour + ":" + minutes + "] " + pseudo + " have been disconnected.");
res.status(200).json({ message: 'Déconnexion réussie' });
}
});
}
static async delAccount(req, res){
const db = new DatabaseService();
try{
await db.connect();
const user = await db.getUserByPseudo(req.body.pseudo);
if(!user){
res.status(400).json({ error: 'Le pseudo n\'existe pas.' });
return;
}
await db.deleteUser(user.idUser);
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la supression du compte.' });
}
finally{
db.disconnect();
}
}
static async validatePassword(req, res){
const db = new DatabaseService();
try{
await db.connect();
const user = await db.getUserByPseudo(req.body.pseudo);
if(!user){
res.status(400).json({ error: 'Le pseudo n\'existe pas.' });
return;
}
const password = req.body.password;
const validPassword = await bcrypt.compare(password, user.password);
if(!validPassword){
res.status(400).json({ error: 'Le mot de passe est incorrect.' });
return;
}
res.status(200).json({ message: 'Mot de passe correct.' });
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la vérification du mot de passe.' });
}
finally{
db.disconnect();
}
}
static async updatePassword(req, res){
const db = new DatabaseService();
try{
await db.connect();
const user = await db.getUserByPseudo(req.body.pseudo);
if(!user){
res.status(400).json({ error: 'Le pseudo n\'existe pas.' });
return;
}
const hashedPassword = await bcrypt.hash(req.body.newPassword, 10);
await db.updatePassword(user.idUser, hashedPassword);
res.status(200).json({ message: 'Mot de passe mis à jour.' });
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la mise à jour du mot de passe.' });
}
finally{
db.disconnect();
}
}
}
module.exports = AuthController;

@ -0,0 +1,209 @@
const path = require('path');
const DatabaseService = require(path.resolve(__dirname, '../services/DatabaseService'));
const ENIGME_FACILE = "enigme_facile";
const ENIGME_MOYEN = "enigme_moyenne";
const ENIGME_DIFFICILE = "enigme_difficile";
class SessionController {
// ---------------------------------------------------
// ----------------- GET DAILY STATS -----------------
// ---------------------------------------------------
static async getDailyMastermind(req, res){
const db = new DatabaseService();
try{
await db.connect();
const dailyMastermindStats = await db.getDailyMastermindStats();
res.status(200).json({ tab : dailyMastermindStats });
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la récupération des stats dailyMastermind.' });
}
finally{
await db.disconnect();
}
}
static async getDailyEasyEnigma(req, res){
const db = new DatabaseService();
try{
await db.connect();
const dailyEasyEnigmaStats = await db.getDailyEnigmaStats(ENIGME_FACILE);
res.status(200).json({ tab : dailyEasyEnigmaStats });
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la récupération des stats dailyEasyEnigma.' });
}
finally{
await db.disconnect();
}
}
static async getDailyMediumEnigma(req, res){
const db = new DatabaseService();
try{
await db.connect();
const dailyMediumEnigmaStats = await db.getDailyEnigmaStats(ENIGME_MOYEN);
res.status(200).json({ tab : dailyMediumEnigmaStats });
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la récupération des stats dailyMediumEnigma.' });
}
finally{
await db.disconnect();
}
}
static async getDailyHardEnigma(req, res){
const db = new DatabaseService();
try{
await db.connect();
const dailyHardEnigmaStats = await db.getDailyEnigmaStats(ENIGME_DIFFICILE);
res.status(200).json({ tab : dailyHardEnigmaStats });
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la récupération des stats dailyHardEnigma.' });
}
finally{
await db.disconnect();
}
}
static async getDailyOnline(req, res){
const db = new DatabaseService();
try{
await db.connect();
const dailyOnlineStats = await db.getDailyOnlineStats();
res.status(200).json({ tab : dailyOnlineStats });
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la récupération des stats dailyOnline' });
}
finally{
await db.disconnect();
}
}
// ---------------------------------------------------
// ---------------- GET WEEKLY STATS -----------------
// ---------------------------------------------------
static async getWeeklyMastermind(req, res){
const db = new DatabaseService();
try{
await db.connect();
const weeklyMastermindStats = await db.getWeeklyMastermindStats();
res.status(200).json({ tab : weeklyMastermindStats });
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la récupération des stats weeklyMastermind.' });
}
finally{
await db.disconnect();
}
}
static async getWeeklyEasyEnigma(req, res){
const db = new DatabaseService();
try{
await db.connect();
const weeklyEasyEnigmaStats = await db.getWeeklyEnigmaStats(ENIGME_FACILE);
res.status(200).json({ tab : weeklyEasyEnigmaStats });
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la récupération des stats weeklyEasyEnigma.' });
}
finally{
await db.disconnect();
}
}
static async getWeeklyMediumEnigma(req, res){
const db = new DatabaseService();
try{
await db.connect();
const weeklyMediumEnigmaStats = await db.getWeeklyEnigmaStats(ENIGME_MOYEN);
res.status(200).json({ tab : weeklyMediumEnigmaStats });
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la récupération des stats weeklyMediumEnigma.' });
}
finally{
await db.disconnect();
}
}
static async getWeeklyHardEnigma(req, res){
const db = new DatabaseService();
try{
await db.connect();
const weeklyHardEnigmaStats = await db.getWeeklyEnigmaStats(ENIGME_DIFFICILE);
res.status(200).json({ tab : weeklyHardEnigmaStats });
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la récupération des stats weeklyHardEnigma.' });
}
finally{
await db.disconnect();
}
}
static async getWeeklyOnline(req, res){
const db = new DatabaseService();
try{
await db.connect();
const weeklyOnlineStats = await db.getWeeklyOnlineStats();
res.status(200).json({ tab : weeklyOnlineStats });
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la récupération des stats weeklyOnline' });
}
finally{
await db.disconnect();
}
}
}
module.exports = SessionController;

@ -0,0 +1,235 @@
const path = require('path');
const DatabaseService = require(path.resolve(__dirname, '../services/DatabaseService'));
const ENIGME_FACILE = "enigme_facile";
const ENIGME_MOYEN = "enigme_moyenne";
const ENIGME_DIFFICILE = "enigme_difficile";
class SessionController {
static async getUserInformation(req, res) {
const db = new DatabaseService();
const date = new Date();
const hour = date.getHours();
const minutes = date.getMinutes();
try{
await db.connect();
if (!req.session.user) {
res.status(200).json({ error: "true", message: 'User not found' });
return;
}
// Récupérer les stats mastermind de l'utilisateur
let nbGamesMM = await db.getNbGamesMastermindByUserId(req.session.user.idUser);
nbGamesMM = nbGamesMM.nbGames || 0;
let bestScoreMM = await db.getBestScoreMastermindByUserId(req.session.user.idUser);
bestScoreMM = bestScoreMM.bestScore || 0;
let avgNbTryMM = await db.getAvgNbTryMastermindByUserId(req.session.user.idUser);
avgNbTryMM = avgNbTryMM.avgNbTry || 0;
req.session.user.mastermindStats = {nbGames: nbGamesMM,
bestScore: bestScoreMM,
avgNbTry: avgNbTryMM};
// Récupérer les stats enigme facile
let nbGamesEF = await db.getNbGamesEnigmeByUserId(req.session.user.idUser, ENIGME_FACILE);
nbGamesEF = nbGamesEF.nbGames || 0;
let nbWinsEF = await db.getNbWinsEnigmeByUserId(req.session.user.idUser, ENIGME_FACILE);
nbWinsEF = nbWinsEF.nbWins || 0;
let ratioEF = (nbWinsEF.nbWins / nbGamesEF.nbGames) * 100 || 0;
let bestTimeEF = await db.getBestTimeEnigmeByUserId(req.session.user.idUser, ENIGME_FACILE);
bestTimeEF = bestTimeEF.bestTime || 0;
let avgTimeEF = await db.getAvgTimeEnigmeByUserId(req.session.user.idUser, ENIGME_FACILE);
avgTimeEF = avgTimeEF.avgTime || 0;
req.session.user.easyEnigmaStats = {nbGames: nbGamesEF,
nbWins: nbWinsEF,
ratio: ratioEF,
bestTime: bestTimeEF,
avgTime: avgTimeEF};
// Récupérer les stats enigme moyenne
let nbGamesEM = await db.getNbGamesEnigmeByUserId(req.session.user.idUser, ENIGME_MOYEN);
nbGamesEM = nbGamesEM.nbGames || 0;
let bestScoreEM = await db.getBestScoreEnigmeByUserId(req.session.user.idUser, ENIGME_MOYEN);
bestScoreEM = bestScoreEM.bestScore || 0;
let avgNbTryEM = await db.getAvgScoreEnigmeByUserId(req.session.user.idUser, ENIGME_MOYEN);
avgNbTryEM = avgNbTryEM.avgScore || 0;
req.session.user.mediumEnigmaStats = {nbGames: nbGamesEM,
bestScore: bestScoreEM,
avgNbTry: avgNbTryEM};
// Récupérer les stats enigme difficile
let nbGamesED = await db.getNbGamesEnigmeByUserId(req.session.user.idUser, ENIGME_DIFFICILE);
nbGamesED = nbGamesED.nbGames || 0;
let nbWinsED = await db.getNbWinsEnigmeByUserId(req.session.user.idUser, ENIGME_DIFFICILE);
nbWinsED = nbWinsED.nbWins || 0;
let ratioED = (nbWinsED.nbWins / nbGamesED.nbGames) * 100 || 0;
let bestTimeED = await db.getBestTimeEnigmeByUserId(req.session.user.idUser, ENIGME_DIFFICILE);
bestTimeED = bestTimeED.bestTime || 0;
let avgTimeED = await db.getAvgTimeEnigmeByUserId(req.session.user.idUser, ENIGME_DIFFICILE);
avgTimeED = avgTimeED.avgTime || 0;
req.session.user.hardEnigmaStats = {nbGames: nbGamesED,
nbWins: nbWinsED,
ratio: ratioED,
bestTime: bestTimeED,
avgTime: avgTimeED};
// Récupérer les stats en ligne de l'utilisateur
let nbGamesOL = await db.getNbGamesOnlineByUserId(req.session.user.idUser);
nbGamesOL = nbGamesOL.nbGames || 0;
let nbWinsOL = await db.getNbWinsOnlineByUserId(req.session.user.idUser);
nbWinsOL = nbWinsOL.nbWins || 0;
let ratioOL = (nbWinsOL.nbWins / nbGamesOL.nbGames) * 100 || 0;
req.session.user.onlineStats = {nbGames: nbGamesOL,
nbWins: nbWinsOL,
ratio: ratioOL};
res.status(200).json({ user: req.session.user });
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la récupération de l\'utilisateur.' });
}
finally{
await db.disconnect();
}
}
static async UpdatePseudo(req, res){
const db = new DatabaseService();
try{
await db.connect();
const user = await db.getUserByPseudo(req.body.pseudo);
console.log("utilisateur" + user.idUser + " pseudo" + user.pseudo)
if (!user) {
res.status(200).json({ error: "true", message: 'User not found' });
return;
}
await db.updatePseudo(user.idUser, req.body.newPseudo); //* update
const updatedUser = await db.getUserByPseudo(req.body.newPseudo);
console.log("updaetdutilisateur" + updatedUser.idUser + " pseudo" + updatedUser.pseudo)
req.session.user.pseudo = updatedUser.pseudo;
console.log("req.session.user.pseudo" + req.session.user.pseudo)
res.status(200).json({ user: req.session.user }); //verif rep
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la modification du pseudo de l\'utilisateur.' });
}
finally{
await db.disconnect();
}
}
static async addMastermindStats(req, res){
const db = new DatabaseService();
try{
await db.connect();
const user = await db.getUserByPseudo(req.body.pseudo);
if (!user) {
res.status(200).json({ error: "true", message: 'User not found' });
return;
}
await db.addMastermindStats(user.idUser, req.body.score, req.body.time);
res.status(200).json({ user: req.session.user }); //verif rep
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la modification des stats mastermind de l\'utilisateur.' });
}
finally{
await db.disconnect();
}
}
static async addEasyEnigmaStats(req, res){
const db = new DatabaseService();
try{
await db.connect();
const user = await db.getUserByPseudo(req.body.pseudo);
if (!user) {
res.status(200).json({ error: "true", message: 'User not found' });
return;
}
await db.addEasyEnigmaStats(user.idUser, ENIGME_FACILE, req.body.win, req.body.time);
res.status(200).json({ user: req.session.user }); //verif rep
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la modification des stats de l\'énigme facile de l\'utilisateur.' });
}
finally{
await db.disconnect();
}
}
// static async addMediumEnigmaStats(req, res)
static async addHardEnigmaStats(req, res){
const db = new DatabaseService();
try{
await db.connect();
const user = await db.getUserByPseudo(req.body.pseudo);
if (!user) {
res.status(200).json({ error: "true", message: 'User not found' });
return;
}
await db.addHardEnigmaStats(user.idUser, ENIGME_DIFFICILE, req.body.win, req.body.time);
res.status(200).json({ user: req.session.user }); //verif rep
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la modification des stats de l\'énigme difficile de l\'utilisateur.' });
}
finally{
await db.disconnect();
}
}
static async addOnlineStats(req, res){
const db = new DatabaseService();
try{
await db.connect();
const user = await db.getUserByPseudo(req.body.pseudo);
if (!user) {
res.status(200).json({ error: "true", message: 'User not found' });
return;
}
await db.addOnlineStats(user.idUser, req.body.win, req.body.time);
res.status(200).json({ user: req.session.user }); //verif rep
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la modification des stats en ligne de l\'utilisateur.' });
}
finally{
await db.disconnect();
}
}
}
module.exports = SessionController;

@ -0,0 +1,41 @@
const express = require('express');
const router = express.Router();
const AuthController = require('../controllers/AuthController');
const SessionController = require('../controllers/SessionController');
const ScoreboardController = require('../controllers/ScoreboardController');
// Routes pour l'authentification
router.post('/auth/signup', AuthController.signUp);
router.post('/auth/signin', AuthController.signIn);
router.delete('/auth/logout', AuthController.logout)
router.delete('/auth/delAccount', AuthController.delAccount)
router.post('/auth/validatePassword', AuthController.validatePassword);
router.put('/auth/updatePassword', AuthController.updatePassword);
router.put('/session/updateNbNodes', AuthController.UpdateNbNodesIndices);
// Routes pour les sessions
router.get('/session', SessionController.getUserInformation);
router.post('/session/addMastermindStats', SessionController.addMastermindStats);
router.post('/session/addEasyEnigmaStats', SessionController.addEasyEnigmaStats);
// router.post('/session/addMediumEnigmaStats', SessionController.addMediumEnigmaStats);
router.post('/session/addHardEnigmaStats', SessionController.addHardEnigmaStats);
router.post('/session/addOnlineStats', SessionController.addOnlineStats);
router.put('/session/updatePseudo', SessionController.UpdatePseudo);
// Routes pour le daily scoreboard
router.get('/scoreboard/getDailyMastermind', ScoreboardController.getDailyMastermind);
router.get('/scoreboard/getDailyEasyEnigma', ScoreboardController.getDailyEasyEnigma);
router.get('/scoreboard/getDailyMediumEnigma', ScoreboardController.getDailyMediumEnigma);
router.get('/scoreboard/getDailyHardEnigma', ScoreboardController.getDailyHardEnigma);
router.get('/scoreboard/getDailyOnline', ScoreboardController.getDailyOnline);
// Routes pour le weekly scoreboard
router.get('/scoreboard/getWeeklyMastermind', ScoreboardController.getWeeklyMastermind);
router.get('/scoreboard/getWeeklyEasyEnigma', ScoreboardController.getWeeklyEasyEnigma);
router.get('/scoreboard/getWeeklyMediumEnigma', ScoreboardController.getWeeklyMediumEnigma);
router.get('/scoreboard/getWeeklyHardEnigma', ScoreboardController.getWeeklyHardEnigma);
router.get('/scoreboard/getWeeklyOnline', ScoreboardController.getWeeklyOnline);
module.exports = router;

@ -3,8 +3,9 @@ const session = require('express-session');
const cors = require('cors');
const bodyParser = require('body-parser');
const crypto = require('crypto');
const authRoutes = require('./routes/authRoutes');
const DatabaseService = require('./services/DatabaseService');
const path = require('path');
const authRoutes = require(path.resolve(__dirname, './routes/authRoutes'));
const DatabaseService = require(path.resolve(__dirname, './services/DatabaseService'));
const app = express();
@ -13,7 +14,7 @@ const port = 3003;
// Middleware
app.use(cors(
{
origin: ["http://localhost:3000", "http://172.20.10.4:3000"],
origin: ["http://172.20.10.4:3000", "http://localhost:3000"],
credentials: true
}
)); // Autoriser les requêtes cross-origin

@ -0,0 +1,473 @@
const sqlite3 = require('sqlite3');
const path = require('path');
const { rejects } = require('assert');
class DatabaseService {
constructor(){
this.db_name = "socialgraph";
}
// ----------------------------------------------------
// ------------------- UTILITAIRE ---------------------
// ----------------------------------------------------
async connect(client){
const dbPath = path.resolve(__dirname, `../../../db/${this.db_name}.db`)
return new Promise((resolve, reject) => {
this.client = new sqlite3.Database(dbPath,
sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE,
(err) => {
if(err){
reject(err);
}
else{
resolve();
}
});
});
}
async disconnect(){
return new Promise((resolve, reject) => {
this.client.close((err) => {
if(err){
reject(err);
}
else{
resolve();
}
});
});
}
// ----------------------------------------------------
// ------------------- UTILISATEUR --------------------
// ----------------------------------------------------
// Récupère l'utilisateur par son id
async getUserByID(id){
return new Promise((resolve, reject) => {
this.client.get('SELECT * FROM users WHERE idUser = ?', id, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
// Récupère l'utilisateur par son pseudo
async getUserByPseudo(pseudo){
return new Promise((resolve, reject) => {
this.client.get('SELECT * FROM users WHERE pseudo = ?', pseudo, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
// insère un utilisateur dans la base de données
async insertUser(user) {
return new Promise((resolve, reject) => {
const { pseudo, password } = user;
this.client.run('INSERT INTO users (pseudo, password) VALUES (?, ?)', [pseudo, password], (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
async deleteUser(userId){
return new Promise((resolve, reject) => {
this.client.run('DELETE FROM users WHERE idUser=?', userId, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async updatePseudo(userId, newPseudo){
return new Promise((resolve, reject) => {
this.client.run('UPDATE users SET pseudo = ? WHERE idUser = ?', newPseudo, userId, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async updatePassword(userId, newPassword){
return new Promise((resolve, reject) => {
this.client.run('UPDATE users SET password = ? WHERE idUser = ?', newPassword, userId, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
// ---------------------------------------------------------------
// ------------------- STATS JOURNALIERE -------------------------
// ---------------------------------------------------------------
async getDailyMastermindStats() {
return new Promise((resolve, reject) => {
// Obtenez la date actuelle au format AAAA-MM-JJ
const currentDate = new Date().toISOString().slice(0, 10);
// Récupérer les 5 meilleurs scores de la journée
this.client.all(
'SELECT pseudo, score FROM users INNER JOIN games ON users.idUser = games.idUser WHERE gameType = ? AND SUBSTR(playedDate, 1, 10) = ? ORDER BY score ASC LIMIT 10',
"mastermind",
currentDate,
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
}
);
});
}
async getDailyEnigmaStats(enigmaLevel) {
return new Promise((resolve, reject) => {
const currentDate = new Date().toISOString().slice(0, 10);
this.client.all(
'SELECT pseudo, time FROM users INNER JOIN games ON users.idUser = games.idUser WHERE gameType = ? AND SUBSTR(playedDate, 1, 10) = ? ORDER BY time ASC LIMIT 10',
enigmaLevel,
currentDate,
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
}
);
});
}
async getDailyOnlineStats() {
return new Promise((resolve, reject) => {
const currentDate = new Date().toISOString().slice(0, 10);
this.client.all(
'SELECT pseudo, COUNT(*) AS wins FROM users INNER JOIN games ON users.idUser = games.idUser WHERE gameType = ? AND SUBSTR(playedDate, 1, 10) = ? AND win = ? GROUP BY users.idUser ORDER BY wins ASC LIMIT 10',
"multijoueur", currentDate, 1,
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
}
);
});
}
// ---------------------------------------------------------------
// ------------------- STATS HEBDOMADAIRE ------------------------
// ---------------------------------------------------------------
async getWeeklyMastermindStats() {
return new Promise((resolve, reject) => {
const currentDate = new Date().toISOString().slice(0, 10);
const currentDay = new Date().getDay();
const firstDayOfWeek = new Date(new Date().setDate(new Date().getDate() - currentDay)).toISOString().slice(0, 10);
this.client.all(
'SELECT pseudo, score FROM users INNER JOIN games ON users.idUser = games.idUser WHERE gameType = ? AND SUBSTR(playedDate, 1, 10) BETWEEN ? AND ? ORDER BY score ASC LIMIT 10',
"mastermind",
firstDayOfWeek,
currentDate,
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
}
);
});
}
async getWeeklyEnigmaStats(enigmaLevel) {
return new Promise((resolve, reject) => {
const currentDate = new Date().toISOString().slice(0, 10);
const currentDay = new Date().getDay();
const firstDayOfWeek = new Date(new Date().setDate(new Date().getDate() - currentDay)).toISOString().slice(0, 10);
this.client.all(
'SELECT pseudo, time FROM users INNER JOIN games ON users.idUser = games.idUser WHERE gameType = ? AND SUBSTR(playedDate, 1, 10) BETWEEN ? AND ? ORDER BY time ASC LIMIT 10',
enigmaLevel,
firstDayOfWeek,
currentDate,
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
}
);
});
}
async getWeeklyOnlineStats() {
return new Promise((resolve, reject) => {
const currentDate = new Date().toISOString().slice(0, 10);
const currentDay = new Date().getDay();
const firstDayOfWeek = new Date(new Date().setDate(new Date().getDate() - currentDay)).toISOString().slice(0, 10);
this.client.all(
'SELECT pseudo, COUNT(*) as wins FROM users INNER JOIN games ON users.idUser = games.idUser WHERE gameType = ? AND SUBSTR(playedDate, 1, 10) BETWEEN ? AND ? AND win = ? ORDER BY wins ASC LIMIT 10',
"multijoueur",
firstDayOfWeek,
currentDate,
1,
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
}
);
});
}
// -------------------------------------------------------------
// ------------------- STATS MASTERMIND ------------------------
// -------------------------------------------------------------
async getNbGamesMastermindByUserId(userId){
return new Promise((resolve, reject) => {
this.client.get('SELECT COUNT(*) AS nbGames FROM games WHERE idUser = ? AND gameType = ?', userId, "mastermind", (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async getBestScoreMastermindByUserId(userId){
return new Promise((resolve, reject) => {
this.client.get('SELECT MIN(score) AS bestScore FROM games WHERE idUser = ? AND gameType = ?', userId, "mastermind", (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async getAvgNbTryMastermindByUserId(userId){
return new Promise((resolve, reject) => {
this.client.get('SELECT AVG(score) AS avgNbTry FROM games WHERE idUser = ? AND gameType = ?', userId, "mastermind", (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async addMastermindStats(userId, score, time){
return new Promise((resolve, reject) => {
this.client.run('INSERT INTO games (idUser, gameType, win, score, time) VALUES (?, ?, ?, ?, ?)', userId, "mastermind", 1, score, time, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
// -------------------------------------------------------------
// ------------------- STATS EN LIGNE --------------------------
// -------------------------------------------------------------
async getNbGamesOnlineByUserId(userId){
return new Promise((resolve, reject) => {
this.client.get('SELECT COUNT(*) AS nbGames FROM games WHERE idUser = ? AND gameType = ?', userId, "multijoueur", (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async getNbWinsOnlineByUserId(userId){
return new Promise((resolve, reject) => {
this.client.get('SELECT COUNT(*) AS nbWins FROM games WHERE idUser = ? AND gameType = ? AND win = ?', userId, "multijoueur", 1, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async addOnlineStats(userId, win, time){
return new Promise((resolve, reject) => {
this.client.run('INSERT INTO games (idUser, gameType, win, score, time) VALUES (?, ?, ?, ?, ?)', userId, "multijoueur", win, 0, time, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
// -------------------------------------------------------------
// ------------------- STATS ENIGME ----------------------------
// -------------------------------------------------------------
async getNbGamesEnigmeByUserId(userId, enigmaLevel){
return new Promise((resolve, reject) => {
this.client.get('SELECT COUNT(*) AS nbGames FROM games WHERE idUser = ? AND gameType = ?', userId, enigmaLevel, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async getNbWinsEnigmeByUserId(userId, enigmaLevel){
return new Promise((resolve, reject) => {
this.client.get('SELECT COUNT(*) AS nbWins FROM games WHERE idUser = ? AND gameType = ? AND win = ?', userId, enigmaLevel, 1, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async getBestScoreEnigmeByUserId(userId, enigmaLevel){
return new Promise((resolve, reject) => {
this.client.get('SELECT MAX(score) AS bestScore FROM games WHERE idUser = ? AND gameType = ?', userId, enigmaLevel, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async getAvgScoreEnigmeByUserId(userId, enigmaLevel){
return new Promise((resolve, reject) => {
this.client.get('SELECT AVG(score) AS avgScore FROM games WHERE idUser = ? AND gameType = ?', userId, enigmaLevel, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async getBestTimeEnigmeByUserId(userId, enigmaLevel){
return new Promise((resolve, reject) => {
this.client.get('SELECT MIN(time) AS bestTime FROM games WHERE idUser = ? AND gameType = ?', userId, enigmaLevel, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async getAvgTimeEnigmeByUserId(userId, enigmaLevel){
return new Promise((resolve, reject) => {
this.client.get('SELECT AVG(time) AS avgTime FROM games WHERE idUser = ? AND gameType = ?', userId, enigmaLevel, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async addEasyEnigmaStats(userId, enigmaLevel, win, time){
return new Promise((resolve, reject) => {
this.client.run('INSERT INTO games (idUser, gameType, win, score, time) VALUES (?, ?, ?, ?, ?)', userId, enigmaLevel, win, 0, time, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
// async addMediumEnigmaStats(userId, enigmaLevel, score)
async addHardEnigmaStats(userId, enigmaLevel, win, time){
return new Promise((resolve, reject) => {
this.client.run('INSERT INTO games (idUser, gameType, win, score, time) VALUES (?, ?, ?, ?, ?)', userId, enigmaLevel, win, 0, time, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
}
module.exports = DatabaseService;

@ -1,132 +0,0 @@
const express = require('express');
const http = require('http');
const socketIO = require('socket.io');
const cors = require('cors');
const app = express();
const server = http.createServer(app);
const io = socketIO(server, {
cors: {
origin: ["http://172.20.10.4:3000", "http://localhost:3000"], // Remplacez par l'URL de votre application React
methods: ["GET", "POST"],
credentials: true
}
});
const map = new Map()
server.listen(3002, () => {
console.log('Serveur Socket.IO écoutant sur le port 3002');
});
io.on('connection', (socket) => {
console.log(socket.id);
socket.on('network created', (network, person, indices, room, start) =>{
io.to(room).emit("game created", network, person, indices, start)
});
socket.on("lobby joined", (room, player) =>{
console.log(player)
if (player.type=="User"){
socket.join(room)
}
if (map.get(room) == undefined){
map.set(room, [{type: player.type, id: socket.id, pseudo: player.pseudo, profilePicture: player.profilePicture}])
}
else{
const tab = map.get(room)
for(let i = 0; i<tab.length; i++){
if (tab[i].id === socket.id && player.type==="User"){
tab.splice(i, 1)
}
}
if (player.type!=="User"){
map.get(room).push({type: player.type, id: player.id, pseudo: player.pseudo, profilePicture: player.profilePicture})
}
else{
map.get(room).push({type: player.type, id: socket.id, pseudo: player.pseudo, profilePicture: player.profilePicture})
}
}
io.to(room).emit("new player", map.get(room))
})
socket.on("bot deleted", (bot, room) =>{
const tab = map.get(room)
for(let i = 0; i<tab.length; i++){
if (tab[i].id === bot.id){
tab.splice(i, 1)
}
}
io.to(room).emit("new player", map.get(room))
})
socket.on("lobby created", () =>{
io.to(socket.id).emit("lobby created", Math.floor(Math.random() * 10000))
})
socket.on("already asked", (nodeId, askingPlayer, askedPlayer) =>{
io.to(askingPlayer.id).emit("already asked", nodeId, askedPlayer)
})
socket.on("ask player", (nodeId, playerId, askingPlayer) =>{
io.to(playerId).emit("asked", nodeId, askingPlayer)
})
socket.on("asked all 1by1", (id, playerId) =>{
io.to(playerId).emit("asked all", id)
})
socket.on("asked wrong", (askingPlayer) =>{
io.to(askingPlayer.id).emit("asked wrong")
})
socket.on("disconnect", () =>{
for (const k of map.keys()){
const tab = map.get(k)
for (let i = 0; i<tab.length; i++){
if (tab[i].id === socket.id){
tab.splice(i, 1)
io.to(k).emit("player left", tab, i)
}
}
}
})
socket.on("node checked", (id, works, color, room, playerIndex) =>{
io.to(room).emit("node checked", id, works, color, playerIndex, socket.id)
})
socket.on("put correct background", (id) =>{
io.to(id).emit("put correct background")
})
socket.on("put grey background", (id, player) =>{
io.to(id).emit("put grey background", player)
})
socket.on("put imossible grey", (id) =>{
io.to(id).emit("put imossible grey")
})
socket.on("opacity activated", (id) => {
io.to(id).emit("opacity activated")
})
socket.on("opacity deactivated", (id) => {
io.to(id).emit("opacity deactivated")
})
socket.on("reset graph", (id) => {
io.to(id).emit("reset graph")
})
socket.on("end game", (winnerIndex, room) =>{
io.to(room).emit("end game", winnerIndex)
})
});

@ -0,0 +1,284 @@
const express = require('express');
const http = require('http');
const socketIO = require('socket.io');
const cors = require('cors');
const app = express();
const server = http.createServer(app);
const io = socketIO(server, {
cors: {
origin: "*", // Remplacez par l'URL de votre application React
methods: ["GET", "POST"],
credentials: true
}
});
let lastSocketJoined = ""
const map = new Map()
server.listen(3002, () => {
console.log('Serveur Socket.IO écoutant sur le port 3002');
});
io.on('connection', (socket) => {
console.log(socket.id);
socket.on('network created', (network, person, indices, room, start) =>{
try{
io.to(room).emit("game created", network, person, indices, start)
map.get(room).started = true
map.get(room).actualPlayer=start
const playerArray = Array.from(map.entries()).map(([key, value]) => ({ key, value }))
const playerJson = JSON.stringify(playerArray);
io.emit("request lobbies", playerJson)
}
catch{
console.log("error")
}
});
socket.on("give network", (networkPerson, person, indices, start, room, nodes, playerId) => {
io.to(playerId).emit("join during game", networkPerson, person, indices, start, map.get(room).tab, nodes)
})
socket.on("join back game", (player) => {
for (const k of map.keys()){
const tab = map.get(k)
for (let i = 0; i<tab.tab.length; i++){
if (tab.tab[i].pseudo === player.pseudo && tab.tab[i].type !== "User"){
if (tab.started){
io.to(socket.id).emit("join back game", k)
}
}
}
}
})
socket.on("lobby joined", (room, player) =>{
const game = map.get(room)
if (game !== undefined){
if (game.tab.length == 6 && !game.started){
io.to(socket.id).emit("room full")
return
}
if (game.started){
for(const u of game.tab){
if(u.type !== "User" && u.pseudo===player.pseudo){
u.type = "User"
u.id=socket.id
io.to(game.tab[game.actualPlayer].id).emit("give network", socket.id)
io.to(room).emit("player joined ingame", game.tab)
socket.join(room)
return
}
}
io.to(socket.id).emit("game already started")
return
}
}
if (game == undefined){
map.set(room, {tab: [{type: player.type, id: socket.id, pseudo: player.pseudo, profilePicture: player.profilePicture}], started: false, actualPlayer: 0, lastWorks: false})
socket.join(room)
}
else{
const tab = game.tab
for(let i = 0; i<tab.length; i++){
if (tab[i].id === socket.id && player.type==="User"){
tab.splice(i, 1)
}
}
if (player.type!=="User"){
tab.push({type: player.type, id: player.id, pseudo: player.pseudo, profilePicture: player.profilePicture})
}
else{
tab.push({type: player.type, id: socket.id, pseudo: player.pseudo, profilePicture: player.profilePicture})
socket.join(room)
}
}
io.to(room).emit("new player", map.get(room))
const playerArray = Array.from(map.entries()).map(([key, value]) => ({ key, value }))
const playerJson = JSON.stringify(playerArray);
io.emit("request lobbies", playerJson)
})
socket.on("request lobbies", () => {
const playerArray = Array.from(map.entries()).map(([key, value]) => ({ key, value }))
const playerJson = JSON.stringify(playerArray);
io.to(socket.id).emit("request lobbies", playerJson)
})
socket.on("bot deleted", (bot, room) =>{
const tab = map.get(room).tab
for(let i = 0; i<tab.length; i++){
if (tab[i].id === bot.id){
tab.splice(i, 1)
}
}
io.to(room).emit("new player", map.get(room))
const playerArray = Array.from(map.entries()).map(([key, value]) => ({ key, value }))
const playerJson = JSON.stringify(playerArray);
io.emit("request lobbies", playerJson)
})
socket.on("lobby created", () =>{
io.to(socket.id).emit("lobby created", Math.floor(Math.random() * 10000))
})
socket.on("already asked", (nodeId, askingPlayer, askedPlayer) =>{
io.to(askingPlayer.id).emit("already asked", nodeId, askedPlayer)
})
socket.on("ask player", (nodeId, playerId, askingPlayer) =>{
io.to(playerId).emit("asked", nodeId, askingPlayer)
})
socket.on("asked all 1by1", (id, playerId) =>{
io.to(playerId).emit("asked all", id)
})
socket.on("asked wrong", (askingPlayer) =>{
io.to(askingPlayer.id).emit("asked wrong")
})
socket.on("who plays", (room) => {
try{
if (map.get(room) !== undefined){
let player = map.get(room).actualPlayer
if (map.get(room).tab[player].type != "User"){
player = player + 1
if (player == map.get(room).tab.length){
player=0
}
}
// console.log(player)
io.to(room).emit("who plays", player, map.get(room).lastWorks)
}
}
catch{
}
})
socket.on("disconnect", () =>{
for (const k of map.keys()){
const tab = map.get(k)
for (let i = 0; i<tab.tab.length; i++){
if (tab.tab[i].id === socket.id){
if (!tab.started){
tab.tab.splice(i, 1)
if (i==0){
tab.tab.sort(comparePlayersByType).reverse()
}
io.to(k).emit("player left", tab, i)
}
else{
tab.tab[i].type="EasyBot"
io.to(k).emit("player left ingame", tab, i)
}
if (tab.tab.filter((p) => p.type=="User").length == 0){
map.delete(k)
}
}
}
}
const playerArray = Array.from(map.entries()).map(([key, value]) => ({ key, value }))
const playerJson = JSON.stringify(playerArray);
io.emit("request lobbies", playerJson)
})
socket.on("player quit", () => {
lastSocketJoined=""
for (const k of map.keys()){
const tab = map.get(k)
for (let i = 0; i<tab.tab.length; i++){
if (tab.tab[i].id === socket.id){
if (!tab.started){
tab.tab.splice(i, 1)
if (i==0){
tab.tab.sort(comparePlayersByType).reverse()
}
io.to(k).emit("player left", tab, i)
}
else{
tab.tab[i].type="EasyBot"
io.to(k).emit("player left ingame", tab, i)
}
if (tab.tab.filter((p) => p.type=="User").length == 0){
map.delete(k)
}
}
}
}
const playerArray = Array.from(map.entries()).map(([key, value]) => ({ key, value }))
const playerJson = JSON.stringify(playerArray);
io.emit("request lobbies", playerJson)
})
socket.on("node checked", (id, works, color, room, playerIndex) =>{
try{
map.get(room).actualPlayer=playerIndex
map.get(room).lastWorks=works
io.to(room).emit("node checked", id, works, color, playerIndex, socket.id)
}
catch{
console.log("error")
}
})
socket.on("put correct background", (id) =>{
io.to(id).emit("put correct background")
})
socket.on("put grey background", (id, player) =>{
io.to(id).emit("put grey background", player)
})
socket.on("put imossible grey", (id) =>{
io.to(id).emit("put imossible grey")
})
socket.on("can't put square", (askingPlayer, room) => {
io.to(room).emit("can't put square" , askingPlayer)
})
socket.on("opacity activated", (id) => {
io.to(id).emit("opacity activated")
})
socket.on("opacity deactivated", (id) => {
io.to(id).emit("opacity deactivated")
})
socket.on("reset graph", (id) => {
io.to(id).emit("reset graph")
})
socket.on("end game", (winnerIndex, room) =>{
io.to(room).emit("end game", winnerIndex)
map.delete(room)
})
});
function comparePlayersByType(a, b) {
if (a.type < b.type) {
return -1;
} else if (a.type > b.type) {
return 1;
} else {
return 0;
}
}

@ -1,7 +1,17 @@
// const ADRESSE_WEBSERVER = "http://172.20.10.4:3002"
const ADRESSE_WEBSERVER = "http://localhost:3002"
// const ADRESSE_DBSERVER = "http://172.20.10.4:3003"
const ADRESSE_DBSERVER = "http://localhost:3003"
const tmp = ADRESSE_DBSERVER
const tmp2 = ADRESSE_WEBSERVER
const ADRESSE_WEBSITE = ""
export {ADRESSE_DBSERVER, ADRESSE_WEBSERVER, ADRESSE_WEBSITE}
const basePath = process.env.REACT_APP_BASE_PATH || '';
const tmp3 = basePath
export {ADRESSE_DBSERVER, ADRESSE_WEBSERVER, ADRESSE_WEBSITE, basePath}

@ -9,20 +9,19 @@ import { AuthProvider } from './Contexts/AuthContext';
import Home from './Pages/Home';
import Login from './Pages/LoginForm';
import SignUp from './Pages/SignUpForm';
import Play from './Pages/Play';
import NewPlay from './Pages/NewPlay';
import Profile from './Pages/Profile';
import Lobby from './Pages/Lobby';
import InGame from './Pages/InGame';
import EndGame from './Pages/EndGame';
import InfoPage from './Pages/InfoPage';
import DeducGrid from './Pages/DeducGrid';
import Lobbies from './Pages/Lobbies';
/* Component */
import AppNavbar from './Components/NavBar';
/* service */
import SessionService from './services/SessionService';
/* nav */
import { BrowserRouter, Route, Routes, useLocation } from "react-router-dom";
@ -38,11 +37,26 @@ import 'bootstrap/dist/css/bootstrap.min.css';
/* Internationnalisation */
import messagesFr from './Translations/fr.json';
import messagesEn from './Translations/en.json';
import SoloGame from './Pages/SoloGame';
import messagesEs from './Translations/es.json';
import messagesPt from './Translations/pt.json';
import messagesRu from './Translations/ru.json';
/* Gestion d' erreur */
import ErrorBoundary from './Error/ErrorBoundary';
import ErrorPage from './Error/ErrorPage';
import DeducCheck from './Pages/DeducCheck';
import {basePath} from "./AdressSetup"
import Tutorial from './Pages/Tutorial';
const messages = {
fr: messagesFr,
en: messagesEn,
es: messagesEs,
pt: messagesPt,
ru: messagesRu,
};
function App() {
@ -56,43 +70,48 @@ function App() {
setLocale(newLocale);
};
console.log(basePath)
//const location = useLocation();
const hasNavbarVisible = ["/", "/login", "/signup", "/play", "/lobby", "/endgame"]//.includes(window.location.pathname);
const hasNavbarVisible = [basePath + "/", basePath + "/login", basePath + "/signup", basePath + "/lobby", basePath + "/endgame", basePath + "/deduc"]//.includes(window.location.pathname);
document.title = "Social Graph";
return (
// <div className="App">
// <header className="App-header">
// <Home />
// <img src={logo} className="App-logo" alt="logo" />
// </header>
// </div>
<AuthProvider>
<GameProvider>
{/*@ts-ignore*/}
<IntlProvider locale={locale} messages={messages[locale]}>
<ThemeProvider>
<BrowserRouter>
{hasNavbarVisible && <AppNavbar changeLocale={changeLocale} />}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<SignUp />} />
<Route path="/play" element={<Play/>} />
<Route path="/lobby" element={<Lobby/>} />
<Route path="/endgame" element={<EndGame/>} />
<Route path="/game" element={<InGame locale={locale} changeLocale={changeLocale}/>}/>
<Route path="/info" element={<InfoPage locale={locale} changeLocale={changeLocale}/>} />
<Route path="/profile" element={<Profile/>} />
{/* <Route path="/solo" element={<SoloGame locale={locale} changeLocale={changeLocale} />}/> */}
</Routes>
</BrowserRouter>
</ThemeProvider>
</IntlProvider>
</GameProvider>
</AuthProvider>
<ErrorBoundary fallback={(error, errorInfo) => <ErrorPage />}>
<AuthProvider>
<GameProvider>
{/*@ts-ignore*/}
<IntlProvider locale={locale} messages={messages[locale]}>
<ThemeProvider>
<BrowserRouter>
{hasNavbarVisible && <AppNavbar changeLocale={changeLocale} locale={locale} />}
<Routes>
<Route path={`${basePath}/`} element={<NewPlay/>} />
<Route path={`${basePath}/login`} element={<Login />} />
<Route path={`${basePath}/signup`} element={<SignUp />} />
<Route path={`${basePath}/presentation`} element={<Home />} />
<Route path={`${basePath}/lobby`} element={<Lobby/>} />
<Route path={`${basePath}/endgame`} element={<EndGame lang={locale}/>} />
<Route path={`${basePath}/game`} element={<InGame locale={locale} changeLocale={changeLocale}/>}/>
<Route path={`${basePath}/info`} element={<InfoPage locale={locale} changeLocale={changeLocale}/>} />
<Route path={`${basePath}/tutorial`} element={<Tutorial locale={locale} changeLocale={changeLocale}/>} />
<Route path={`${basePath}/deduc`} element={<DeducCheck lang={locale}/>} />
<Route path={`${basePath}/TheRealDeduc`} element={<DeducGrid/>} />
<Route path={`${basePath}/profile`} element={<Profile/>} />
<Route path={`${basePath}/join`} element={<Lobbies/>}/>
{/* <Route path="/solo" element={<SoloGame locale={locale} changeLocale={changeLocale} />}/> */}
<Route path="*" element={<ErrorPage code="404" msg='not found' />} /> {/* page 404 */}
</Routes>
</BrowserRouter>
</ThemeProvider>
</IntlProvider>
</GameProvider>
</AuthProvider>
</ErrorBoundary>
);
}

@ -0,0 +1,87 @@
import React, { useState } from 'react';
/* Style */
import '../Style/Global.css';
//import { useTheme } from '../Style/ThemeContext';
/* Model */
import Stub from '../model/Stub';
import Indice from '../model/Indices/Indice';
/* lang */
import { FormattedMessage } from 'react-intl';
/* Boostrap */
import Accordion from 'react-bootstrap/Accordion';
import Table from 'react-bootstrap/Table';
import Case from './CheckCase';
interface AccordionIndiceComponentProps<T extends Indice> {
instance: (new (...args: any[]) => T) | (Function & { prototype: T });
head: string;
lang: string;
}
const AccordionIndice: React.FC<AccordionIndiceComponentProps<any>> = ({ instance, head, lang }) => {
const indices = Stub.GenerateIndice();
const [selectedRows, setSelectedRows] = useState<number[]>([]);
const handleRowClick = (index: number) => {
const newSelectedRows = [...selectedRows];
const selectedIndex = newSelectedRows.indexOf(index);
if (selectedIndex === -1) {
newSelectedRows.push(index);
} else {
newSelectedRows.splice(selectedIndex, 1);
}
console.log('New Selected Rows:', newSelectedRows);
setSelectedRows(newSelectedRows);
};
return (
<>
<Accordion defaultActiveKey={['0']} alwaysOpen style={{ width: '100%' }}>
<Accordion.Item eventKey="0">
<Accordion.Header>{head}</Accordion.Header>
<Accordion.Body>
<Table striped bordered hover>
<thead>
<tr>
<th>Indice</th>
</tr>
</thead>
<tbody>
{indices
.filter((i) => i instanceof instance)
.map((indice, index) => (
<tr
key={index}
onClick={() => handleRowClick(index)}
style={{
cursor: 'pointer',
}}>
<td
style={{
border: selectedRows.includes(index) ? '1px solid red' : 'none',
backgroundColor: selectedRows.includes(index) ? '#FF9191' : 'white',
}}>
{indice.ToString(lang)}
</td>
</tr>
))}
</tbody>
</Table>
</Accordion.Body>
</Accordion.Item>
</Accordion>
</>
);
};
export default AccordionIndice;

@ -5,6 +5,8 @@ import { FormattedMessage } from 'react-intl';
import { useTheme } from '../Style/ThemeContext';
import COLORS from '../Style/Color';
import {basePath} from "../AdressSetup"
//@ts-ignore
function BigButtonNav({ dest, img}) {
@ -15,7 +17,7 @@ function BigButtonNav({ dest, img}) {
// };
return (
<Link to={dest} className="link-without-underline">
<Link to={`${basePath}/${dest}`} className="link-without-underline">
<button className='bigbuttonNabImg' style={{ backgroundColor: theme.colors.primary }}>
<img src={img} alt="Button Image" height="100" width="100"/>
</button>

@ -4,12 +4,14 @@ import './ButtonImgNav.css';
import { FormattedMessage } from 'react-intl';
import { useTheme } from '../Style/ThemeContext';
import {basePath} from "../AdressSetup"
//@ts-ignore
function ButtonImgNav({ dest, img, text = "" }) {
const theme = useTheme();
return (
<Link to={dest} className="link-without-underline"> {/*target='_blank' ==> ouvre un nouvelle onglet*/ }
<Link to={`${basePath}/${dest}`} className="link-without-underline"> {/*target='_blank' ==> ouvre un nouvelle onglet*/ }
<button className='buttonNabImg' style={{ backgroundColor: theme.colors.primary, color: theme.colors.secondary}}>
<img src={img} alt="Button Image" height="50" width="50"/>
<p>{text}</p>

@ -0,0 +1,41 @@
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
/* style */
import { useTheme } from '../Style/ThemeContext';
import '../Pages/DeducGrid.css';
/* res */
import Check from '../res/icon/checkboxGreen.png';
/* trad */
import { FormattedMessage } from 'react-intl';
//@ts-ignore
function Case() {
const theme = useTheme();
const [bg, setbg] = useState('whitesmoke');
//let check = ""; //? avec image
//let bg = 'whitesmoke';
function changeOnCheck(){
// if (check == "")check = Check;
// else check = "";
if(bg == "whitesmoke")setbg(theme.colors.tertiary);
else setbg("whitesmoke");
console.log("clic")
}
return (
<button className='case' onClick={changeOnCheck} style={{backgroundColor: bg, borderColor:'grey'}}>
</button>
);
}
export default Case;

@ -19,6 +19,7 @@ const ChoiceBar = () => {
async function askEveryone(){
if (nodeId !== null){
//@ts-ignore
const person = personNetwork?.getPersons().find((p) => p.getId() == nodeId)
if (person != undefined){
const indiceTester = IndiceTesterFactory.Create(indices[actualPlayerIndex])

File diff suppressed because it is too large Load Diff

@ -3,11 +3,39 @@ import React from 'react';
import { NavDropdown } from 'react-bootstrap';
import LanguageNavItem from './LangNavItem';
import { HiLanguage } from 'react-icons/hi2';
import ReactCountryFlag from 'react-country-flag';
//@ts-ignore
const localToCountryCode = (locale) => {
switch (locale) {
case 'fr':
return 'FR';
case 'en':
return 'GB';
case 'es':
return 'ES';
case 'pt':
return 'PT';
case 'ru':
return 'RU';
default:
return 'FR';
}
};
// @ts-ignore
const LangDropdown = ({ changeLocale }) => {
const LangDropdown = ({ changeLocale, locale}) => {
const selectedcountryCode = localToCountryCode(locale);
return (
<NavDropdown title={<HiLanguage/>} id="language-dropdown" align='end' drop='down-centered'>
<NavDropdown title={
<ReactCountryFlag
className="custom-flag"
countryCode={selectedcountryCode === null ? 'FR' : selectedcountryCode}
svg
style={{ margin: 'auto 10px 3px auto' }}
/>
} id="language-dropdown" align='end' drop='down-centered'>
<LanguageNavItem
countryCode="FR"
languageKey="languageSelector.french"
@ -18,7 +46,21 @@ const LangDropdown = ({ changeLocale }) => {
languageKey="languageSelector.english"
onClick={() => changeLocale('en')}
/>
{/* Ajoutez d'autres langues selon vos besoins */}
<LanguageNavItem
countryCode="ES"
languageKey="languageSelector.espagnol"
onClick={() => changeLocale('es')}
/>
<LanguageNavItem
countryCode="PT"
languageKey="languageSelector.portuguese"
onClick={() => changeLocale('pt')}
/>
<LanguageNavItem
countryCode="RU"
languageKey="languageSelector.russian"
onClick={() => changeLocale('ru')}
/>
</NavDropdown>
);
};

@ -0,0 +1,159 @@
import React, { useEffect, useState } from 'react';
import { useTheme } from '../Style/ThemeContext';
import Player from '../model/Player';
import { Link } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import Modal from 'react-bootstrap/Modal';
import Offcanvas from 'react-bootstrap/Offcanvas';
import Button from 'react-bootstrap/Button';
import { socket } from '../SocketConfig';
import Tooltip from 'react-bootstrap/esm/Tooltip';
import OverlayTrigger from 'react-bootstrap/esm/OverlayTrigger';
import {basePath} from "../AdressSetup"
interface LobbyContainerProps {
roomNum : string
HeadPlayer : Player
nbPlayer : number
setFirst: (first: boolean) => void
started : boolean
//? mettre un "nbplayermax" si le nombre de joueur max peut etre fixé ?
}
const LobbyContainer: React.FC<LobbyContainerProps> = ({roomNum, HeadPlayer, nbPlayer, setFirst, started}) => {
const theme=useTheme();
const navigate = useNavigate();
const dest = 'lobby?room=' + roomNum;
//* Modal
const [showFull, setShowFull] = useState(false);
const [showStart, setShowStart] = useState(false);
const handleClose = () => {
setShowFull(false)
setShowStart(false)
};
const handleShowFull = () => setShowFull(true);
const handleShowStart = () => setShowStart(true);
const handleContainerClick = () => {
if (showFull || showStart){
handleClose()
}
else{
if (nbPlayer < 6 && !started) {
socket.off("request lobbies")
setFirst(true)
navigate(`${basePath}/${dest}`);
}
else if(started){
handleShowStart()
}
else {
handleShowFull()
}
}
};
//* popup pour salle pleine
//@ts-ignore
const renderTooltipFull = (props) => (
<Tooltip id="button-tooltip" {...props}>
Salle Pleine !
</Tooltip>
);
//* popup pour partie déjà lancé
//@ts-ignore
const renderTooltipInGame = (props) => (
<Tooltip id="button-tooltip" {...props}>
En Jeu !
</Tooltip>
);
//* autre
//@ts-ignore
const renderTooltip = (props) => (
<Tooltip id="button-tooltip" {...props}>
Rejoindre le lobby !
</Tooltip>
);
//let IsInGame = true;
let stylefull;
let colorfull;
let actualRender = renderTooltip;
let bgcol = 'white';
if (nbPlayer >= 6) {
stylefull = "darkred"
colorfull = "darkred"
actualRender = renderTooltipFull
}
else {
stylefull = "whitesmoke"
colorfull = "black"
}
if(started){
bgcol = 'lightgrey' //! le hover ne marche plus
actualRender = renderTooltipInGame
}
return(
<OverlayTrigger
placement="top"
delay={{ show: 250, hide: 400 }}
overlay={actualRender}>
<div className='lobbyMainContainer' onClick={handleContainerClick} style={{borderColor:stylefull, backgroundColor:bgcol}}>
<header style={{height:'20%', display:'flex', justifyContent:'end'}}>
<h6><i>Room : {roomNum}</i></h6>
</header>
<hr/>
<h3><b>{HeadPlayer.pseudo}</b></h3>
<div style={{display:'flex', justifyContent:'end', alignItems:'end'}}>
<h2 style={{color:colorfull}}>{nbPlayer}/6</h2>
</div>
{/* Modals */}
<Modal show={showFull} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Salle pleine</Modal.Title>
</Modal.Header>
<Modal.Body>La salle est pleine, il est impossible d'y aller pour le moment !</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Ok
</Button>
</Modal.Footer>
</Modal>
<Modal show={showStart} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>La partie a déjà commencée</Modal.Title>
</Modal.Header>
<Modal.Body>La partie a déjà commencée, il est impossible d'y aller pour le moment !</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Ok
</Button>
</Modal.Footer>
</Modal>
</div>
</OverlayTrigger>
);
}
export default LobbyContainer;

@ -12,6 +12,7 @@ import { BsFillPersonPlusFill } from 'react-icons/bs';
/* Images */
import logo from '../res/img/logo2_preview_rev_1.png';
import defaultImg from '../res/img/Person.png';
/* Components */
import LanguageNavItem from './LangNavItem';
@ -25,23 +26,26 @@ import { useTheme } from '../Style/ThemeContext';
import { useAuth } from '../Contexts/AuthContext';
import { useNavigate } from 'react-router-dom';
import {basePath} from "../AdressSetup"
import Player from '../model/Player';
import { set } from 'lodash';
// @ts-ignore
function AppNavbar({changeLocale}) {
function AppNavbar({changeLocale, locale}) {
const theme = useTheme();
const {user, isLoggedIn, logout} = useAuth();
const navigate = useNavigate();
useEffect(() => {
console.log(user)
}, [user])
const {isLoggedIn, login, user, setUserData, manager } = useAuth();
function navigateToProfile(){
navigate("/profile")
navigate(`${basePath}/profile`)
}
function navigateToLogin(){
navigate(`${basePath}/login`)
}
function navigateToHome(){
navigate("/")
navigate(`${basePath}/`)
}
return (
@ -53,55 +57,46 @@ function AppNavbar({changeLocale}) {
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="me-auto">
<NavDropdown title={<span style={{ color: theme.colors.text }}><FormattedMessage id="play" /></span>} className="navbar-title" id="basic-nav-dropdown">
<NavDropdown.Item href="play"><FormattedMessage id="play_solo" /> </NavDropdown.Item>
<NavDropdown.Divider />
<NavDropdown.Item href="play"><FormattedMessage id="create_room" /> </NavDropdown.Item>
<NavDropdown.Item href="play"><FormattedMessage id="join" /> </NavDropdown.Item>
</NavDropdown>
<Nav.Link href={`${basePath}/`} style={{ color: theme.colors.text }}>
<FormattedMessage id="navbar.play" />
</Nav.Link>
<Nav.Link href={`${basePath}/presentation`} style={{ color: theme.colors.text }}>
<FormattedMessage id="navbar.presentation" />
</Nav.Link>
<Nav.Link href={`${basePath}/info`} style={{ color: theme.colors.text }}>
<FormattedMessage id="navbar.info" />
</Nav.Link>
</Nav>
<div className='leftdiv'>
<Nav className="ml-auto navbar-title-dd">
{isLoggedIn ? (
<NavDropdown
title={<span style={{ color: theme.colors.text }}>Menu <BiDoorOpen /></span>}
id="basic-nav-dropdown"
align="end"
drop='down-centered'
>
<NavDropdown.Item onClick={navigateToProfile}>Profil</NavDropdown.Item>
<LanguageNavItem
countryCode="FR"
languageKey="languageSelector.french"
onClick={() => changeLocale('fr')}
/>
<LanguageNavItem
countryCode="GB"
languageKey="languageSelector.english"
onClick={() => changeLocale('en')}
/>
<NavDropdown.Divider />
<NavDropdown.Item onClick={logout}>Déconnexion</NavDropdown.Item>
</NavDropdown>
) : (
<>
<Nav.Link href="/login" className='navbar-title-dd' style={{ backgroundColor: theme.colors.secondary }}>
<BiLogInCircle />
<FormattedMessage id="log_in" />
</Nav.Link>
<Nav.Link href="/signup" className='navbar-title-dd' style={{ backgroundColor: theme.colors.secondary }}>
<BsFillPersonPlusFill />
<FormattedMessage id="sign_up" />
</Nav.Link>
<LangDropDown changeLocale={changeLocale}/>
</>
)}
{isLoggedIn ? (
<>
{/* Boutou qui lors du clique nous redirige vers le profile */}
<Nav.Link onClick={navigateToProfile} style={{ color: theme.colors.text }}>
<span>
<img src={user? user.profilePicture : defaultImg} height="50" width="50" alt="profile"/>
{user && user.pseudo}
</span>
</Nav.Link>
</>
):(
<>
{/* Bouton qui lors du clique nous redirige vers la connexion */}
<Nav.Link onClick={navigateToLogin} style={{ color: theme.colors.text }}>
<span>
<img src={user?.profilePicture} height="50" width="50" alt="profile"/>
{user && user.pseudo}
</span>
</Nav.Link>
</>
)}
</Nav>
</div>
</Navbar.Collapse>
<LangDropDown changeLocale={changeLocale} locale={locale}/>
</Container>
</Navbar>
);
}
export default AppNavbar;
export default AppNavbar;

@ -7,8 +7,14 @@ import { useTheme } from '../Style/ThemeContext';
/* Ressources */
import Person from '../res/img/Person.png'
import BotImg from '../res/img/bot.png'
import { useGame } from '../Contexts/GameContext';
/* Model */
import Bot from '../model/Bot';
/* Context */
import { useGame } from '../Contexts/GameContext';
import { positionToColor } from '../ColorHelper';
interface PlayerStatusProps {
img: any
state: any
@ -17,39 +23,115 @@ interface PlayerStatusProps {
setPlayerTouched: (newPlayerTouch: number) => void;
playerTouched: number
showCircle: boolean
}
let touchedPlayer = -1
playerIndex: number
askedWrong: boolean
}
let touchedPlayer = -1
//@ts-ignore
const PersonStatus: React.FC<PlayerStatusProps> = ({img = Person, state= Person, name = "Dummy", index, playerTouched, setPlayerTouched, showCircle}) => {
const PersonStatus: React.FC<PlayerStatusProps> = ({img = Person, state= Person, name = "Dummy", index, playerTouched, setPlayerTouched, showCircle, playerIndex, askedWrong}) => {
const theme=useTheme();
const {players} = useGame()
const {players, actualPlayerIndex} = useGame()
if (players[index] instanceof Bot){
img = BotImg
}
const [buffer, setBuffer] = useState('50%')
const [touchedPlayer, setTouchedPlayer] = useState(-2)
useEffect(() =>{
setTouchedPlayer(playerTouched)
}, [playerTouched])
let IsActualPlayer = index != actualPlayerIndex
useEffect(() => {
if (playerIndex===index){
setBuffer('5px')
}
else{
setBuffer('50%')
}
}, [playerIndex])
function onTouch(){
if (IsActualPlayer && !askedWrong){
setPlayerTouched(index)
setTouchedPlayer(index)
//setIsClicked(!isClicked);
}
}
//* Code pour le cercle
//const [isClicked, setIsClicked] = useState(false); // État du bouton, par défaut false
const handleClick = () => {
onTouch();
};
const circleStyle: React.CSSProperties = {
backgroundColor: positionToColor(index), // Changement de la couleur en fonction de la condition
borderRadius: buffer,
width: '80px',
height: '80px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
position: 'relative',
objectFit: 'cover'
};
const circleStyleInt: React.CSSProperties = {
backgroundColor: touchedPlayer == index && showCircle ? 'lightblue' : 'white',
borderRadius: buffer,
width: '70px',
height: '70px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
position: 'relative',
objectFit: 'cover'
}
const imageStyle: React.CSSProperties = {
borderRadius: '50%',
width: '60px',
height: '60px',
objectFit: 'cover', // image est ajustée pour couvrir le cercle
position: 'relative',
};
return (
<div className='centerDivV' onClick={() => setPlayerTouched(index)}>
<img src={img} alt="player" height="60" width="60"/>
<h5>{name}</h5>
{(touchedPlayer == index && showCircle) ?(
<div className='statusDiv' style={{ backgroundColor: "gold" }}>
<img src={state} alt="state" height="30" width="30"/>
</div>
): showCircle &&
(
<div className='statusDiv' style={{ backgroundColor: theme.colors.primary }}>
<img src={state} alt="state" height="30" width="30"/>
</div>
) }
<div className='centerDivV'>
{/* <div className='centerDivV' style={{border:buffer, borderRadius: '50%', padding:'1px'}}> */}
<div className='centerDivV' style={circleStyle} onClick={() => handleClick()}>
<div className='centerDivV' style={circleStyleInt}>
<img src={img} alt="player" height="40" width="40" style={{ ...imageStyle, objectFit: 'cover' }}/>
</div>
{/* </div> */}
</div>
<div className='playerNameDisplay'>
<h6>{actualPlayerIndex !== index ? (name.length > 18 ? name.substring(0, 15) + '...' : name) : 'vous'}</h6>
</div>
</div>
);
}
export default PersonStatus;
// {IsActualPlayer && (
// (touchedPlayer == index && showCircle) ?(
// <div className='statusDiv' style={{ backgroundColor: "gold" }}>
// <img src={state} alt="state" height="30" width="30"/>
// </div>
// ): showCircle &&
// (
// <div className='statusDiv' style={{ backgroundColor: theme.colors.primary }}>
// <img src={state} alt="state" height="30" width="30"/>
// </div>
// )
// )}

@ -20,6 +20,7 @@ import Bot from '../model/Bot';
/* server */
import { socket } from '../SocketConfig';
import { Form } from 'react-router-dom';
interface MyPlayerItemListProps {
player : Player,
@ -31,7 +32,7 @@ const PlayerItemList:React.FC<MyPlayerItemListProps> =({ player, room }) => {
// const isBot = pdp === Bot;
let pdp;
const isBot = player instanceof Bot;
isBot ? pdp = BotPDP : pdp = player.profilePicture;
isBot ? pdp = BotPDP : pdp = PersonPDP;
const delBot = () => {
@ -58,13 +59,13 @@ const PlayerItemList:React.FC<MyPlayerItemListProps> =({ player, room }) => {
{isBot && (
<ToggleButtonGroup type='radio' name={`options-${player.id}`} defaultValue={1}>
<ToggleButton id={`tbg-radio-1-${player.id}`} value={1}>
Facile
<FormattedMessage id='easy' />
</ToggleButton>
<ToggleButton id={`tbg-radio-2-${player.id}`} value={2}>
Intermédiaire
<FormattedMessage id='medium' />
</ToggleButton>
<ToggleButton id={`tbg-radio-3-${player.id}`} value={3}>
Fort
<FormattedMessage id='strong' />
</ToggleButton>
</ToggleButtonGroup>
)}

@ -1,11 +1,14 @@
import React from 'react';
import { colorToEmoji, positionToColor } from '../ColorHelper';
import { colorToEmoji, positionToColor, positionToEmoji } from '../ColorHelper';
import Player from '../model/Player';
import { useTheme } from '../Style/ThemeContext';
import PersonStatus from './PersonStatus';
import Person from '../res/img/Person.png'
import BotImg from '../res/img/bot.png'
import { socket } from '../SocketConfig';
import { FormattedMessage } from 'react-intl';
//@ts-ignore
@ -14,18 +17,38 @@ interface PlayerListProps {
players: Player[];
playerTouched: number
setPlayerTouched: (newPlayerTouch: number) => void;
}
playerIndex: number
askedWrong: boolean
greyForEveryone: () => void
}
const PlayerList: React.FC<PlayerListProps> = ({ players, playerTouched, setPlayerTouched}) => {
const PlayerList: React.FC<PlayerListProps> = ({ players, playerTouched, setPlayerTouched, playerIndex, askedWrong, greyForEveryone}) => {
const theme = useTheme();
function askEveryone(){
if (!askedWrong){
greyForEveryone()
setPlayerTouched(players.length)
}
}
return (
<div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '16px' }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '20px' }}>
{
//@ts-ignore
players.map((player, index) => (
player.id!=socket.id && <PersonStatus img={player.profilePicture} state={Person} key={index} name={player.pseudo + " " + colorToEmoji(positionToColor(index), true)} playerTouched={playerTouched} setPlayerTouched={setPlayerTouched} index={index} showCircle={true}/>
//player.id!=socket.id &&
<PersonStatus img={player instanceof Player ? Person : BotImg}
state={Person}
key={index}
name={player.pseudo}
playerTouched={playerTouched}
setPlayerTouched={setPlayerTouched}
index={index}
showCircle={true}
playerIndex={playerIndex}
askedWrong={askedWrong}/>
))
}
</div>
@ -36,28 +59,31 @@ const PlayerList: React.FC<PlayerListProps> = ({ players, playerTouched, setPlay
alignItems:"center",
margin: 10
}}>
{(playerTouched == players.length)
?(
<button style={{
backgroundColor: "gold",
borderColor: theme.colors.secondary,
borderRadius: "10px",
border: "solid 1px",
textAlign: "center",
padding: "10px"}}
onClick={() => setPlayerTouched(players.length)}>Ask everyone</button>
):
(
<button style={{
backgroundColor: theme.colors.primary,
borderColor: theme.colors.secondary,
borderRadius: "10px",
border: "solid 1px",
textAlign: "center",
padding: "10px"}}
onClick={() => setPlayerTouched(players.length)}>Ask everyone</button>
)
}
{(playerTouched == players.length)
?(
<button style={{
backgroundColor: "gold",
borderColor: theme.colors.secondary,
borderRadius: "10px",
border: "solid 1px",
textAlign: "center",
color: "white",
padding: "10px"}}
onClick={() => askEveryone()}><FormattedMessage id='askeveryone'/></button>
):
(
<button style={{
backgroundColor: theme.colors.primary,
borderColor: theme.colors.secondary,
borderRadius: "10px",
border: "solid 1px",
textAlign: "center",
color: "white",
padding: "10px"}}
onClick={() => askEveryone()}><FormattedMessage id='askeveryone'/></button>
)
}
</div>
</div>

@ -0,0 +1,28 @@
/* Ajoutez ces styles dans votre fichier CSS ou utilisez un préprocesseur comme SCSS */
.tabsStats {
padding: 20px;
}
.stats {
background-color: #f0f0f0;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
text-align: center;
}
/* Personnalisez davantage selon vos préférences */
.carousselButton{
border:none;
background-color:darkgray;
color:white;
opacity: 60%;
width: 50px;
height: 70px;
font-size: 40px;
border-radius: 5px;
padding: 5px;
margin: 5px;
cursor: pointer;
}

@ -1,13 +1,13 @@
import React from 'react';
import React, {useState, useEffect} from 'react';
import Carousel from 'nuka-carousel';
/* Style */
import '../Pages/Play.css';
import '../Style/Global.css'
import './ScoreBoard.css';
import { useTheme } from '../Style/ThemeContext';
/* Ressources */
import Person from '../res/img/Person.png'
import leave from '../res/img/bot.png'
import trophy from '../res/icon/trophy.png'
import share from '../res/icon/share.png';
@ -19,95 +19,373 @@ import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
/* Component */
import ButtonImgNav from './ButtonImgNav';
import User from '../model/User';
/* Types */
/* Services */
import ScoreboardService from '../services/ScoreboardService';
import { BiLineChart, BiLineChartDown } from 'react-icons/bi';
import { CarouselCaption } from 'react-bootstrap';
import { BsLine } from 'react-icons/bs';
import { FormattedMessage, useIntl } from 'react-intl';
//@ts-ignore
const ScoreBoard: React.FC<{ Player: User }> = ({ Player }) => {
const theme=useTheme();
const [carouselKey, setCarouselKey] = useState(0);
const [activeTab, setActiveTab] = useState("perso");
// DAILY STATS
const [dailyMastermindStats, setDailyMastermindStats] = useState<any>(null);
const [dailyEasyEnigmaStats, setDailyEasyEnigmaStats] = useState<any>(null);
const [dailyMediumEnigmaStats, setDailyMediumEnigmaStats] = useState<any>(null);
const [dailyHardEnigmaStats, setDailyHardEnigmaStats] = useState<any>(null);
const [dailyOnlineStats, setDailyOnlineStats] = useState<any>(null);
// WEEKLY STATS
const [weeklyMastermindStats, setWeeklyMastermindStats] = useState<any>(null);
const [weeklyEasyEnigmaStats, setWeeklyEasyEnigmaStats] = useState<any>(null);
const [weeklyMediumEnigmaStats, setWeeklyMediumEnigmaStats] = useState<any>(null);
const [weeklyHardEnigmaStats, setWeeklyHardEnigmaStats] = useState<any>(null);
const [weeklyOnlineStats, setWeeklyOnlineStats] = useState<any>(null);
// Récupérer les records daily
useEffect(() => {
async function fetchDailyStats() {
try {
const resultMM = await ScoreboardService.getDailyMastermindStats();
const resultEF = await ScoreboardService.getDailyEasyEnigmaStats();
const resultEM = await ScoreboardService.getDailyMediumEnigmaStats();
const resultED = await ScoreboardService.getDailyHardEnigmaStats();
const resultOL = await ScoreboardService.getDailyOnlineStats();
setDailyMastermindStats(resultMM);
setDailyEasyEnigmaStats(resultEF);
setDailyMediumEnigmaStats(resultEM);
setDailyHardEnigmaStats(resultED);
setDailyOnlineStats(resultOL);
} catch (error) {
console.error(error);
}
}
async function fetchWeeklyStats() {
try{
const resultMM = await ScoreboardService.getWeeklyMastermindStats();
const resultEF = await ScoreboardService.getWeeklyEasyEnigmaStats();
const resultEM = await ScoreboardService.getWeeklyMediumEnigmaStats();
const resultED = await ScoreboardService.getWeeklyHardEnigmaStats();
const resultOL = await ScoreboardService.getWeeklyOnlineStats();
setWeeklyMastermindStats(resultMM);
setWeeklyEasyEnigmaStats(resultEF);
setWeeklyMediumEnigmaStats(resultEM);
setWeeklyHardEnigmaStats(resultED);
setWeeklyOnlineStats(resultOL);
} catch (error) {
console.error(error);
}
}
fetchDailyStats();
fetchWeeklyStats();
}, []);
const intl = useIntl();
//@ts-ignore
const CustomPrevButton = ({ previousSlide }) => (
<button className='carousselButton' onClick={previousSlide}>&lt;</button>
);
//@ts-ignore
const CustomNextButton = ({ nextSlide }) => (
<button className='carousselButton' onClick={nextSlide}>&gt;</button>
);
return (
// <div className='LeaderBoardiv'>
<div className='LeaderBoardiv'>
<Tabs style={{width:"100%"}}
defaultActiveKey="daily"
id="ScoreBoard"
className="mb-3">
<Tab eventKey="perso" title="Vos Stats" disabled = { !Player.pseudo.startsWith("Guest_") ? false : true}>
<Container fluid>
<Row>Stats en solo :</Row>
<Row>
<Col sm={10}>Partie Jouées :</Col>
<Col className='leftRow'>{Player !== null ? Player.soloStats.nbGames : "0"}</Col>
</Row>
<Row>
<Col sm={10}>Best-Score :</Col>
<Col className='leftRow'>{Player !== null ? Player.soloStats.bestScore : "0"}</Col>
</Row>
<Row>
<Col sm={10}>Moyenne d'essai :</Col>
<Col className='leftRow'>{Player !== null ? Player.soloStats.avgNbTry : "0"}</Col>
</Row>
<hr/>
<Row>Stats en ligne :</Row>
<Row>
<Col sm={10}>Partie jouée :</Col>
<Col className='leftRow'>{Player !== null ? Player.onlineStats.nbGames : "0"}</Col>
</Row>
<Row>
<Col sm={10}>Nombre de victoire :</Col>
<Col className='leftRow'>{Player !== null ? Player.onlineStats.nbWins : "0"}</Col>
</Row>
<Row>
<Col sm={10}>Ratio P/V :</Col>
<Col className='leftRow'>{Player !== null ? Player.onlineStats.ratio : "0"}</Col>
</Row>
</Container>
</Tab>
<Tab eventKey="daily" title="Daily"
style={{display:"flex", flexDirection:'column', alignItems:'center'}}>
<img src={trophy}
height='100'
width='100'
alt="Person2"/>
<Container fluid>
<Row>
<Col sm={8}>Partie Jouées :</Col>
<Col className='leftRow'>10</Col>
</Row>
<Row>
<Col sm={8}>Partie gagnées :</Col>
<Col className='leftRow'>2</Col>
</Row>
<Row>
<Col sm={8}>Pions posés :</Col>
<Col className='leftRow'>2</Col>
</Row>
<hr/>
<Row>
<Col sm={8}>Partie solo :</Col>
<Col className='leftRow'>21</Col>
</Row>
<Row>
<Col sm={8}>Nombre de coups moyen :</Col>
<Col className='leftRow'>19</Col>
</Row>
</Container>
</Tab>
<Tab eventKey="weekly" title="Weekly">
<img src={trophy}
height='100'
width='100'
alt="Person2"/>
</Tab>
</Tabs>
<ButtonImgNav dest='/' img={share}/>
</div>
//</div>
<Tabs
activeKey={activeTab}
onSelect={(key:any) => {
setActiveTab(key);
// Forcer une mise à jour du carousel
setCarouselKey((prevKey) => prevKey + 1);
}}
id="ScoreBoard"
className="tabsStats justify-content-around">
<Tab eventKey="perso"
title={intl.formatMessage({ id: 'score.tab.stat' })}
disabled={!Player.pseudo.startsWith("Guest_") ? false : true}>
<Tab.Content className={`tabsStats ${activeTab !== 'perso' ? 'hidden' : ''}`}>
<Carousel
renderCenterLeftControls={({ previousSlide }) => <CustomPrevButton previousSlide={previousSlide} />}
renderCenterRightControls={({ nextSlide }) => <CustomNextButton nextSlide={nextSlide} />}
adaptiveHeight
wrapAround
slidesToShow={1}
cellSpacing={10}
key={carouselKey}
>
<div className="stats">
<h5><FormattedMessage id='info.mdj.mastermind'/></h5>
<hr />
<p><FormattedMessage id='score.nbPlayed'/> : {Player.mastermindStats.nbGames}</p>
<p><FormattedMessage id='score.best'/>: {Player.mastermindStats.bestScore}</p>
<p><FormattedMessage id='score.moy'/>: {Player.mastermindStats.avgNbTry.toFixed(2)}</p>
</div>
<div className="stats">
<h5><FormattedMessage id='score.titre.easy'/></h5>
<hr />
<p><FormattedMessage id='score.nbPlayed'/>: {Player.easyEnigmaStats.nbGames}</p>
<p><FormattedMessage id='score.NbWin'/>: {Player.easyEnigmaStats.nbWins}</p>
<p><FormattedMessage id='score.ratio'/>: {Player.easyEnigmaStats.ratio.toFixed(2) + "%"}</p>
<p><FormattedMessage id='score.bestTmp'/>: {Player.easyEnigmaStats.bestTime + "s"}</p>
<p><FormattedMessage id='score.moyTmp'/>: {Player.easyEnigmaStats.avgTime.toFixed(2) + "s"}</p>
</div>
<div className="stats">
<h5><FormattedMessage id='score.titre.int'/></h5>
<hr />
<p><FormattedMessage id='score.nbPlayed'/>: {Player.mediumEnigmaStats.nbGames}</p>
<p><FormattedMessage id='score.best'/>: {Player.mediumEnigmaStats.bestScore}</p>
<p><FormattedMessage id='score.moy'/>: {Player.mediumEnigmaStats.avgNbTry.toFixed(2)}</p>
</div>
<div className="stats">
<h5><FormattedMessage id='score.titre.hard'/></h5>
<hr />
<p><FormattedMessage id='score.nbPlayed'/>: {Player.hardEnigmaStats.nbGames}</p>
<p><FormattedMessage id='score.NbWin'/>: {Player.hardEnigmaStats.nbWins}</p>
<p><FormattedMessage id='score.ratio'/>: {Player.hardEnigmaStats.ratio.toFixed(2) + "%"}</p>
<p><FormattedMessage id='score.bestTmp'/>: {Player.hardEnigmaStats.bestTime + "s"}</p>
<p><FormattedMessage id='score.moyTmp'/>: {Player.hardEnigmaStats.avgTime.toFixed(2) + "s"}</p>
</div>
<div className="stats">
<h5><FormattedMessage id='score.online'/></h5>
<hr />
<p><FormattedMessage id='score.nbPlayed'/>: {Player.onlineStats.nbGames}</p>
<p><FormattedMessage id='score.NbWin'/>: {Player.onlineStats.nbWins}</p>
<p><FormattedMessage id='score.ratio'/>: {Player.onlineStats.ratio.toFixed(2) + "s"}</p>
</div>
</Carousel>
</Tab.Content>
</Tab>
<Tab eventKey="daily" title={intl.formatMessage({ id: 'score.tab.quoti' })}>
<Tab.Content className={`tabsStats ${activeTab !== 'daily' ? 'hidden' : ''}`}>
<Carousel adaptiveHeight wrapAround slidesToShow={1} cellSpacing={10} key={carouselKey}>
<div className="stats">
<h5><FormattedMessage id='info.mdj.mastermind'/></h5>
<hr />
{dailyMastermindStats !== null ? (dailyMastermindStats.tab.length !== 0 ? dailyMastermindStats.tab.map((stats: any, index: number) => (
<>
<Row>
<Col>
<p>{index+1}.{stats.pseudo}</p>
</Col>
<Col>
<p>{stats.score + " essai(s)"}</p>
</Col>
</Row>
</>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)}
</div>
<div className="stats">
<h5><FormattedMessage id='score.titre.easy'/></h5>
<hr />
{dailyEasyEnigmaStats !== null ? (dailyEasyEnigmaStats.tab.length !== 0 ? dailyEasyEnigmaStats.tab.map((stats: any, index: number) => (
<>
<Row>
<Col>
<p>{index+1}.{stats.pseudo}</p>
</Col>
<Col>
<p>{stats.time + "s"}</p>
</Col>
</Row>
</>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)}
</div>
<div className="stats">
<h5><FormattedMessage id='score.titre.int'/></h5>
<hr />
{dailyMediumEnigmaStats !== null ? (dailyMediumEnigmaStats.tab.length !== 0 ? dailyMediumEnigmaStats.tab.map((stats: any, index: number) => (
<>
<Row>
<Col>
<p>{index+1}.{stats.pseudo}</p>
</Col>
<Col>
<p>{stats.time + "s"}</p>
</Col>
</Row>
</>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)}
</div>
<div className="stats">
<h5><FormattedMessage id='score.titre.hard'/></h5>
<hr />
{dailyHardEnigmaStats !== null ? (dailyHardEnigmaStats.tab.length !== 0 ? dailyHardEnigmaStats.tab.map((stats: any, index: number) => (
<>
<Row>
<Col>
<p>{index+1}.{stats.pseudo}</p>
</Col>
<Col>
<p>{stats.time + "s"}</p>
</Col>
</Row>
</>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)}
</div>
<div className="stats">
<h5><FormattedMessage id='score.online'/></h5>
<hr />
{dailyOnlineStats !== null ? (dailyOnlineStats.tab.length !== 0 ? dailyOnlineStats.tab.map((stats: any, index: number) => (
<>
<Row>
<Col>
<p>{index+1}.{stats.pseudo}</p>
</Col>
<Col>
<p>{stats.wins + " victoires"}</p>
</Col>
</Row>
</>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)}
</div>
</Carousel>
</Tab.Content>
</Tab>
<Tab eventKey="weekly" title={intl.formatMessage({ id: 'score.tab.hebdo' })}>
<Tab.Content className={`tabsStats ${activeTab !== 'weekly' ? 'hidden' : ''}`}>
<Carousel adaptiveHeight wrapAround slidesToShow={1} cellSpacing={10} key={carouselKey}>
<div className="stats">
<h5><FormattedMessage id='info.mdj.mastermind'/></h5>
<hr />
{weeklyMastermindStats !== null ? (weeklyMastermindStats.tab.length !== 0 ? weeklyMastermindStats.tab.map((stats: any, index: number) => (
<>
<Row>
<Col>
<p>{index+1}.{stats.pseudo}</p>
</Col>
<Col>
<p>{stats.score + " essai(s)"}</p>
</Col>
</Row>
</>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)}
</div>
<div className="stats">
<h5><FormattedMessage id='score.titre.easy'/></h5>
<hr />
{weeklyEasyEnigmaStats !== null ? (weeklyEasyEnigmaStats.tab.length !== 0 ? weeklyEasyEnigmaStats.tab.map((stats: any, index: number) => (
<>
<Row>
<Col>
<p>{index+1}.{stats.pseudo}</p>
</Col>
<Col>
<p>{stats.time + "s"}</p>
</Col>
</Row>
</>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)}
</div>
<div className="stats">
<h5><FormattedMessage id='score.titre.int'/></h5>
<hr />
{weeklyMediumEnigmaStats !== null ? (weeklyMediumEnigmaStats.tab.length !== 0 ? weeklyMediumEnigmaStats.tab.map((stats: any, index: number) => (
<>
<Row>
<Col>
<p>{index+1}.{stats.pseudo}</p>
</Col>
<Col>
<p>{stats.time + "s"}</p>
</Col>
</Row>
</>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)}
</div>
<div className="stats">
<h5><FormattedMessage id='score.titre.hard'/></h5>
<hr />
{weeklyHardEnigmaStats !== null ? (weeklyHardEnigmaStats.tab.length !== 0 ? weeklyHardEnigmaStats.tab.map((stats: any, index: number) => (
<>
<Row>
<Col>
<p>{index+1}.{stats.pseudo}</p>
</Col>
<Col>
<p>{stats.time + "s"}</p>
</Col>
</Row>
</>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)}
</div>
<div className="stats">
<h5><FormattedMessage id='score.titre.online'/></h5>
<hr />
{weeklyOnlineStats !== null ? (weeklyOnlineStats.tab.length !== 0 ? weeklyOnlineStats.tab.map((stats: any, index: number) => (
<>
<Row>
<Col>
<p>{index+1}.{stats.pseudo}</p>
</Col>
<Col>
<p>{stats.wins + " victoires"}</p>
</Col>
</Row>
</>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)) : (
<p className='text-warning'><FormattedMessage id='score.nothing'/></p>
)}
</div>
</Carousel>
</Tab.Content>
</Tab>
</Tabs>
);
}
export default ScoreBoard;
export default ScoreBoard;

@ -12,7 +12,6 @@ const TurnBar: React.FC<TurnBarProps> = ({text})=> {
style={{
borderColor: theme.colors.secondary
}}>
{/* texte changeable et a traduire */}
<p>{text}</p>
</div>
);

@ -0,0 +1,335 @@
import React, { useEffect, useState } from "react";
import { DataSet, Network} from "vis-network/standalone/esm/vis-network";
import GraphCreator from "../model/Graph/GraphCreator";
import "./GraphContainer.css";
import IndiceTesterFactory from "../model/Factory/IndiceTesterFactory";
import Person from "../model/Person";
import { useNavigate } from "react-router-dom";
import { useGame } from "../Contexts/GameContext";
import { socket } from "../SocketConfig"
import { colorToEmoji, positionToColor, positionToEmoji } from "../ColorHelper";
import { ColorToHexa } from "../model/EnumExtender";
import Bot from "../model/Bot";
import NodePerson from "../model/Graph/NodePerson";
import { useAuth } from "../Contexts/AuthContext";
import Indice from "../model/Indices/Indice";
import Pair from "../model/Pair";
import Player from "../model/Player";
import JSONParser from "../JSONParser";
import User from "../model/User";
import { json } from "body-parser";
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
import {basePath} from "../AdressSetup"
import PersonNetwork from "../model/PersonsNetwork";
import { useIntl } from "react-intl";
interface TutorialGraphProps {
setNetwork: (network: Network) => void
showLast: boolean
setPlayerIndex: (playerIndex: number) => void
handleShowTurnBar: (bool: boolean) => void
handleTurnBarTextChange: (text: string) => void
addToHistory: (text: string) => void
setPlayerTouched: (playerIndex: number) => void
playerTouched: number
tutoStep: number
setTutoStep: (step: number) => void
setGreyForEveryone: (func: () => void) => void
displayModalstep: (step: number) => void;
}
let lastNodes: NodePerson[] = []
let firstIndex = true
let first = true
let touchedPlayer = -1
let stepTuto = -1
const TutorialGraph: React.FC<TutorialGraphProps> = ({showLast, setNetwork, setPlayerIndex, handleShowTurnBar, handleTurnBarTextChange, addToHistory, setPlayerTouched, playerTouched, tutoStep, setTutoStep, setGreyForEveryone, displayModalstep}) => {
let cptTour: number = 0
//* Gestion du temps :
let initMtn = 0
//* traduction
const intl = useIntl();
const {isLoggedIn, user, manager} = useAuth();
const {setIndiceData, setIndicesData, setActualPlayerIndexData, setWinnerData, setPlayersData, setNetworkDataData, setPersonData} = useGame();
const params = new URLSearchParams(window.location.search);
const navigate = useNavigate();
const [lastIndex, setLastIndex] = useState(-1)
if (first){
first = false
setActualPlayerIndexData(0)
handleTurnBarTextChange(intl.formatMessage({ id: 'game.yourTurn' }))
handleShowTurnBar(true)
}
useEffect(() =>{
touchedPlayer=playerTouched
}, [playerTouched])
useEffect(() => {
stepTuto = tutoStep
if(stepTuto===1){
handleShowTurnBar(true)
}
}, [tutoStep])
useEffect(() => {
const tab: NodePerson[] = []
for(const n of lastNodes.reverse()){
}
lastNodes = tab
if (showLast){
socket.emit("opacity activated", socket.id)
}
else{
socket.emit("opacity deactivated", socket.id)
}
}, [showLast])
let playerIndex: number = 0
if (firstIndex){
firstIndex=false
setPlayerIndex(playerIndex)
}
let index = 0
useEffect(() => {
let jsonGraph = require("../tuto/graph.json")
let jsonIndice = require("../tuto/indices.json")
const personNetwork = JSONParser.JSONToNetwork(JSON.stringify(jsonGraph))
const indices = JSONParser.JSONToIndices(jsonIndice)
setIndiceData(indices[0])
if (personNetwork == null){
return
}
const graph = GraphCreator.CreateGraph(personNetwork)
const nodes = new DataSet(graph.nodesPerson);
setIndicesData(indices)
setPersonData(personNetwork.getPersons()[4])
let n = graph.nodesPerson;
let e = graph.edges;
const graphState = { n, e };
// Sauvegarder l'état dans localStorage
localStorage.setItem('graphState', JSON.stringify(graphState));
const container = document.getElementById('graph-container');
if (!container) {
console.error("Container not found");
return;
}
// Charger les données dans le graph
// Configuration des options du Graphe
const initialOptions = {
layout: {
improvedLayout: true,
hierarchical: {
enabled: false,
direction: 'LR', // LR (Left to Right) ou autre selon votre préférence
sortMethod: 'hubsize'
},
randomSeed: 3
},
physics: {
enabled: true,
barnesHut: {
gravitationalConstant: -1000,
springConstant: 0.001,
springLength: 100
},
solver: "repulsion",
repulsion: {
nodeDistance: 100 // Put more distance between the nodes.
}
}
};
const networkData = { nodes: nodes, edges: graph.edges };
const network = new Network(container, networkData, initialOptions);
network.stabilize();
setNetwork(network)
const myFunctionInsideEffect = () => {
if (stepTuto === 3){
const tabGrey = [0, 1, 2, 3, 5, 6, 7, 8, 9]
for (const i of tabGrey){
nodes.update({id: i, color: "#808080"})
}
}
};
setGreyForEveryone(() => myFunctionInsideEffect);
network.on("click", async (params) => {
if(params.nodes.length > 0){
}
});
network.on("dragging", (params) => {
if (params.nodes.length > 0) {
// Un nœud a été cliqué
initialOptions.physics.enabled = false;
network.setOptions(initialOptions);
setNetwork(network)
}
});
network.on("click", async (params) => {
if(params.nodes.length > 0){
if (stepTuto === 0 && touchedPlayer === 1){
const node = nodes.get().find((n: NodePerson) => n.id === params.nodes[0])
if (node === undefined)return;
if (node.id === 7){
nodes.update({id: node.id, label: node.label + positionToEmoji(1, true)})
handleShowTurnBar(false)
setPlayerIndex(1)
setPlayerTouched(-1)
await delay(500)
const node2 = nodes.get().find((n: NodePerson) => n.id === params.nodes[0])
if (node2 === undefined)return;
nodes.update({id: node.id, label: node2.label + positionToEmoji(2, false)})
await delay(500)
const node3 = nodes.get().find((n: NodePerson) => n.id === 8)
if (node3 === undefined)return;
nodes.update({id: node3.id, label: node3.label + positionToEmoji(1, false)})
setPlayerIndex(2)
await delay(500)
const node4 = nodes.get().find((n: NodePerson) => n.id === 0)
if (node4 === undefined)return;
nodes.update({id: node4.id, label: node4.label + positionToEmoji(1, true)})
setPlayerIndex(0)
setTutoStep(1)
displayModalstep(1);
}
}
else if(stepTuto === 1 && touchedPlayer === 2){
const node = nodes.get().find((n: NodePerson) => n.id === params.nodes[0])
if (node === undefined){
return;
}
if (node.id === 0){
nodes.update({id: node.id, label: node.label + positionToEmoji(2, false)})
setPlayerTouched(-1)
displayModalstep(2);
handleTurnBarTextChange(intl.formatMessage({ id: 'game.wrong' }))
const tabGrey = [7, 0, 4, 1, 8]
for (const id of tabGrey){
const node = nodes.get().find((n: NodePerson) => n.id === id)
if (node === undefined)return;
nodes.update({id: node.id, color: "#808080"})
}
setTutoStep(2)
}
}
else if(stepTuto === 2){
const node = nodes.get().find((n: NodePerson) => n.id === params.nodes[0])
if (node === undefined)return;
if (node.id === 9){
const tabColor = [7, 0, 4, 1, 8]
nodes.update({id: node.id, label: node.label + positionToEmoji(0, false)})
for(const id of tabColor){
const pers = personNetwork.getPersons().find((p: Person) => p.getId() === id)
if (pers !== undefined){
nodes.update({id: id, color: ColorToHexa(pers.getColor())})
}
}
handleShowTurnBar(false)
setPlayerIndex(1)
await delay(500)
const node2 = nodes.get().find((n: NodePerson) => n.id === 4)
if (node2 === undefined)return;
nodes.update({id: node2.id, label: node2.label + positionToEmoji(2, true)})
setPlayerIndex(2)
await delay(500)
const node3 = nodes.get().find((n: NodePerson) => n.id === 3)
if (node3 === undefined)return;
nodes.update({id: node3.id, label: node3.label + positionToEmoji(0, false)})
await delay(500)
const node4 = nodes.get().find((n: NodePerson) => n.id === 1)
if (node4 === undefined)return;
nodes.update({id: node4.id, label: node4.label + positionToEmoji(2, false)})
setPlayerIndex(0)
handleTurnBarTextChange(intl.formatMessage({ id: 'game.yourTurn' }))
handleShowTurnBar(true)
setTutoStep(3)
displayModalstep(3);
}
}
else if(stepTuto === 3 && touchedPlayer === 3){
const node = nodes.get().find((n: NodePerson) => n.id === params.nodes[0])
if (node === undefined)return;
if (node.id === 4){
nodes.update({id: node.id, label: node.label + positionToEmoji(0, true)})
setPlayerTouched(-1)
await delay(500)
const node2 = nodes.get().find((n: NodePerson) => n.id === 4)
if (node2 === undefined)return;
nodes.update({id: node2.id, label: node2.label + positionToEmoji(1, true)})
await delay(500)
for(const person of personNetwork.getPersons()){
nodes.update({id: person.getId(), color: ColorToHexa(person.getColor())})
}
if (user != null){
const winner = user;
setNetworkDataData(networkData)
setActualPlayerIndexData(-1)
setLastIndex(-1)
setPlayerTouched(-1)
setWinnerData(winner)
first = true
navigate(`${basePath}/endgame`)
}
}
}
}
else{
setPlayerTouched(-1)
}
});
}, []); // Le tableau vide signifie que cela ne s'exécutera qu'une fois après le premier rendu
return (
<>
<div id="graph-container"/>
</>
);
function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
export default TutorialGraph;

@ -24,8 +24,6 @@ const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const login = async () => {
setIsLoggedIn(true);
const [u, bool] = await manager.userService.fetchUserInformation()
setUser(u)
};
const setUserData = (newPlayer: User) => {

@ -1,4 +1,6 @@
import React, { createContext, useContext, useState, ReactNode } from 'react';
import { DataSet, Edge } from 'vis-network';
import NodePerson from '../model/Graph/NodePerson';
import Indice from '../model/Indices/Indice';
import Pair from '../model/Pair';
import Person from '../model/Person';
@ -21,6 +23,11 @@ interface GameContextProps {
dailyEnigme: Map<number, Pair<Indice, boolean>[]>
nbCoup : number
temps : number
networkData: any
seed: number | string;
nodesC: NodePerson[]
gameStart: boolean
setGameStartData : (newStart: boolean) => void
setIndicesData: (newIndices: Indice[]) => void;
setIndiceData: (newIndice: Indice) => void;
setPersonData: (newPerson: Person) => void;
@ -32,11 +39,14 @@ interface GameContextProps {
setTurnPlayerIndexData: (newTurnPlayerIndex: number) => void;
setRoomData: (newRoom: string) => void;
setOnlyFalseData: (newOnlyFalse: boolean) => void
setWinnerData: (winner: Player) => void
setWinnerData: (winner: Player | null) => void
reset: () => void
setDailyEnigmeData: (map: Map<number, Pair<Indice, boolean>[]>) => void
setNbCoupData: (newNbCoup : number) => void
settempsData: (newtemps : number) => void
setNetworkDataData: (networkData: any) => void
setSeedData: (seed: number | string) => void
setNodesData: (nodes: NodePerson[]) => void
}
const GameContext = createContext<GameContextProps | undefined>(undefined);
@ -61,8 +71,28 @@ export const GameProvider: React.FC<GameProviderProps> = ({ children }) => {
const [dailyEnigme, setDailyEnigme] = useState<Map<number, Pair<Indice, boolean>[]>>(new Map())
const [nbCoup, setNbCoup] = useState<number>(0);
const [temps, settemps] = useState<number>(0);
const [networkData, setNetworkData] = useState<any>(null);
const [seed, setSeed] = useState<number | string>(0);
const [nodesC, setNodes] = useState<NodePerson[]>([]);
const [gameStart, setGameStart] = useState<boolean>(true)
const setGameStartData = (newStart: boolean) => {
setGameStart(newStart)
}
const setNodesData = (nodes: NodePerson[]) => {
setNodes(nodes)
}
const setNetworkDataData = (networkData: any) => {
setNetworkData(networkData);
}
const setSeedData = (seed: number | string) => {
setSeed(seed);
}
const setIndicesData = (newIndices: Indice[]) => {
setIndices(newIndices);
};
@ -108,7 +138,7 @@ export const GameProvider: React.FC<GameProviderProps> = ({ children }) => {
setOnlyFalse(newOnlyFalse)
}
const setWinnerData = (winner: Player) =>{
const setWinnerData = (winner: Player | null) =>{
setWinner(winner)
}
@ -142,7 +172,7 @@ export const GameProvider: React.FC<GameProviderProps> = ({ children }) => {
}
return (
<GameContext.Provider value={{ indices, setIndicesData, indice, setIndiceData, person, setPersonData, personNetwork, setPersonNetworkData, players, setPlayersData, nodeId, setNodeIdData, askedPersons, setAskedPersonsData, actualPlayerIndex, setActualPlayerIndexData, turnPlayerIndex, setTurnPlayerIndexData, room, setRoomData, onlyFalse, setOnlyFalseData, winner, setWinnerData, reset, dailyEnigme, setDailyEnigmeData, nbCoup, setNbCoupData, temps, settempsData}}>
<GameContext.Provider value={{ indices, setIndicesData, indice, setIndiceData, person, setPersonData, personNetwork, setPersonNetworkData, players, setPlayersData, nodeId, setNodeIdData, askedPersons, setAskedPersonsData, actualPlayerIndex, setActualPlayerIndexData, turnPlayerIndex, setTurnPlayerIndexData, room, setRoomData, onlyFalse, setOnlyFalseData, winner, setWinnerData, reset, dailyEnigme, setDailyEnigmeData, nbCoup, setNbCoupData, temps, settempsData, setNetworkDataData, networkData, seed, setSeedData, nodesC, setNodesData, gameStart, setGameStartData}}>
{children}
</GameContext.Provider>
);

@ -0,0 +1,67 @@
import React, { Component, ErrorInfo, ReactNode } from 'react';
import ErrorPage from './ErrorPage';
interface ErrorBoundaryProps {
fallback: (error: Error, errorInfo: ErrorInfo) => ReactNode;
children: ReactNode;
}
interface ErrorBoundaryState {
hasError: boolean;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = {
hasError: false,
};
}
static getDerivedStateFromError(): ErrorBoundaryState {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
console.error('Error caught by ErrorBoundary:', error, errorInfo);
}
render(): ReactNode {
if (this.state.hasError) {
return this.props.fallback(new Error('Error caught by ErrorBoundary'), {});
}
return this.props.children;
}
}
export default ErrorBoundary;
// interface ErrorBoundaryProps {
// children: ReactNode;
// }
// class ErrorBoundary extends React.Component <ErrorBoundaryProps>{
// state = { hasError : true }
// //@ts-ignore
// static getDerivedStateFromError(error){
// return { hasError : true};
// }
// componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
// console.log(error, errorInfo);
// }
// render(){
// if (this.state.hasError){
// return <ErrorPage/>;
// }
// return this.props.children;
// }
// }
// export default ErrorBoundary;

@ -0,0 +1,37 @@
import React from 'react';
import { useTheme } from '../Style/ThemeContext';
import { Link } from 'react-router-dom';
import './ErrorStyle.css';
import { FormattedMessage } from 'react-intl';
import { Button } from 'react-bootstrap';
const basePath = process.env.REACT_APP_BASE_PATH || '/containers/Crypteam-website';
//@ts-ignore
function ErrorPage({ code = "", msg = "Something is wrong"}) {
const theme = useTheme();
return (
<div className='mainErrorDiv'>
<div className='titleError'>
<div>
<h1>ERROR</h1>
<hr style={{width:"100%"}}/>
</div>
{ code != "" &&
<h1 style={{color:'darkred', margin:'10px'}}>{code}</h1>
}
<h2>{msg}</h2>
</div>
<div className='centerDivH' style={{margin: "20px"}}>
<Button href={`${basePath}/`} variant='danger'><FormattedMessage id='BackHome'/></Button>
</div>
</div>
);
}
export default ErrorPage;

@ -0,0 +1,29 @@
.mainErrorDiv{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.titleError{
border: solid 2px #C70039;
border-radius: 10px;
margin: 15px;
padding: 10px;
box-shadow: 5px 5px 5px #900C3F;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 400px;
height: 250px;
}
.titleError h1 {
color: #900C3F;
margin: 10px !important;
}

@ -11,6 +11,8 @@ import Person from "./model/Person";
import PersonNetwork from "./model/PersonsNetwork";
import Player from "./model/Player";
import User from "./model/User";
import NodePerson from "./model/Graph/NodePerson";
import Font from "./model/Graph/Font";
class JSONParser{
@ -86,13 +88,22 @@ class JSONParser{
static JSONToPlayer(json: any): Player{
switch (json.type){
case "User":
return new User(json.id, json.pseudo, json.profilePicture, json.soloStats, json.onlineStats)
return new User(json.id, json.pseudo, json.profilePicture, json.mastermindStats, json.easyEnigmaStats, json.mediumEnigmaStats ,json.hardEnigmaStatsS, json.onlineStats)
case "EasyBot":
return new EasyBot(json.id, json.pseudo, json.profilePicture)
default:
throw new Error("PARSER unable to parse player: " + json.type);
}
}
static JSONToNodePersons(json: any): NodePerson[]{
const tmpTab: NodePerson[] = []
json.forEach((element: any) => {
tmpTab.push(new NodePerson(element.id, element.label, element.color, new Font(element.font.color, element.font.size, element.font.align), element.shape))
});
return tmpTab
}
}
export default JSONParser

@ -0,0 +1,120 @@
import React from 'react';
/* Style */
import './DeducGrid.css';
import { useTheme } from '../Style/ThemeContext';
/* Component */
/* Boostrap */
import Table from 'react-bootstrap/Table';
import Form from 'react-bootstrap/Form';
/* lang */
import { FormattedMessage } from 'react-intl';
/* model */
import Stub from '../model/Stub';
import { useGame } from '../Contexts/GameContext';
import { positionToEmoji } from '../ColorHelper';
function DeducCheck({lang}: {lang: string}) {
const theme = useTheme();
//const indices = Stub.GenerateIndice();
const params = new URLSearchParams(window.location.search);
const NbPlayer = params.get('nbPlayer');
const actualPlayerIndex = params.get('actualId') ?? '0';
//const { actualPlayerIndex, players } = useGame();
// let playerstmp
// if (players.length == 0) playerstmp = ["1", "2", "3", "4", "5", "4"];
// else { playerstmp = players}
//* Gestion players
const playerList = Array.from({ length: parseInt(NbPlayer ?? '1') }, (_, index) => (index + 1).toString());
const playerColors = playerList.map((_, index) => positionToEmoji(index, true));
const players = playerColors.filter((_, index) => index !== parseInt(actualPlayerIndex ?? '0'));
// const players = [
// "🔵",
// "🟢",
// "🟡",
// "🟣",
// "🔴"
//]
//console.log(players)
// console.log(playerColors)
// console.log(actualPlayerIndex)
//* Gestion indices
const indices = Stub.GenerateIndice();
const halfLength = Math.ceil(indices.length / 2);
const firstHalfIndices = indices.slice(0, halfLength);
const secondHalfIndices = indices.slice(halfLength);
return (
<div style={{ margin: '20px', display: 'flex', flexDirection: 'column' }}>
<div style={{ display: 'flex', flexDirection: 'row', overflowX: 'auto' }}>
{/* Premier tableau */}
<Table striped bordered hover style={{ marginRight: '20px' }}>
<thead>
<tr>
<th>Indices</th>
{players.map((player, index) => (
<th key={index}>{player}</th>
))}
</tr>
</thead>
<tbody>
{firstHalfIndices.map((indice, rowIndex) => (
<tr key={rowIndex}>
<td>{indice.ToString(lang)}</td>
{players.map((player, colIndex) => (
<td key={colIndex}>
{/* <input type="checkbox"/> */}
<Form.Check aria-label="option 1" />
</td>
))}
</tr>
))}
</tbody>
</Table>
{/* Deuxième tableau */}
<Table striped bordered hover>
<thead>
<tr>
<th>Indices</th>
{players.map((player, index) => (
<th key={index}>{player}</th>
))}
</tr>
</thead>
<tbody>
{secondHalfIndices.map((indice, rowIndex) => (
<tr key={rowIndex}>
<td>{indice.ToString(lang)}</td>
{players.map((player, colIndex) => (
<td key={colIndex}>
{/* <input type="checkbox"/> */}
<Form.Check aria-label="option 1" />
</td>
))}
</tr>
))}
</tbody>
</Table>
</div>
</div>
);
}
export default DeducCheck;

@ -0,0 +1,17 @@
.case{
min-width: 50px;
min-height: 50px;
border: solid 1px whitesmoke;
}
.deducDiv{
display: flex;
flex-direction: column;
}
.sectionAccordion{
display: flex;
flex-direction: row;
justify-content: space-around;
}

@ -0,0 +1,79 @@
import React from 'react';
/* Style */
import './DeducGrid.css';
import { useTheme } from '../Style/ThemeContext';
/* Component */
/* Boostrap */
import Accordion from 'react-bootstrap/Accordion';
import Table from 'react-bootstrap/Table';
import Tab from 'react-bootstrap/Tab';
import Tabs from 'react-bootstrap/Tabs';
/* nav */
import { Link } from 'react-router-dom';
/* lang */
import { FormattedMessage } from 'react-intl';
import Case from '../Components/CheckCase';
import Age from '../model/Indices/AgeIndice'
import Stub from '../model/Stub';
import Edge from '../model/Graph/Edge';
import EdgesIndice from '../model/Indices/EdgesIndice';
import AccordionIndice from '../Components/AccordionIndice';
import AgeIndice from '../model/Indices/AgeIndice';
import ColorIndice from '../model/Indices/ColorIndice';
import ColorEdgesIndice from '../model/Indices/ColorEdgesIndice';
import SportIndice from '../model/Indices/SportIndice';
import NbEdgesIndice from '../model/Indices/NbEdgesIndice';
import NbSportIndice from '../model/Indices/NbSportIndice';
import { useGame } from '../Contexts/GameContext';
function DeducGrid() {
const theme = useTheme();
//const indices = Stub.GenerateIndice();
// const { players } = useGame();
const players = [
"bla",
"bli",
"blou"
]
console.log(players)
return (
<div style={{margin:'20px'}}>
<Tabs defaultActiveKey="0" id="uncontrolled-tab-example" className="mb-3">
{players.map((joueur, index) => (
<Tab key={index} eventKey={index.toString()} title={`${joueur} ${index + 1}`}>
<div className='deducDiv'>
<div className='sectionAccordion'>
<AccordionIndice instance={AgeIndice} head='Age' lang='fr'/>
</div>
<div className='sectionAccordion'>
<AccordionIndice instance={ColorIndice} head='Couleur de cheveux' lang='fr'/>
<AccordionIndice instance={ColorEdgesIndice} head='Couleur de cheveux voisine' lang='fr'/>
</div>
<div className='sectionAccordion'>
<AccordionIndice instance={SportIndice} head='Sport' lang='fr'/>
<AccordionIndice instance={NbSportIndice} head='Nombre de Sport' lang='fr'/>
</div>
<div className='sectionAccordion'>
<AccordionIndice instance={EdgesIndice} head='Caractéristique des voisin' lang='fr'/>
<AccordionIndice instance={NbEdgesIndice} head='Nombre de voisin' lang='fr'/>
</div>
</div>
</Tab>
))}
</Tabs>
</div>
);
}
export default DeducGrid;

@ -1,117 +1,90 @@
.head{
/* Styles for the Winner and Indice section */
.head {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.leaderboard-header{
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
width: fit-content;
border-radius: 0px 0px 30px 30px;
border: solid;
border-width: 0 5px;
}
.leaderboard-header {
border: 2px solid;
border-color: #0064E0;
padding: 20px;
}
.bottomEnd{
text-align: center;
border-radius: 0px 0px 10px 10px;
}
/* Styles for the Winner's details */
.winner-details {
display: flex;
justify-content: space-around;
}
.winner{
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.playersContainer {
justify-content: center;
}
.winner-details img {
width: 75px;
height: 75px;
border-radius: 50%;
margin-right: 10px;
}
/* Styles for the Indice Display */
.indiceDisplay {
border: 2px solid whitesmoke;
border-radius: 10px;
margin: 5px; /* Réduire la marge */
padding: 8px; /* Réduire le rembourrage */
box-shadow: 3px 3px 3px rgb(246, 246, 246); /* Réduire l'ombre */
}
/* Styles for the Losing Players Container */
.losingPlayersContainer {
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
margin: 5px;
}
/* Styles for individual Player Containers */
.playerContainer {
display: flex;
flex-direction: column;
align-items: center;
/* padding-left: "5px"; */
width: 100px;
background-color: red;
}
.playerContainer{
/* display: flex;
align-items: center; */
/* flex-direction: column; */
/* width: 300px; */
width: 30%;
margin-bottom: 20px;
/* margin-bottom: 10px; */
border: solid 1px whitesmoke;
margin-top: 30px;
margin-bottom: 30px;
border: 1px solid whitesmoke;
border-radius: 15px;
background-color: white;
}
}
.losingPlayersContainer{
.soloContainer {
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
margin: 10px 0;
max-width: 50%;
overflow-y: scroll;
max-height: 200px;
/* background-color: yellow; */
}
.indiceDisplay{
border: solid 2px whitesmoke;
border-radius: 10px;
margin: 0 15px 0 15px;
padding: 10px;
box-shadow: 5px 5px 5px rgb(246, 246, 246);
}
.SoloContainer{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%; /* Ajoutez cette ligne pour occuper toute la largeur */
}
.solostat{
border: solid 1px whitesmoke;
border-radius: 15px;
background-color: white;
padding: 10px;
margin: 5px;
width: fit-content; /* Ajoutez cette ligne pour ajuster la largeur au contenu */
}
max-width: 50%;
}
.indicesolo{
.center {
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
justify-content: center;
flex-direction: column;
align-items: center;
}
/* margin: 10px 0; */
/* max-height: 200px; */
}
.solostat{
display: flex;
justify-content: space-between;
width: 70%;
}
.solostat p {
border: solid 1px whitesmoke;
border-radius: 15px;
background-color: white;
padding: 10px;
}
/* Styles for the Graph Container */
#vis-graph {
height: 500px;
margin: 5px;
border: 2px solid #ccc;
overflow: hidden;
}

@ -1,4 +1,4 @@
import React from 'react';
import React, {useEffect, useState} from 'react';
/* Style */
@ -11,24 +11,79 @@ import Person from '../res/img/Person.png';
import Leave from '../res/icon/leave.png';
import Replay from '../res/icon/replay.png';
/* sound */
import WinSound from '../res/Audio/win.wav';
/* Component */
import PersonStatus from '../Components/PersonStatus';
import ButtonImgNav from '../Components/ButtonImgNav';
import BigButtonNav from '../Components/BigButtonNav';
//
import { Network } from "vis-network/standalone/esm/vis-network";
import { map } from 'lodash';
/* nav */
/* Nav */
import { Link } from 'react-router-dom';
/* lang */
/* Lang */
import { FormattedMessage } from 'react-intl';
import { useGame } from '../Contexts/GameContext';
import { map } from 'lodash';
/* Model */
import Player from '../model/Player';
/* Context */
import { useGame } from '../Contexts/GameContext';
function EndGame() {
/* Boostrap */
import { Button, Col, Container, Row } from 'react-bootstrap';
import Bot from '../model/Bot';
import {basePath} from "../AdressSetup"
function EndGame({lang}: {lang: string}) {
const {networkData, seed} = useGame();
const params = new URLSearchParams(window.location.search);
const initialOptions = {
layout: {
improvedLayout: true,
randomSeed: seed,
hierarchical: {
enabled: false,
direction: 'LR',
sortMethod: 'hubsize'
},
//randomSeed: 2
},
physics: {
enabled: true,
barnesHut: {
gravitationalConstant: -1000,
springConstant: 0.001,
springLength: 100
}
}
};
useEffect(() => {
handleWinSound();
}, []);
useEffect(() => {
const container = document.getElementById("vis-graph");
if (!container) {
console.error("Container not found");
return;
}
const network = new Network(container, networkData, initialOptions);
console.log(networkData)
}, []);
//* Gestion solo
let IsSolo: boolean = false
@ -73,81 +128,209 @@ function EndGame() {
}
const theme = useTheme();
console.log(winner)
console.log(indices)
let indicenull = false;
if (indices.length == 0){
indicenull = true;
}
//* Sound
const [playTurnSound, setPlayTurnSound] = useState(false);
const handleWinSound = () => {
setTimeout(() => { // on attend 1s avant de jouer le son
setPlayTurnSound(true);
}, 300);
setTimeout(() => { // on attend 2s avant de remettre le son à false
setPlayTurnSound(false);
}, 2000);
};
return (
<div>
<div style={{overflow:"hidden"}}>
{playTurnSound && <audio src={WinSound} autoPlay />}
{!IsSolo ? (
<div>
<div className="head">
<header className='leaderboard-header' style={{ borderColor: theme.colors.primary }}>
<h1>{winner?.pseudo} a gagné !</h1>
<h3>Le tueur était <u>{person?.getName()}</u></h3>
</header>
</div>
<div className='winner'>
<img src={Person} width='250' height='250'/>
<h3 className='indiceDisplay'>{indices[players.findIndex((p) => p.id == winner?.id)].ToString("fr")}</h3>
</div>
<div className='bottomEnd'>
<div className='centerDivH' onClick={resetAll}>
<BigButtonNav dest="/play" img={Leave}/>
</div>
<div className="losingPlayersContainer">
{losingPlayers.map((player, index) => (
<div className="playerContainer" key={index}>
{player.id !== winner?.id && (
<div>
<PersonStatus img={Person} state={Person} key={index} name={player.pseudo} playerTouched={1} setPlayerTouched={() => {}} index={index} showCircle={false}/>
<h6 className='indiceDisplay'>{indices[players.findIndex((p) => p.id == player?.id)].ToString("fr")}</h6>
<Container>
{/* Winner et son indice */}
<Row>
<Col>
<div className="losingPlayersContainer">
{/* Indices pairs */}
{players.map((player, index) => (
index % 2 == 0 && (
<div className="playerContainer" key={index}>
<PersonStatus img={Person} state={Person} key={index} name={player.pseudo} playerTouched={1} setPlayerTouched={() => {}} index={index} playerIndex={-2} showCircle={false} askedWrong={false}/>
{!indicenull && (<h6 className='indiceDisplay'>{indices[players.findIndex((p) => p.id == player?.id)].ToString(lang)}</h6>)}
</div>
)}
)
))}
</div>
</Col>
<Col xs={6}>
<Row>
<div className="head">
<header className='leaderboard-header' style={{ borderColor: theme.colors.primary }}>
<h4>{winner?.pseudo} a gagné !</h4>
<h5>Le coupable était <u>{person?.getName()}</u></h5>
</header>
</div>
))}
</div>
<div className='centerDivH'>
<BigButtonNav dest="/lobby" img={Replay}/>
</div>
</div>
</div>
</Row>
<Row>
<div id="vis-graph"></div>
</Row>
<Row className="justify-content-md-center">
<Button href={`${basePath}/`} style={{
width:"50%",
margin:"10px"
}}>Retour à l'accueil</Button>
</Row>
</Col>
<Col>
<div className="losingPlayersContainer">
{players.map((player, index) => (
index % 2 == 1 && (
<div className="playerContainer" key={index}>
<PersonStatus img={Person} state={Person} key={index} name={player.pseudo} playerTouched={1} setPlayerTouched={() => {}} index={index} playerIndex={-2} showCircle={false} askedWrong={false}/>
{!indicenull && (<h6 className='indiceDisplay'>{indices[players.findIndex((p) => p.id == player?.id)].ToString(lang)}</h6>)}
</div>
)
))}
</div>
</Col>
</Row>
</Container>
): (
<div>
<div className="head">
<header className='leaderboard-header' style={{ borderColor: theme.colors.primary }}>
<h1>Vous avez gagné !</h1>
<h3>Le tueur était <u>{person?.getName()}</u></h3>
</header>
</div>
<div className='winner'>
<img src={Person} width='250' height='250'/>
<h1>{winner?.pseudo}</h1>
</div>
<div className='bottomEnd'>
<div className='centerDivH' onClick={resetAll}>
<BigButtonNav dest="/play" img={Leave}/>
</div>
<div className="SoloContainer">
<div className='solostat'>
{!IsDaily && <p>Nombre de coups : {nbCoup}</p> }
<p>Temps : {temps}s</p>
</div>
<div className='indicesolo'>
{indices.map((indice, index) => (
// <div className="playerContainer" key={index}>
<div>
<h6 className='indiceDisplay'> <u>Indice {index+1}</u> : {indice.ToString("fr")}</h6>
</div>
//</div>
))
}
</div>
</div>
<div className='centerDivH'>
<BigButtonNav dest="/lobby" img={Replay}/>
</div>
</div>
</div>
<Container fluid>
{/* Perd une énigme */}
{!winner && (
<Row>
<Col>
<Row>
<div className="head">
<header className='leaderboard-header' style={{ borderColor: theme.colors.primary }}>
<h4>Vous avez perdu !</h4>
<h5>Le coupable était <u>{person?.getName()}</u></h5>
</header>
</div>
<Row>
{!IsDaily &&
<Col className='center'>
<p className='solostat'>Nombre de coups : {nbCoup}</p>
</Col>
}
<Col className='center'>
<p className='solostat'>Temps : {temps}s</p>
</Col>
</Row>
</Row>
<Row>
<Col>
{indices.map((indice, index) => (
index % 2 == 0 && (
<div className='playerContainer'>
<h6 className='indiceDisplay'> <u>Indice {index+1}</u> : {indice.ToString("fr")}</h6>
</div>
)
))}
</Col>
<Col xs={6}>
<div id="vis-graph"></div>
</Col>
<Col>
<div className="losingPlayersContainer">
{indices.map((indice, index) => (
index % 2 == 1 && (
<div className='playerContainer'>
<h6 className='indiceDisplay'> <u>Indice {index+1}</u> : {indice.ToString("fr")}</h6>
</div>
)
))}
</div>
</Col>
</Row>
<Row className="justify-content-md-center">
<Button href={`${basePath}/`} style={{
width:"50%",
margin:"10px"
}}>Retour à l'accueil</Button>
</Row>
</Col>
</Row>
)}
{/* Gagne une énigme */}
{winner && (
<Row>
<Col>
<Row>
<div className="head">
<header className='leaderboard-header' style={{ borderColor: theme.colors.primary }}>
<h4>Vous avez gagné !</h4>
<h5>Le coupable était <u>{person?.getName()}</u></h5>
</header>
</div>
<Row>
{!IsDaily &&
<Col className='center'>
<p className='solostat'>Nombre de coups : {nbCoup}</p>
</Col>
}
<Col className='center'>
<p className='solostat'>Temps : {temps}s</p>
</Col>
</Row>
</Row>
<Row>
<Col>
{indices.map((indice, index) => (
index % 2 == 0 && (
<div className='playerContainer'>
<h6 className='indiceDisplay'> <u>Indice {index+1}</u> : {indice.ToString("fr")}</h6>
</div>
)
))}
</Col>
<Col xs={6}>
<div id="vis-graph"></div>
</Col>
<Col>
<div className="losingPlayersContainer">
{indices.map((indice, index) => (
index % 2 == 1 && (
<div className='playerContainer'>
<h6 className='indiceDisplay'> <u>Indice {index+1}</u> : {indice.ToString("fr")}</h6>
</div>
)
))}
</div>
</Col>
</Row>
<Row className='justify-content-center'>
<Button href={`${basePath}/`} style={{
width:"50%",
margin:"10px"
}}>Retour à l'accueil</Button>
</Row>
</Col>
</Row>
)}
</Container>
)}
</div>
);

@ -3,8 +3,6 @@
justify-content: space-between;
/* background-color: #D7D4C6; */
background-color: #fff;
min-height: 100vh;
display: flex;
font-size: calc(10px + 2vmin);
/* color: #2A4541; */
}

@ -9,6 +9,10 @@ import { FormattedMessage } from 'react-intl';
import ButtonImgNav from '../Components/ButtonImgNav';
import defaultImg from "../res/img/Person.png"
import {loadImageAsync} from "../ImageHelper"
import { socket } from '../SocketConfig';
import JSONParser from '../JSONParser';
import {basePath} from "../AdressSetup"
// @ts-ignore
@ -36,77 +40,91 @@ function Home() {
}
}, [isLoggedIn]);
return (
<div className="home-container">
return (
<div className="home-container" style={{overflow:"hidden"}}>
<div className="left-section">
<div>
{/* <h2><FormattedMessage id="home.histoire.title"/></h2> */}
<h2>Introduction</h2>
<p style={{fontSize:"14px"}}>
{/* <FormattedMessage id="home.histoire" /> */}
Bienvenue dans notre jeu de déduction captivant, où l'intrigue et la malice se rejoignent dans une aventure palpitante !
Plongez-vous dans un monde de mystère et d'intrigue, où chaque interaction compte, et chaque indice vous rapproche de la vérité.
Imaginez un graphique complexe où chaque sommet représente une personne, chaque axe une relation, et chaque détail compte.
Vous êtes plongé dans un défi stimulant pour découvrir qui parmi ces individus est le mystérieux coupable.
Chaque joueur détient un indice crucial, et seul le partage stratégique de ces indices vous mènera à la résolution du mystère.
Explorez notre page de règles pour comprendre les subtilités du jeu, découvrez les indices qui peuvent vous guider, et élaborez des stratégies intelligentes pour identifier le coupable.
Manipuler vos amis, afin d'être le premier à découvrir qui est le meurtrier !
Êtes-vous prêt à relever le défi et à démasquer le coupable caché dans le graphe ? Que l'enquête commence !
</p>
</div>
<div className="left-section">
<hr/>
<div>
{/* <h2><FormattedMessage id="home.histoire.title"/></h2> */}
<h2>Introduction</h2>
<p>
{/* <FormattedMessage id="home.histoire" /> */}
Bienvenue dans notre jeu de déduction captivant, où l'intrigue et la malice se rejoignent dans une aventure palpitante ! Plongez-vous dans un monde de mystère et d'intrigue, où chaque interaction compte, et chaque indice vous rapproche de la vérité.Imaginez un graphique complexe où chaque sommet représente une personne, chaque axe une relation, et chaque détail compte. Vous êtes plongé dans un défi stimulant pour découvrir qui parmi ces individus est le mystérieux tueur. Chaque joueur détient un indice crucial, et seul le partage stratégique de ces indices vous mènera à la résolution du mystère. Explorez notre page de règles pour comprendre les subtilités du jeu, découvrez les indices qui peuvent vous guider, et élaborez des stratégies intelligentes pour identifier le coupable. Manipuler vos amis, afin d'être le premier à découvrir qui est le meurtrier ! Êtes-vous prêt à relever le défi et à démasquer le tueur caché dans le graphe ? Que l'enquête commence !
</p>
</div>
<div>
<h2><FormattedMessage id="home.jeu.title"/></h2>
<p>
{/* <FormattedMessage id="home.jeu" /> */}
Dans l'univers captivant de notre jeu de déduction, la tromperie et la ruse sont les maîtres mots de la réussite. Explorez le mystère qui se dissimule derrière chaque interaction de notre graphique complexe, dévoilant les liens entre les individus.
<div>
<h2><FormattedMessage id="home.jeu.title"/></h2>
<p style={{fontSize:"14px"}}>
{/* <FormattedMessage id="home.jeu" /> */}
Dans l'univers captivant de notre jeu de déduction, la tromperie et la ruse sont les maîtres mots de la réussite. Explorez le mystère qui se dissimule derrière chaque interaction de notre graphique complexe, dévoilant les liens entre les individus.
</p>
Votre mission ultime ? Découvrir qui parmi les individus est le coupable, mais n'attendez pas une collaboration ouverte. Utilisez plutôt la manipulation subtile pour embrouiller les pistes, détourner l'attention de vos adversaires. Posez des questions stratégiques, répondez avec malice et plantez des indices trompeurs pour vous rapprocher du dénouement.
<p style={{fontSize:"14px"}}>
{/* <FormattedMessage id="home.jeu" /> */}
Votre mission ultime ? Découvrir qui parmi les individus est le coupable, mais n'attendez pas une collaboration ouverte. Utilisez plutôt la manipulation subtile pour embrouiller les pistes, détourner l'attention de vos adversaires. Posez des questions stratégiques, répondez avec malice et plantez des indices trompeurs pour vous rapprocher du dénouement.
</p>
Chaque occasion offre la possibilité de semer le doute parmi vos adversaires. Lorsqu'un joueur vous interroge, répondez en plaçant adroitement un jeton carré pour suggérer que "selon votre indice, cette personne ne peut être le coupable", ou un jeton rond pour indiquer qu'elle reste dans la liste des suspects. Soyez vigilant, chaque geste peut être interprété, et la vérité se dissimule souvent derrière une façade d'indices trompeurs.
<p style={{fontSize:"14px"}}>
{/* <FormattedMessage id="home.jeu" /> */}
Chaque occasion offre la possibilité de semer le doute parmi vos adversaires. Lorsqu'un joueur vous interroge, répondez en plaçant adroitement un jeton carré pour suggérer que "selon votre indice, cette personne ne peut être le coupable", ou un jeton rond pour indiquer qu'elle reste dans la liste des suspects. Soyez vigilant, chaque geste peut être interprété, et la vérité se dissimule souvent derrière une façade d'indices trompeurs.
</p>
Si un joueur place un jeton carré, le questionneur doit également jouer son jeu en plaçant un jeton carré de sa couleur sur un nœud du graphique. La contre-manipulation devient ainsi une arme redoutable pour détourner l'accusation et semer la confusion. Pour en savoir plus, plongez-vous dans les détails de ce passionnant récit sur une autre page.
</p>
<br/>
<p>Pour des informations plus détaillées, consulter les <Link to="/info">règles</Link>.</p>
<br/>
<p style={{fontSize:"14px"}}>
{/* <FormattedMessage id="home.jeu" /> */}
Si un joueur place un jeton carré, le questionneur doit également jouer son jeu en plaçant un jeton carré de sa couleur sur un nœud du graphique. La contre-manipulation devient ainsi une arme redoutable pour détourner l'accusation et semer la confusion. Pour en savoir plus, plongez-vous dans les détails de ce passionnant récit sur une autre page.
</p>
<h5>Pour des informations plus détaillées, consulter les <Link to={`${basePath}/info`}>règles</Link>.</h5>
<hr/>
</div>
<div>
<h6>Jeu inspiré par le jeu de société "Cryptide"</h6>
</div>
</div>
<div>
{/* <h2><FormattedMessage id="home.plus.title"/></h2>
<ul>
<li><FormattedMessage id="home.plus.1"/></li>
<li><FormattedMessage id="home.plus.2"/></li>
<li><FormattedMessage id="home.plus.3"/></li>
</ul> */}
<h2>Présentation Video :</h2>
(ici il y aura la vidéo)
<div className="vertical-divider"></div>
<div className="right-section">
{/* <h3><FormattedMessage id="game.time"/></h3>
<h3><FormattedMessage id="game.players"/></h3>
<h3><FormattedMessage id="game.age"/></h3> */}
<h3>
Temps : 20 minutes<br/>
Joueurs : 1 à 6<br/>
Âge : 8ans et +<br/>
</h3>
<hr/>
<h3>
<u><FormattedMessage id="game.createdBy"/></u><br/>
Chazot Thomas<br/>
Ferreira Pierre<br/>
Marcel Baptiste<br/>
</h3>
{/* <h3> <u><FormattedMessage id="game.illustratedBy"/></u><br/> Kwanchai Moriya</h3> */}
{/* <button>Jouer au jeu</button> */}
<br/>
<Link to={`${basePath}/`} className='button'
style={{
backgroundColor: theme.colors.primary,
borderColor: theme.colors.secondary
}}>
<FormattedMessage id="play"/>
</Link>
</div>
</div>
<div className="vertical-divider"></div>
<div className="right-section">
{/* <h3><FormattedMessage id="game.time"/></h3>
<h3><FormattedMessage id="game.players"/></h3>
<h3><FormattedMessage id="game.age"/></h3> */}
<h3>
Temps : 20 minutes<br/>
Joueurs : 1 à 6<br/>
Âge : 8ans et +<br/>
</h3>
<hr/>
<h3> <u><FormattedMessage id="game.createdBy"/></u><br/>
Chazot Thomas<br/>
Ferreira Pierre<br/>
Marcel Baptiste<br/>
</h3>
{/* <h3> <u><FormattedMessage id="game.illustratedBy"/></u><br/> Kwanchai Moriya</h3> */}
{/* <button>Jouer au jeu</button> */}
<br/>
<Link to="/play" className='button'
style={{
backgroundColor: theme.colors.primary,
borderColor: theme.colors.secondary
}}>
<FormattedMessage id="play"/>
</Link>
</div>
</div>
);
}

@ -72,8 +72,8 @@
.playerlistDiv{
position: absolute;
z-index: 1;
left: 10px;
top :50px;
left: 30px;
top : 30px;
}
.nbLaps{ /*nombre de tour*/
@ -137,21 +137,43 @@
/** Historique*/
.historique{
.historique {
position: absolute;
z-index: 1;
bottom: 2%;
left: 2%;
display: flex;
flex-direction: column;
/* justify-content: end; */
padding: 15px 25px;
background-color: #d9d9d9;
border-radius: 15px;
border: solid;
border-width: 0.5px 0.5px 5px 0.5px;
border-color: whitesmoke lightgray gray whitesmoke;
border-radius: 0 5% 15px 0;
height: 250px;
width: 20%;
overflow-y:auto;
direction: rtl;
overflow-y: auto;
}
.historique div{
direction: ltr;
}
.historique::-webkit-scrollbar {
width: 10px;
}
.historique::-webkit-scrollbar-thumb {
background-color: lightgray;
border-radius: 5px;
}
.historique::-webkit-scrollbar-track {
background-color: whitesmoke;
}
.historique::-webkit-scrollbar-corner {
background-color: whitesmoke;
}

@ -14,20 +14,24 @@ import PlayerList from '../Components/PlayerList';
import TurnBar from '../Components/TurnBar';
/* Icon */
import Leave from "../res/icon/leave.png";
import Param from "../res/icon/param.png";
import Replay from "../res/icon/replay.png";
import Info from "../res/icon/infoGreen.png";
import Check from "../res/icon/checkboxGreen.png";
import Alpha from "../res/GreekLetters/alphaW.png";
import MGlass from "../res/icon/magnifying-glass.png";
import Download from "../res/icon/download.png"
import Pdf from "../res/icon/pdf.png"
import Tex from "../res/icon/tex.png"
import Reset from "../res/icon/reset.png";
import Oeye from "../res/icon/eye.png";
import Ceye from "../res/icon/hidden.png";
import JSZip from 'jszip';
/* Sound */
import turnSound from "../res/Audio/turn.mp3";
import winSound from "../res/Audio/win.wav";
/* nav */
import { Link } from 'react-router-dom';
import { Link, Navigate, useNavigate, useNavigationType } from 'react-router-dom';
/* Boostrap */
import Button from 'react-bootstrap/Button';
@ -45,15 +49,39 @@ import { NavLink } from 'react-router-dom';
import { last } from 'lodash';
import { socket } from '../SocketConfig';
import { Network } from 'vis-network';
import generateLatexCode from '../Script/LatexScript';
import {generateLatexCode, generateLatexCodeEnigme} from '../Script/LatexScript';
import Pair from '../model/Pair';
import Indice from '../model/Indices/Indice';
import {basePath} from "../AdressSetup"
import { useAuth } from '../Contexts/AuthContext';
import ballonDeBasket from '../Script/ballon-de-basket.png';
import ballonDeFoot from '../Script/ballon-de-foot.png';
import baseball from '../Script/baseball.png';
import bowling from '../Script/bowling.png';
import tennis from '../Script/tennis.png';
let cptNavigation = 0
//@ts-ignore
const InGame = ({locale, changeLocale}) => {
const theme = useTheme();
const navigate = useNavigate()
const {user, manager} = useAuth()
const params = new URLSearchParams(window.location.search);
const navigationType = useNavigationType()
cptNavigation++
if (cptNavigation % 2 == 0){
if (navigationType.toString() == "POP"){
socket.emit("player quit")
navigate(`${basePath}/`)
}
}
//* Gestion solo
let IsSolo: boolean = true
@ -69,14 +97,51 @@ const InGame = ({locale, changeLocale}) => {
isDaily=false
}
let isEasy: boolean = true
const isEasytmp = params.get('easy');
if (isEasytmp == "false"){
isEasy=false
let difficulty: string = "";
let difficultyTmp = params.get('difficulty')
if (difficultyTmp !== null){
difficulty=difficultyTmp
}
//* Historique
const [history, setHistory] = useState<string[]>([]);
const [showLast, setShowLast] = useState(false)
const [askedWrong, setAskedWrong] = useState(false)
const [importToPdf, setImportToPdf] = useState(false)
const [importToJSON, setImportToJSON] = useState(false)
const [firstHistory, setFirstHistory] = useState(true)
const [putCorrectBackground, setPutCorrectBackground] = useState<() => void>(() => {});
const [putGreyBackgroud, setPutGreyBackground] = useState<() => void>(() => {});
const [putImposssibleGrey, setPutImposssibleGrey] = useState<() => void>(() => {});
const [changeGraph, setChangeGraph] = useState<(nbNodes: number, nbIndices: number) => void>(() => {});
const setPutCorrectBackgroundData = (func: () => void) => {
setPutCorrectBackground(func)
}
const setPutGreyBackgroundData = (func: () => void) => {
setPutGreyBackground(func)
}
const setPutImposssibleGreyData = (func: () => void) => {
setPutImposssibleGrey(func)
}
const setChangeGraphData = (func: (nbNodes: number, nbIndices: number) => void) => {
setChangeGraph(func)
}
const setImportToJSONData = (imp: boolean) => {
setImportToJSON(imp)
}
const setImportToPdfData = (imp: boolean) => {
setImportToPdf(imp)
}
// Fonction pour ajouter un élément à l'historique
const addToHistory = (message: string) => {
@ -88,6 +153,10 @@ const InGame = ({locale, changeLocale}) => {
setShowLast(!showLast);
}
const setAskedWrongData = (askedWrong: boolean) => {
setAskedWrong(askedWrong)
}
useEffect(() => {
const historyContainer = document.getElementById('history-container');
if (historyContainer) {
@ -102,13 +171,20 @@ const InGame = ({locale, changeLocale}) => {
const [showTurnBar, setShowTurnBar] = useState(false);
const [turnBarText, setTurnBarText] = useState("");
const [playerTouched, setPlayerTouched] = useState(-2)
const [playerIndex, setPlayerIndex] = useState(-2)
const [network, setNetwork] = useState<Network | null>(null)
const [networkEnigme, setNetworkEnigme] = useState<Map<number, Pair<Indice, boolean>[]> | null>(null)
const setNetworkData = (network: Network) => {
setNetwork(network)
}
const setNetworkEnigmeData = (networkEnigme: Map<number, Pair<Indice, boolean>[]>) => {
setNetworkEnigme(networkEnigme)
}
const handleNodeClick = (shouldShowChoiceBar: boolean) => {
setShowChoiceBar(shouldShowChoiceBar);
};
@ -126,13 +202,55 @@ const InGame = ({locale, changeLocale}) => {
setTurnBarText(newTurnBarText)
}
const generateTEX = () => {
const setPlayerIndexData = (playerIndex: number) => {
setPlayerIndex(playerIndex)
}
const generateTEX = async () => {
if (network != null && personNetwork != null && person != null){
const tex = generateLatexCode(personNetwork, person, indices, network)
const blob = new Blob([tex], { type: 'application/x-latex;charset=utf-8' });
const zip = new JSZip();
if (isDaily && (difficulty === "hard" || difficulty === "intermediate") && networkEnigme != null){
const tex = generateLatexCodeEnigme(personNetwork, person, indices, network, networkEnigme)
const blob = new Blob([tex], { type: 'application/x-latex;charset=utf-8' });
zip.file('socialGraph.tex', tex);
}
else{
const tex = generateLatexCode(personNetwork, person, indices, network)
const blob = new Blob([tex], { type: 'application/x-latex;charset=utf-8' });
zip.file('socialGraph.tex', tex);
}
const imageNames = ['ballon-de-basket.png', 'ballon-de-foot.png', "baseball.png", "bowling.png", "tennis.png"]; // Liste des noms de fichiers d'images
const imageNames2 = [ballonDeBasket, ballonDeFoot, baseball, bowling, tennis];
const imagesFolder = 'Script';
let test = 0
for (const image of imageNames2) {
/*
const imageUrl = `localhost/${imagesFolder}/${imageName}`;
console.log(imageUrl)
const response = await fetch(imageUrl);
*/
const response = await fetch(image);
if (response.ok) {
const imageBlob = await response.blob();
zip.file(imageNames[test], imageBlob);
test++
} else {
// console.error(`Erreur de chargement de l'image ${imageName}`);
}
}
const content = await zip.generateAsync({ type: 'blob' });
// Enregistre l'archive en tant que fichier
saveAs(content, 'social_graph.zip');
// Utiliser FileSaver pour télécharger le fichier
saveAs(blob, 'socialGraph.tex');
}
}
@ -159,7 +277,7 @@ const InGame = ({locale, changeLocale}) => {
const handleCloseS = () => setShowS(false);
const handleShowS = () => setShowS(true);
const [cptTour, setcptTour] = useState(0);
const [cptTour, setcptTour] = useState(1);
const [LastVisible, setLastVisible] = useState(false);
@ -209,9 +327,106 @@ const InGame = ({locale, changeLocale}) => {
window.open(url);
};
const [SwitchEnabled, setSwitchEnabled] = useState(false)
// const [SwitchEnabled, setSwitchEnabled] = useState(false)
const allIndices = Stub.GenerateIndice()
const { indice, players } = useGame();
const { indice, players, actualPlayerIndex} = useGame();
const nbPlayer = players.length;
const navdeduc = 'deduc?actualId=' + actualPlayerIndex + '&nbPlayer=' + nbPlayer;
//* Sound
const [playTurnSound, setPlayTurnSound] = useState(false);
const [soundPreference, setSoundPreference] = useState(true); // utilisateur
const [enteredNumber, setEnteredNumber] = useState(user?.nbNodes || 25);
const [enteredNumberIndices, setEnteredNumberIndices] = useState(user?.nbIndices || 3);
//@ts-ignore
const handleNumberChange = (event) => {
if (parseInt(event.target.value)){
setEnteredNumber(parseInt(event.target.value));
}
};
//@ts-ignore
const handleKeyDown = (event) => {
// Vérifier si la touche appuyée est "Entrée"
if (event.key === 'Enter' && user!==null && parseInt(event.target.value)) {
const newNumber = Math.max(20, Math.min(50, parseInt(event.target.value, 10)));
user.nbNodes = newNumber;
setEnteredNumber(newNumber);
}
};
const handleBlur = () => {
if (user!==null){
const newNumber = Math.max(20, Math.min(50, enteredNumber));
user.nbNodes = newNumber;
setEnteredNumber(newNumber);
}
};
//@ts-ignore
const handleNumberIndicesChange = (event) => {
if (parseInt(event.target.value)){
setEnteredNumberIndices(parseInt(event.target.value));
}
};
useEffect(() => {
if (changeGraph){
if (enteredNumber>=20 && enteredNumber<=50 && enteredNumberIndices>=3 && enteredNumberIndices<=6){
console.log(enteredNumber)
console.log(enteredNumberIndices)
manager?.userService.changeNodesIndices(enteredNumber, enteredNumberIndices)
setHistory([]);
setFirstHistory(true)
changeGraph(enteredNumber, enteredNumberIndices)
}
}
else{
setEnteredNumber(user?.nbNodes || 25)
setEnteredNumberIndices(user?.nbIndices || 3)
}
}, [enteredNumber, enteredNumberIndices])
//@ts-ignore
const handleKeyDownIndice = (event) => {
// Vérifier si la touche appuyée est "Entrée"
if (event.key === 'Enter' && user!=null && parseInt(event.target.value)) {
const newNumber = Math.max(3, Math.min(6, parseInt(event.target.value, 10)));
user.nbIndices = newNumber;
setEnteredNumberIndices(newNumber);
}
};
const handleBlurIndice = () => {
if (user!==null){
const newNumber = Math.max(3, Math.min(6, enteredNumberIndices));
setEnteredNumberIndices(newNumber);
user.nbIndices = newNumber;
}
};
const handleSoundPreferenceChange = () => {
setSoundPreference(!soundPreference);
console.log("changement des options du son : "+ soundPreference)
};
const handleTurn = () => {
console.log("etat normal du sound : " + soundPreference)
if (soundPreference) {
setPlayTurnSound(true);
setTimeout(() => {
setPlayTurnSound(false);
}, 2000);
}
};
return (
@ -225,24 +440,42 @@ const InGame = ({locale, changeLocale}) => {
addToHistory={addToHistory}
solo={IsSolo}
isDaily={isDaily}
isEasy={isEasy}
difficulty={difficulty}
setPlayerTouched={handleSetPlayerTouched}
playerTouched={playerTouched}
setNetwork={setNetworkData}
showLast={showLast}/>
setNetworkEnigme={setNetworkEnigmeData}
showLast={showLast}
setPlayerIndex={setPlayerIndexData}
askedWrong={askedWrong}
setAskedWrong={setAskedWrongData}
importToPdf={importToPdf}
setImportToPdf={setImportToPdfData}
importToJSON={importToJSON}
setImportToJSON={setImportToJSONData}
setPutCorrectBackground={setPutCorrectBackgroundData}
setPutGreyBackground={setPutGreyBackgroundData}
setPutImposssibleGrey={setPutImposssibleGreyData}
putCorrectBackground={putCorrectBackground}
putGreyBackground={putGreyBackgroud}
putImposssibleGrey={putImposssibleGrey}
handleTurn={handleTurn}
lang={locale}
setChangeGraph={setChangeGraphData}/>
</div>
{playTurnSound && <audio src={turnSound} autoPlay />}
{IsSolo && !isDaily &&
{IsSolo && (!isDaily || difficulty !== "hard") &&
<div className='nbLaps' style={{
backgroundColor: theme.colors.primary,
borderColor: theme.colors.secondary
}}>
Tour : {cptTour}
<FormattedMessage id='turn'/> : {cptTour}
</div>
}
{(!isDaily || (isDaily && isEasy)) &&
{(!isDaily || (isDaily && (difficulty==="easy" || difficulty==="intermediate"))) &&
<div className='historique' id="history-container">
{history.map((item, index) => (
<div key={index}>{item}</div>
@ -264,6 +497,7 @@ const InGame = ({locale, changeLocale}) => {
<div className='menuGame'>
<div className='resetDiv'>
<button className='button'
style={{
@ -285,13 +519,15 @@ const InGame = ({locale, changeLocale}) => {
</button>
</div>
{/* <Link to='/info#indice-possible' target='_blank'>
//? redirection impossible apparament (securité des navigateur
*/}
<Link to='/info' target='_blank'>
<Link to={`${basePath}/info`} target='_blank'>
<button className='button'
style={{
backgroundColor: theme.colors.tertiary,
@ -300,12 +536,9 @@ const InGame = ({locale, changeLocale}) => {
<img src={Info} alt="info" height="40"/>
</button>
</Link>
{/* <button className='button' onClick={() => openInNewTab('http://localhost:3000/play')}> //! avec url =={'>'} dangereux
<img src={Check} alt="check" height="40"/>
</button> */}
{!IsSolo &&
<Link to='/info' target='_blank'>
<Link to={`${basePath}/${navdeduc}`} target='_blank'>
<button className='button'
style={{
backgroundColor: theme.colors.tertiary,
@ -338,27 +571,24 @@ const InGame = ({locale, changeLocale}) => {
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary
}}>
<img src={Download} alt="indice" height="40"/>
<img src={Tex} alt="indice" height="40"/>
</button>
}
</div>
{/*
<Offcanvas show={showP}
onHide={handleCloseP}>
<Offcanvas.Header closeButton>
<Offcanvas.Title>Joueurs</Offcanvas.Title>
<h3>Il y a {players.length} joueurs</h3>
</Offcanvas.Header>
<Offcanvas.Body>
</Offcanvas.Body>
</Offcanvas>
*/}
{IsSolo &&
<button className='button' onClick={ () => setImportToPdfData(true)}
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary
}}>
<img src={Pdf} alt="indice" height="40"/>
</button>
}
</div>
{ !IsSolo &&
<div className='playerlistDiv'>
<PlayerList players={players} setPlayerTouched={handleSetPlayerTouched} playerTouched={playerTouched} />
<PlayerList players={players} setPlayerTouched={handleSetPlayerTouched} playerTouched={playerTouched} playerIndex={playerIndex} askedWrong={askedWrong} greyForEveryone={() => {}}/>
</div>
}
@ -369,10 +599,9 @@ const InGame = ({locale, changeLocale}) => {
backdrop={false}
style={{ height: '20%', width: '25%', top: '60vh' }}>
<Offcanvas.Header closeButton>
<Offcanvas.Title>Indice</Offcanvas.Title>
<Offcanvas.Title><FormattedMessage id='indice'/></Offcanvas.Title>
</Offcanvas.Header>
<Offcanvas.Body>
{/* Possède les cheveux noir <u>ou</u> joue au basket */}
{indice?.ToString(locale)}
</Offcanvas.Body>
</Offcanvas>
@ -383,36 +612,62 @@ const InGame = ({locale, changeLocale}) => {
<Offcanvas show={showS}
onHide={handleCloseS}
placement='top'
style={{height: '30%', width: '30%', left: '70%' }}>
style={{height: '80%', width: '30%', left: '70%' }}>
<Offcanvas.Header closeButton>
<Offcanvas.Title><img src={Param} alt='param'/> Paramètres</Offcanvas.Title>
<Offcanvas.Title><img src={Param} alt='param'/> <FormattedMessage id='param'/></Offcanvas.Title>
</Offcanvas.Header>
<Offcanvas.Body>
<Nav className="me-auto">
<NavDropdown
title={<span><HiLanguage/> Language </span>}
className="navbar-title" id="basic-nav-dropdown">
<NavDropdown.Item onClick={() => changeLocale('fr')}>
<FormattedMessage id="languageSelector.french"/>
</NavDropdown.Item>
<NavDropdown.Item onClick={() => changeLocale('en')}>
<FormattedMessage id="languageSelector.english"/>
</NavDropdown.Item>
</NavDropdown>
</Nav>
<label>
<Switch checked={SwitchEnabled} onChange={setSwitchEnabled}/>
<p>Afficher les noeuds possibles</p>
</label>
<Offcanvas.Body >
<div style={{display: "flex", justifyContent: "center", flexDirection: "column", alignItems: "center"}}>
<label style={{ display:'flex'}}>
<Switch checked={soundPreference} onChange={handleSoundPreferenceChange}/>
<p style={{ marginLeft:'20px'}}><FormattedMessage id='sfx'/></p>
</label>
{IsSolo &&
<div className='nbNodeDiv' style={{ padding:'20px'}}>
<label htmlFor="numberInput"><FormattedMessage id='param.node'/> :</label>
<div>
<button className='valuebutton' onClick={() => { if (enteredNumber>20 && user !== null){ setEnteredNumber(enteredNumber-1); user.nbNodes = user.nbNodes-1; setHistory([]); }}}
style={{borderColor:theme.colors.secondary}}> - </button>
<input
// type="number"
style={{textAlign:'center', border: 'none', width: '100px'}}
id="numberInput"
value={enteredNumber}
onChange={handleNumberChange}
onKeyDown={handleKeyDown} // Ajout de l'événement onKeyDown
onBlur={handleBlur} // Ajout de l'événement onBlur
/>
<button className='valuebutton' onClick={() => { if (enteredNumber<50 && user!==null){ setEnteredNumber(enteredNumber+1); user.nbNodes = user.nbNodes+1 ;setHistory([]); }}}
style={{borderColor:theme.colors.secondary}}> + </button>
</div>
</div>}
{IsSolo &&
<div className='nbNodeDiv' style={{ padding:'20px'}}>
<label htmlFor="numberInput"><FormattedMessage id='param.clue'/> :</label>
<div>
<button className='valuebutton' onClick={() => { if (enteredNumberIndices>3 && user!==null){ setEnteredNumberIndices(enteredNumberIndices - 1); user.nbIndices = user.nbIndices-1; setHistory([]); }}}
style={{borderColor:theme.colors.secondary}}> - </button>
<input
// type="number"
style={{textAlign:'center', border: 'none', width: '100px'}}
id="numberInput"
value={enteredNumberIndices}
onChange={handleNumberIndicesChange}
onKeyDown={handleKeyDownIndice} // Ajout de l'événement onKeyDown
onBlur={handleBlurIndice}/>
<button className='valuebutton' onClick={() => { if (enteredNumberIndices<6 && user!==null){ setEnteredNumberIndices(enteredNumberIndices + 1); user.nbIndices = user.nbIndices+1; setHistory([]); }}}
style={{borderColor:theme.colors.secondary}}> + </button>
</div>
</div>}
<div className='centerDivH' style={{margin: "20px"}}>
<Button variant="outline-warning" onClick={() => {setHistory([]); changeGraph(enteredNumber, enteredNumberIndices)}}><FormattedMessage id='regenerate'/></Button>
</div>
<Button variant="outline-danger" href={`${basePath}/`}><FormattedMessage id='BackHome'/></Button>
</div>
</Offcanvas.Body>
</Offcanvas>
{/*
<div id="endgamebutton" > {/* tmp
<ButtonImgNav dest="/endgame" img={Leave} text='endgame'/>
</div>
*/}
</div>
);
};

@ -1,4 +1,4 @@
import React from 'react';
import React, {useEffect} from 'react';
/* Style */
import '../Style/Global.css';
@ -24,11 +24,34 @@ import Alert from 'react-bootstrap/Alert';
import MGlass from "../res/icon/magnifying-glass.png";
import Param from "../res/icon/param.png";
import Info from "../res/icon/infoGreen.png"; //todo changer la couleur de l'icon
import { useAuth } from '../Contexts/AuthContext';
import { Spinner } from 'react-bootstrap';
import Reset from '../res/icon/reset.png';
import Ceye from '../res/icon/hidden.png';
//@ts-ignore
function InfoPage({locale, changeLocale}) {
const theme = useTheme();
const {isLoggedIn, login, user, setUserData, manager } = useAuth();
useEffect(() => {
if (user == null){
console.log(user)
manager.userService.fetchUserInformation().then(([user, loggedIn]) =>{
console.log(user);
if (user!=null){
if (loggedIn){
login()
setUserData(user)
}
else{
setUserData(user)
}
}
})
}
}, [isLoggedIn]);
const styles = {
roux: { backgroundColor: ColorToHexa(Color.REDHEAD), width: '15px', height: '15px', display: 'inline-block', marginRight: '5px' },
@ -55,16 +78,15 @@ function InfoPage({locale, changeLocale}) {
<li><Link to="#composants-du-jeu"><span><FormattedMessage id="info.title.composant"/></span></Link></li>
<li><Link to="#objectif-du-jeu"><span><FormattedMessage id="info.title.objectif"/></span></Link></li>
<li><Link to="#deroulement-du-jeu"><span><FormattedMessage id="info.title.deroulement"/></span></Link></li>
<li><Link to="#mode-de-jeu"><span><FormattedMessage id="info.mdj"/></span></Link></li>
<li><Link to="#indice-possible"><span><FormattedMessage id="info.title.indice_possible"/></span></Link></li>
</ul>
</div>
<section id="composants-du-jeu">
<h2><FormattedMessage id="info.pions"/> :</h2>
<h4>
<FormattedMessage id="info.sommaire"/>
</h4>
<h6><FormattedMessage id="info.composant.text"/></h6>
<h4><FormattedMessage id="info.sommaire"/></h4>
<h6><FormattedMessage id="info.composant.text"/></h6>
<ul>
<p>
<li><h5 className='h5title'><FormattedMessage id="info.composant.carre.title"/> : 🟪🟦🟩🟨🟥🟫</h5></li>
@ -73,7 +95,6 @@ function InfoPage({locale, changeLocale}) {
<li><h5 className='h5title'><FormattedMessage id="info.composant.rond.title"/> : 🟣🔵🟢🟡🔴🟤</h5></li>
<FormattedMessage id="info.composant.rond"/>
</p>
</ul>
<hr/>
<h4>
<FormattedMessage id="info.car_perso"/>
@ -122,6 +143,7 @@ function InfoPage({locale, changeLocale}) {
</ul>
<FormattedMessage id="info.composant.sport.bis"/>
</p>
</ul>
</section>
<hr/>
<section id="objectif-du-jeu">
@ -145,9 +167,9 @@ function InfoPage({locale, changeLocale}) {
</p>
<h4>
Interface :
<FormattedMessage id="info.interface"/>
</h4>
<h6> Pour chacune des parties, vous aurez certains éléments d'interface à disposition :</h6>
<h6> <FormattedMessage id="info.interface.text"/> </h6>
<ul>
<li>
<div className='LiInterfaceDisplay'>
@ -160,12 +182,24 @@ function InfoPage({locale, changeLocale}) {
<img src={Param} alt="paramètres" height='40'/>
</button>
<p>
Le bouton "<b>Paramètre</b>" permet l'affichage et la gestion de différent paramètres de la partie, comme par exemple le language, l'aide ... .
<FormattedMessage id="info.interface.param"/>
</p>
</div>
<Alert variant='danger'>
Attention, cette partie ne peut pas être complétée tant que tout les paramètres n'ont pas été choisis !
</Alert>
</li>
<li>
<div className='LiInterfaceDisplay'>
<button className='button'
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary,
margin:"0 20px"
}}>
<img src={Reset} alt="paramètres" height='40'/>
</button>
<p>
<FormattedMessage id="info.interface.reset"/>
</p>
</div>
</li>
<li>
<div className='LiInterfaceDisplay'>
@ -178,10 +212,7 @@ function InfoPage({locale, changeLocale}) {
<img src={Info} alt="info" height="40"/>
</button>
<p>
Le bouton "<b>Information</b>" permet de rediriger vers la page de règle du jeu (celle ci).
{/*
//! mais est ce que nous devons rediriger sur les indices possibles ?
*/}
<FormattedMessage id="info.interface.info"/>
</p>
</div>
</li>
@ -197,12 +228,9 @@ function InfoPage({locale, changeLocale}) {
</button>
<p>
Le bouton "<b>Fiche de déduction d'indice</b>" permet l'affichage de tableau dynamic permettant, avec le déroulé de la partie, de déduire quels indices sont les plus probables.
<FormattedMessage id="info.interface.fiche"/>
</p>
</div>
<Alert variant='danger'>
Attention, cette partie ne peut pas être complétée tant que la page et l'algorithme dédié ne sont pas fait !
</Alert>
</li>
<li>
<div className='LiInterfaceDisplay'>
@ -216,10 +244,26 @@ function InfoPage({locale, changeLocale}) {
</button>
<p>
Le bouton "<b>Indice personnel</b>" est le plus important, en effet il permet d'afficher quel est votre indice secret. Vous seul le connaissais ! Il va falloir ruser pour tromper vos amis et le garder secret le plus longtemps possible !
<FormattedMessage id="info.interface.indice"/>
</p>
</div>
</li>
<li>
<div className='LiInterfaceDisplay'>
<button className='button'
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary,
margin:"0 20px"
}}>
<img src={Ceye} alt="check" height="40"/>
</button>
<p>
<FormattedMessage id="info.interface.vision"/>
</p>
</div>
</li>
{/* ajouter les bouton de telechargement de graph ? */}
</ul>
</section>
<hr/>
@ -245,6 +289,46 @@ function InfoPage({locale, changeLocale}) {
</p>
</section>
<hr/>
<section id="mode-de-jeu">
<h2> <FormattedMessage id="info.mdj"/> </h2>
<br/>
<p>
<FormattedMessage id="info.mdj.text"/>
</p>
<h3>
<FormattedMessage id="info.mdj.mastermind"/>
</h3>
<br/>
<p>
<FormattedMessage id="info.mdj.mastermind.text"/>
</p>
<h3>
<FormattedMessage id="info.mdj.enigme"/>
</h3>
<p>
<FormattedMessage id="info.mdj.enigme.text"/>
</p>
<h4>
<u><FormattedMessage id="info.mdj.enigme.easy"/></u>
</h4>
<p>
<FormattedMessage id="info.mdj.enigme.easy.txt"/>
</p>
<h4>
<u><FormattedMessage id="info.mdj.enigme.medium"/></u>
</h4>
<p>
<FormattedMessage id="info.mdj.enigme.medium.txt"/>
</p>
<h4>
<u><FormattedMessage id="info.mdj.enigme.hard"/></u>
</h4>
<p>
<FormattedMessage id="info.mdj.enigme.hard.txt"/>
</p>
</section>
<hr/>
<section id="indice-possible">
<h2><FormattedMessage id="info.title.indice_possible"/> :</h2>
<br/>
@ -270,7 +354,6 @@ function InfoPage({locale, changeLocale}) {
<FormattedMessage id="info.indice-possible.voisin"/>
</h4>
<IndiceList instance={EdgesIndice} lang={locale}/>
<IndiceList instance={NbEdgesIndice} lang={locale}/>
<hr/>
</section>
</div>

@ -0,0 +1,46 @@
.lobbyMainContainer{
display: flex;
flex-direction: column;
background-color: white;
border: solid 1px whitesmoke;
border-radius: 15px;
box-shadow: 5px 5px 5px rgb(246, 246, 246);
width: 300px;
/* max-width: 300px; */
/* max-width: 20%; */
max-height: 200px;
padding: 20px;
margin: 20px;
cursor: pointer;
}
.lobbyMainContainer:hover{
background-color: whitesmoke;
}
.lobbyList{
display: flex;
/* justify-content: space-around; */
/* columns: 4; */
flex-wrap: wrap;
justify-content: center;
}
.searchLobby{
border: solid 1px whitesmoke;
border-radius: 15px;
padding: 15px;
width: 50%;
}
h1{
margin: 50px !important;
font-family: "Raleway", sans-serif;
}

@ -0,0 +1,175 @@
import React, { useEffect, useState } from 'react';
/* Style */
import './Lobbies.css';
import "../Style/Global.css"
import { useTheme } from '../Style/ThemeContext';
import LobbyContainer from '../Components/LobbyContainer';
import Player from '../model/Player';
import User from '../model/User';
import { socket } from '../SocketConfig';
import JSONParser from '../JSONParser';
import Person from '../model/Person';
import { useNavigationType } from 'react-router-dom';
import { FormattedMessage, useIntl } from 'react-intl';
class LobbyDataProps {
roomNum : string
headPlayer: Player
nbPlayer: number
started: boolean
constructor(roomNum: string, player: Player, nbPlayer: number, started: boolean){
this.roomNum = roomNum
this.headPlayer = player
this.nbPlayer = nbPlayer
this.started=started
}
}
let cptNavigation = 0
function Lobbies() {
const theme=useTheme();
const [first, setFirst] = useState(true)
const [lobbyData, setLobbyData] = useState<LobbyDataProps[]>([])
const [searchTerm, setSearchTerm] = useState('');
const [showAvailable, setShowAvailable] = useState(true);
const handleShowAllClick = () => {
setShowAvailable(false);
};
const handleShowAvailableClick = () => {
setShowAvailable(true);
};
const handleSetFirst = () => {
setFirst(false);
socket.emit("request lobbies");
};
const handleSetCptNavigation = () => {
cptNavigation++
if (cptNavigation % 2 >= 0 && navigationType.toString() === "POP") {
socket.emit("player quit");
}
};
const filteredLobbies = lobbyData.filter((lobby) =>
lobby.roomNum.toLowerCase().includes(searchTerm.toLowerCase()) ||
lobby.headPlayer.pseudo.toLowerCase().includes(searchTerm.toLowerCase())
);
const filteredLobbiesToShow = showAvailable
? filteredLobbies.filter((lobby) => lobby.started == false && lobby.nbPlayer < 6) //* retire les lobbies pleins ou commencés
: filteredLobbies;
const setFirstData = (first: boolean) => {
setFirst(first)
}
const navigationType = useNavigationType();
handleSetCptNavigation();
if (first){
handleSetFirst();
}
useEffect(() => {
socket.on("request lobbies", (map) => {
const jsonMap = JSON.parse(map)
const tmpTab: LobbyDataProps[]=[]
for(const item of jsonMap){
tmpTab.push(new LobbyDataProps(item.key, JSONParser.JSONToPlayer(item.value.tab[0]), item.value.tab.length, item.value.started))
}
setLobbyData(tmpTab)
})
}, [])
function createLobby(){
socket.emit("lobby created")
}
const intl = useIntl();
return(
<div style={{display:'flex', flexDirection:'column', alignItems:'center'}}>
<input
type="text"
className='searchLobby'
placeholder={intl.formatMessage({ id: 'placeholder.searchLobby' })}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
style={{width:'80%', margin:'10px'}}
/>
<div style={{border:'solid 3px', borderColor:'lightgray', borderRadius:'20px', margin:'10px'}}>
<button
style={{
width:'120px',
border:'solid',
borderStyle:'none',
borderRadius: '15px 0px 0px 15px',
borderWidth: '2px',
padding: '10px 15px',
backgroundColor: !showAvailable ? 'white' : 'lightgray',
}}
onClick={handleShowAllClick}
>
<FormattedMessage id='lobbies.all'/>
</button>
<button
style={{
width:'120px',
border:'solid',
borderStyle:'none',
borderRadius: '0px 15px 15px 0px',
padding: '10px 15px',
backgroundColor: showAvailable ? 'white' : 'lightgray',
}}
onClick={handleShowAvailableClick}
>
<FormattedMessage id='lobbies.dispo'/>
</button>
</div>
{filteredLobbiesToShow.length === 0 ? (
<button onClick={createLobby} className='ButtonNav' style={{backgroundColor: theme.colors.primary, borderColor: theme.colors.secondary}}><FormattedMessage id='play.create' /></button>
) : (
<div className="lobbyList">
{filteredLobbiesToShow.map((lobby, index) => (
<LobbyContainer
key={index}
roomNum={lobby.roomNum}
HeadPlayer={lobby.headPlayer}
nbPlayer={lobby.nbPlayer}
setFirst={setFirstData}
started={lobby.started}
/>
))}
</div>
)}
</div>
);
}
export default Lobbies;

@ -41,6 +41,13 @@
margin: 20px;
}
.NumbDiv{
display: flex;
align-items: start;
justify-content: start;
margin: auto 20px;
}
.codeDiv{
display: flex;
align-items: end;
@ -48,7 +55,7 @@
margin: auto 20px;
}
.codeDiv p{
.codeDiv p, .NumbDiv p{
font-style: italic;
font-weight: bold;
color: gray;
@ -59,4 +66,39 @@
.centerButton {
display: flex;
justify-content: center;
}
.lobbyR{
display: flex;
flex-direction: column;
align-items: center;
}
.lobbyR *{
margin: 20px 10px;
}
.valuebutton{
border-radius: 90px;
width: 50px;
height: 50px;
background-color: #fff;
}
.nbNodeDiv{
display: flex;
flex-direction: column;
justify-content: center;
border: solid 2px whitesmoke;
border-radius: 15px;
background-color: white;
width: 70%;
}
.nbNodeDiv div {
display: flex;
justify-content: center;
}

@ -42,15 +42,23 @@ import { socket } from "../SocketConfig";
import { random } from 'lodash';
import SessionService from '../services/SessionService';
import { useRef } from 'react';
import Button from 'react-bootstrap/Button';
import Overlay from 'react-bootstrap/Overlay';
import { DataSet } from 'vis-network';
import {basePath} from "../AdressSetup"
import { FormattedMessage } from 'react-intl';
let gameStarted = false
let firstLaunch = true
function Lobby() {
const theme=useTheme();
const navigate = useNavigate();
const { indices, setIndicesData, indice, setIndiceData, person, setPersonData, personNetwork, setPersonNetworkData, players, setPlayersData, setActualPlayerIndexData, setTurnPlayerIndexData, setRoomData } = useGame();
const { indices, setIndicesData, setGameStartData, indice, setIndiceData, person, setPersonData, personNetwork, setPersonNetworkData, players, setPlayersData, setActualPlayerIndexData, setTurnPlayerIndexData, setRoomData, setNodesData } = useGame();
const {user, setUserData, manager, login} = useAuth()
let first = true
@ -59,10 +67,23 @@ function Lobby() {
const room = params.get('room');
function addBot(){
socket.emit("lobby joined", room, new EasyBot("botId" + Math.floor(Math.random() * 1000), "Bot" + Math.floor(Math.random() * 100), "").toJson())
let json = require("../res/botNames.json")
const tabNames = [...json.names]
let name = tabNames[Math.floor(Math.random() * tabNames.length)]
while (players.find((p) => p.pseudo === name) != undefined){
name = tabNames[Math.floor(Math.random() * tabNames.length)]
}
socket.emit("lobby joined", room, new EasyBot(name, name, "").toJson())
}
//* nb Node
const [enteredNumber, setEnteredNumber] = useState(25);
//@ts-ignore
const handleNumberChange = (event) => {
const newNumber = Math.max(20, Math.min(60, parseInt(event.target.value, 10)));
setEnteredNumber(newNumber);
};
useEffect(() => {
if (first){
@ -118,25 +139,75 @@ function Lobby() {
setPersonData(choosenOne)
setPersonNetworkData(network)
setIndicesData(choosenIndices)
setGameStartData(true)
first = true
gameStarted = true
socket.off("player left")
socket.off("new player")
navigate('/game?solo=false&daily=false');
//socket.off("player left")
//socket.off("new player")
navigate(`${basePath}/game?solo=false&daily=false`);
});
socket.on("join during game", (jsonNetwork, jsonPersonString, jsonIndicesString, playerIndex, players, nodes)=> {
const jsonPerson = JSON.parse(jsonPersonString)
const networkPerson: PersonNetwork = JSONParser.JSONToNetwork(jsonNetwork)
const choosenOne: Person = networkPerson.getPersons().filter((i) => i.getId() == jsonPerson.id)[0]
const choosenIndices : Indice[] = JSONParser.JSONToIndices(jsonIndicesString)
for (let i=0; i<players.length; i++){
const player = players[i]
if(player.id == socket.id){
setActualPlayerIndexData(i)
setIndiceData(choosenIndices[i])
}
if (player instanceof Bot){
player.indice = choosenIndices[i]
}
}
const tmpPlayers: Player[] = []
console.log(players)
for (const p of players){
tmpPlayers.push(JSONParser.JSONToPlayer(p))
}
setPlayersData(tmpPlayers)
if (room != null){
setRoomData(room)
}
const tab = JSONParser.JSONToNodePersons(JSON.parse(nodes))
setNodesData(tab)
setTurnPlayerIndexData(playerIndex)
setPersonData(choosenOne)
setPersonNetworkData(networkPerson)
setIndicesData(choosenIndices)
setGameStartData(true)
first = true
gameStarted = true
navigate(`${basePath}/game?solo=false&daily=false`)
});
socket.on("new player", (tab) =>{
const tmpTab: Player[] = []
for (const p of tab){
for (const p of tab.tab){
tmpTab.push(JSONParser.JSONToPlayer(p))
}
console.log(tmpTab)
setPlayersData(tmpTab)
setPlayersData(tmpTab);
});
socket.on("room full", () => {
navigate(`${basePath}/`)
})
socket.on("game started", () => {
navigate(`${basePath}/`)
})
socket.on("game already started", () => {
navigate(`${basePath}/`)
})
socket.on("player left", (tab, i) => {
const tmpTab: Player[] = []
for (const p of tab){
for (const p of tab.tab){
tmpTab.push(JSONParser.JSONToPlayer(p))
}
setPlayersData(tmpTab)
@ -146,38 +217,78 @@ function Lobby() {
function StartGame(){
const [networkPerson, choosenPerson, choosenIndices] = GameCreator.CreateGame(players.length, 30)
setPersonData(choosenPerson)
setPersonNetworkData(networkPerson)
setIndicesData(choosenIndices)
let users = players.filter((p) => p instanceof User)
let u = users[Math.floor(Math.random() * users.length)]
let start = players.findIndex((p) => p.id == u.id)
if (start == -1){
start = 0
if (players.length > 2){
const [networkPerson, choosenPerson, choosenIndices] = GameCreator.CreateGame(players.length, enteredNumber)
setPersonData(choosenPerson)
setPersonNetworkData(networkPerson)
setIndicesData(choosenIndices)
let users = players.filter((p) => p instanceof User)
let u = users[Math.floor(Math.random() * users.length)]
let start = players.findIndex((p) => p.id == u.id)
if (start == -1){
start = 0
}
socket.emit('network created', JSON.stringify(networkPerson, null, 2), JSON.stringify(choosenPerson), JSON.stringify(choosenIndices), room, start);
}
socket.emit('network created', JSON.stringify(networkPerson, null, 2), JSON.stringify(choosenPerson), JSON.stringify(choosenIndices), room, start);
}
const copyGameLink = () => {
setShow(!show)
const gameLink = basePath + "/lobby?room="+ room;
navigator.clipboard.writeText(gameLink)
.then(() => {
console.log('Lien copié avec succès !');
})
.catch((err) => {
console.error('Erreur lors de la copie du lien :', err);
});
};
const textAreaRef = useRef<HTMLTextAreaElement>(null);
const linkToCopy = basePath + "/lobby?room="+ room
const handleCopyClick = () => {
setShow(!show)
if(textAreaRef.current != null){
textAreaRef.current.select();
document.execCommand('copy');
}
};
const [show, setShow] = useState(false);
const target = useRef(null);
return (
<div className='lobby-container'>
<div className='left-part'>
<div className='player-board'>
<div className='codeDiv' onClick={() => setCodeShowed(!codeShowed)}>
{
codeShowed ? (
<p>Room : {room}</p>
) : (
<p>Room : ******</p>
)
}
<div>
<div className='codeDiv' onClick={() => setCodeShowed(!codeShowed)}>
{
codeShowed ? (
<p>Room : {room}</p>
) : (
<p>Room : ******</p>
)
}
</div>
<div className='NumbDiv'>
{players.length == 6 ? (
<p style={{color:'darkred'}}>6/6 <FormattedMessage id='lobby.players'/></p>
) : (
<p>{players.length}/6 <FormattedMessage id='lobby.players'/></p>
)
}
</div>
</div>
{/* //! voir pour la gestion avec un liste, utilisateur avec le "+ (vous)" et les pdp avec les lettres grecs (?)*/}
{players.map((player, index) => (
// <PlayerItemList key={player.id} pdp={PersonImg} name={player.name} id={player.id}/>
<PlayerItemList key={player.id} player={player} room={room}/>
))}
<div className='centerButton'>
{(players.length < 6) && <div className='centerButton'>
<button className='button' onClick={addBot}
style={{
backgroundColor: theme.colors.primary,
@ -185,42 +296,75 @@ function Lobby() {
+
</button>
</div>
}
</div>
</div>
<div className="lobby-vertical-divider" style={{backgroundColor: theme.colors.secondary}}></div>
<div className='right-part'>
<div className='title-param-div'>
<img src={param} alt="param"/>
<h2>Paramètre de la partie</h2>
<div className='lobbyR'
style={{flexDirection:'column',
alignItems:'space-around'}}>
<h3><FormattedMessage id='lobby.bienvenue'/></h3>
<p><FormattedMessage id='lobby.wait'/></p>
{/* Bouton pour copier le lien */}
{/* <Button variant="primary" ref={target} onClick={copyGameLink}>
<FormattedMessage id='lobby.invite'/>
</Button> */}
<Overlay target={target.current} show={show} placement="top">
{({
placement: _placement,
arrowProps: _arrowProps,
show: _show,
popper: _popper,
hasDoneInitialMeasure: _hasDoneInitialMeasure,
...props
}) => (
<div
{...props}
style={{
position: 'absolute',
backgroundColor: theme.colors.secondary,
padding: '2px 10px',
color: 'white',
borderRadius: 3,
...props.style,
}}
>
<FormattedMessage id='lobby.copyLink'/>
</div>
)}
</Overlay>
<div className='nbNodeDiv'>
<label htmlFor="numberInput"> <FormattedMessage id='lobby.nbNode'/> :</label>
<div>
<button className='valuebutton' onClick={() => { if (enteredNumber>20) setEnteredNumber(enteredNumber-1)}}
style={{borderColor:theme.colors.secondary}}> - </button>
<input
// type="number"
style={{textAlign:'center'}}
id="numberInput"
disabled
value={enteredNumber}
onChange={handleNumberChange}
min={20}
max={60}/>
<button className='valuebutton' onClick={() => { if (enteredNumber<50) setEnteredNumber(enteredNumber+1)}}
style={{borderColor:theme.colors.secondary}}> + </button>
</div>
</div>
<ul>
<li><h4> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim</h4></li>
<li><h4>paramètre super important pour la partie</h4></li>
<li><h4>paramètre super important pour la partie</h4></li>
<li><h4>paramètre super important pour la partie</h4></li>
<li><h4>Niveau des bots : Facile </h4></li> {/* mettre un dropdown ou un swiper */}
<li><h4>Thèmes : basique </h4></li> {/* mettre un dropdown*/}
{
//? mettre un timer pour chaques personne ?
//? indice avancé ? ==> négation, voisin du 2e degré etc.
}
</ul>
{/* <center >
<button className='buttonNabImg' onClick={StartGame}>
<img src={cible} alt="Button Image" height="50" width="50" />
<p>{"la chasse !"}</p>
</button>
</center> */}
<div className='centerDivH'>
<button className='button' onClick={StartGame}
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary
borderColor: theme.colors.secondary,
width: 'auto',
height: 'auto'
}}>
<img src={cible} alt="cible" height="40"/>
<FormattedMessage id='lobby.start'/>
</button>
</div>
</div>

@ -4,22 +4,23 @@ import { useNavigate } from 'react-router-dom';
import { useAuth } from '../Contexts/AuthContext';
import AuthService from '../services/AuthService';
import '../Style/Global.css';
import { Link } from 'react-router-dom';
import {basePath} from "../AdressSetup"
const SignIn = () => {
const navigate = useNavigate();
const { login } = useAuth();
const [error, setError] = useState<string | null>(null);
const {login} = useAuth();
const [showConfirmation, setShowConfirmation] = useState(false);
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
try {
const data = {
pseudo: (event.target as any).pseudo.value,
password: (event.target as any).password.value,
remember: (event.target as any).remember.checked
password: (event.target as any).password.value
};
const validation = await AuthService.validateSignIn(data);
@ -31,13 +32,13 @@ const SignIn = () => {
const result = await AuthService.signIn(data);
// console.log(result);
console.log(result);
setShowConfirmation(true);
setTimeout(async () => {
await login();
navigate('/play'); // 3 secondes avant de rediriger vers la page de connexion
}, 3000);
navigate(`${basePath}/`);
}, 1250);
}
} catch (error: any) {
setError(error.message);
@ -73,26 +74,13 @@ const SignIn = () => {
placeholder="Entrez votre mot de passe ici"
/>
</div>
<div className="mb-3">
<div className="custom-control custom-checkbox">
<input
type="checkbox"
name='remember'
className="custom-control-input"
id="customCheck1"
/>
<label className="custom-control-label" htmlFor="customCheck1">
Se souvenir de moi
</label>
</div>
</div>
<div className="d-grid">
<button type="submit" className="btn btn-primary">
Soumettre <AiOutlineSend/>
</button>
</div>
<p className="forgot-password text-right">
<a href="#">Mot de passe</a> oublié ?
<Link to={`${basePath}/signup`}>Pas encore inscrit ?</Link>
</p>
</form>
@ -104,7 +92,7 @@ const SignIn = () => {
{showConfirmation && (
<div className="alert alert-success" role="alert">
Connexion réussie ! Vous serez redirigé vers votre profil dans 3 secondes.
Connexion réussie ! Vous serez redirigé vers votre profil.
</div>
)}
</div>

@ -0,0 +1,270 @@
import React, { useEffect, useRef, useState } from 'react';
/* Context */
import { useAuth } from '../Contexts/AuthContext';
/* Style */
import './Play.css';
import { useTheme } from '../Style/ThemeContext';
/* Component */
import ButtonImgNav from "../Components/ButtonImgNav";
/* Img */
/* Icon */
import { socket } from '../SocketConfig';
import { NavigationType, useNavigate, useNavigationType } from 'react-router-dom';
import GameCreator from '../model/GameCreator';
import { useGame } from '../Contexts/GameContext';
import ScoreBoard from '../Components/ScoreBoard';
import defaultImg from "../res/img/Person.png"
/* Types */
import User from '../model/User';
import EnigmeDuJourCreator from '../model/EnigmeDuJourCreator';
import Stub from '../model/Stub';
import SessionService from '../services/SessionService';
import { loadImageAsync } from '../ImageHelper';
import { Overlay, ToggleButton, ToggleButtonGroup } from 'react-bootstrap';
import Button from 'react-bootstrap/Button';
import ButtonGroup from 'react-bootstrap/ButtonGroup';
import Lobbies from './Lobbies';
import {basePath} from "../AdressSetup"
import { FormattedMessage } from 'react-intl';
let cptNavigation = 0
function NewPlay() {
let first = true
const theme=useTheme()
const {isLoggedIn, login, user, setUserData, manager } = useAuth();
const {setDailyEnigmeData, setIndicesData, setPersonData, setPersonNetworkData, setGameStartData } = useGame()
const target = useRef(null);
const navigationType = useNavigationType()
cptNavigation++
if (cptNavigation % 2 == 0){
if (navigationType.toString() == "POP"){
first=true
socket.emit("player quit")
}
}
useEffect(() => {
if (user == null){
manager.userService.fetchUserInformation().then(([user, loggedIn]) =>{
if (user!=null){
if (loggedIn){
login()
setUserData(user)
socket.emit("join back game", user)
}
else{
loadImageAsync(defaultImg).then((blob) => {
user.profilePicture=blob
setUserData(user)
})
}
}
})
}
else{
socket.emit("join back game", user)
}
}, [isLoggedIn]);
const [goBackRoom, setGoBackRoom] = useState(-1)
useEffect(() => {
socket.on("join back game", (room) => {
setGoBackRoom(room)
})
}, [])
const [room, setRoom] = useState(null);
const navigate = useNavigate();
function createLobby(){
socket.emit("lobby created")
}
function launchMastermind(){
const [networkPerson, choosenPerson, choosenIndices] = GameCreator.CreateGame(user?.nbIndices || 3, user?.nbNodes || 25)
setPersonData(choosenPerson)
setPersonNetworkData(networkPerson)
setIndicesData(choosenIndices)
setIndicesData(choosenIndices)
setGameStartData(true)
navigate(`${basePath}/game?solo=true&daily=false`);
}
function launchEngimeJour(){
//* overlay
if (!showOverlay)setShowOverlay(true)
else setShowOverlay(true)
}
function launchTuto(){
navigate(`${basePath}/tutorial`);
}
useEffect(() => {
const handleLobbyCreated = (newRoom: any) => {
setRoom(newRoom);
};
// Ajouter l'event listener
socket.on('lobby created', handleLobbyCreated);
// Nettoyer l'event listener lors du démontage du composant
return () => {
socket.off('lobby created', handleLobbyCreated);
};
}, []); // Aucune dépendance ici
useEffect(() => {
if (room !== null) {
const nouvelleURL = `/lobby?room=${room}`;
navigate(`${basePath}${nouvelleURL}`)
}
}, [room, navigate]);
const goBack = () => {
navigate(`${basePath}/lobby?room=${goBackRoom}`)
}
const [showOverlay, setShowOverlay] = useState(false);
const [selectedDifficulty, setSelectedDifficulty] = useState(null);
//@ts-ignore
const handleDifficultyChange = (value) => {
setSelectedDifficulty(value);
};
const handleStartEasyGame = () => {
//* Mode facile
//todo différencier les deux
const [networkPerson, choosenPerson, choosenIndices] = GameCreator.CreateGame(user?.nbIndices || 3, user?.nbNodes || 25)
setPersonData(choosenPerson)
setPersonNetworkData(networkPerson)
setIndicesData(choosenIndices)
setIndicesData(choosenIndices)
setGameStartData(true)
navigate(`${basePath}/game?solo=true&daily=true&difficulty=easy`);
setShowOverlay(false);
};
const handleStartMediumGame = () => {
//* Mode facile
//todo différencier les deux
const [networkPerson, choosenPerson, choosenIndices] = GameCreator.CreateGame(user?.nbIndices || 3, user?.nbNodes || 25)
setPersonData(choosenPerson)
setPersonNetworkData(networkPerson)
setIndicesData(choosenIndices)
setGameStartData(true)
navigate(`${basePath}/game?solo=true&daily=true&difficulty=intermediate`);
if (first){
first = false
const map = EnigmeDuJourCreator.createEnigme(networkPerson, choosenIndices, choosenPerson, Stub.GenerateIndice())
setDailyEnigmeData(map)
}
setShowOverlay(false);
};
const handleStartHardGame = () => {
//* Mode difficile
//todo différencier les deux
const [networkPerson, choosenPerson, choosenIndices] = GameCreator.CreateGame(user?.nbIndices || 3, user?.nbNodes || 25)
setPersonData(choosenPerson)
setPersonNetworkData(networkPerson)
setIndicesData(choosenIndices)
setGameStartData(true)
if (first){
first = false
const map = EnigmeDuJourCreator.createEnigme(networkPerson, choosenIndices, choosenPerson, Stub.GenerateIndice())
setDailyEnigmeData(map)
}
navigate(`${basePath}/game?solo=true&daily=true&difficulty=hard`);
setShowOverlay(false);
};
// if (goBackRoom != -1){
// var returnVisibility = "visible"
// }
// else{
// var returnVisibility = "hidden"
// }
// const returnVisibility: Visibility = goBackRoom !== -1 ? "visible" : "hidden";
const returnVisibility = goBackRoom !== -1 ? "block" : "none" ;
return (
<div className="MainContainer">
<div className="leftContainer">
{/* Boutons pour jouer */}
<div className='NewbuttonGroupVertical'>
<button onClick={launchMastermind} className="ButtonNav" style={{backgroundColor: theme.colors.primary, borderColor: theme.colors.secondary}}> <FormattedMessage id="play.jouerseul"/> </button>
<button ref={target} onClick={launchEngimeJour} className="ButtonNav" style={{backgroundColor: theme.colors.primary, borderColor: theme.colors.secondary}}> <FormattedMessage id="play.enigme"/> </button>
<Overlay show={showOverlay} target={target.current} placement="bottom" rootClose={true} rootCloseEvent='click' onHide={() => setShowOverlay(false)}>
{({ placement, arrowProps, show: _show, popper, ...props }) => (
<div
{...props}
style={{
backgroundColor: theme.colors.secondary,
padding: '2px 10px',
borderRadius: 3,
...props.style,
}}>
<ButtonGroup aria-label="difficulty">
<Button onClick={handleStartEasyGame}><FormattedMessage id='play.enigme.easy'/></Button>
<Button onClick={handleStartMediumGame}><FormattedMessage id='play.enigme.medium'/></Button>
<Button onClick={handleStartHardGame}><FormattedMessage id='play.enigme.hard'/></Button>
</ButtonGroup>
</div>
)}
</Overlay>
<button onClick={createLobby} className="ButtonNav" style={{backgroundColor: theme.colors.primary, borderColor: theme.colors.secondary}}> <FormattedMessage id='play.create'/> </button>
<button onClick={launchTuto} className="ButtonNav" style={{backgroundColor: theme.colors.primary, borderColor: theme.colors.secondary}}> <FormattedMessage id='play.tuto'/> </button>
<button onClick={goBack} className="ButtonNavRejoin" style={{ display:returnVisibility}}><FormattedMessage id='play.return'/> </button>
</div>
{/* Lobbies */}
<div style={{border:'solid 1px lightgray', borderRadius:'15px', marginTop:'20px', minHeight:'400px'}}>
<Lobbies/>
</div>
</div>
<div className='rightContainer'>
{user && <ScoreBoard Player={user}/>}
</div>
</div>
);
}
export default NewPlay;

@ -1,17 +1,16 @@
.MainContainer{
flex: 1 1 0;
.MainContainer {
display: flex;
flex-direction: row;
justify-content:space-around
justify-content: center;
align-items: center;
height: 100%;
}
.MidContainer{
.MidContainer {
display: flex;
/* justify-content:center; */
align-items:center;
justify-content: center;
align-items: center;
flex-direction: column;
margin-top: 15px;
width: 30%;
}
@ -20,54 +19,81 @@
display: flex;
justify-content: center;
}
/*
.MidContainer div {
display: flex;
justify-content: center;
flex-direction: column;
} */
.leftContainer{
width: 30%;
.leftContainer {
height: 100%;
margin: 20px 30px;
width: 70%;
}
.rightContainer{
.rightContainer {
height: 100%;
margin: 20px 30px;
width: 30%;
}
.NewleftContainer {
margin: 20px 30px;
width: 70%;
}
/* .textBoard div{
.NewrightContainer {
display: flex;
flex-direction:column-reverse;
justify-content:space-between
} */
flex-direction: column;
justify-content: center;
align-items: center;
width: 30%;
}
/* .textBoard div:nth-child(2){
.buttonGroupVertical {
display: flex;
justify-content: end;
} */
justify-content: center;
align-items: center;
flex-direction: column;
}
/**Button**/
.buttonGroupVertical{
.NewbuttonGroupVertical {
display: flex;
justify-content:center;
align-items:center;
flex-direction: column;
flex-direction: row;
justify-content: space-evenly;
}
.ButtonNav{
margin: 15px 0;
width:200px;
.ButtonNav {
margin: 15px 10px;
width: 200px;
height: 8vh;
color: white;
border: 2px solid #0056b3;
border-radius: 15px;
font-size: larger;
}
/* background-color: #85C9C2;
color: #2A4541; */
.ButtonNavRejoin {
margin: 15px 10px;
width: 200px;
height: 8vh;
color: white;
background-color: aquamarine;
border: 2px solid #0056b3;
border-radius: 15px;
font-size: larger;
}
border: solid;
.returnDiv {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 40px 15px;
border: 2px solid whitesmoke;
border-radius: 15px;
border-width: 2px;
background-color: white;
}
font-size:larger;
}
.returnDiv p {
margin: 15px;
padding: 10px;
border: 1px solid whitesmoke;
border-radius: 10px;
font-weight: 500;
}

@ -2,7 +2,6 @@ import React, { useEffect, useRef, useState } from 'react';
/* Context */
import { useAuth } from '../Contexts/AuthContext';
/* Style */
import './Play.css';
import { useTheme } from '../Style/ThemeContext';
@ -13,7 +12,7 @@ import ButtonImgNav from "../Components/ButtonImgNav";
/* Img */
/* Icon */
import { socket } from '../SocketConfig';
import { useNavigate } from 'react-router-dom';
import { NavigationType, useNavigate, useNavigationType } from 'react-router-dom';
import GameCreator from '../model/GameCreator';
import { useGame } from '../Contexts/GameContext';
import ScoreBoard from '../Components/ScoreBoard';
@ -21,18 +20,19 @@ import ScoreBoard from '../Components/ScoreBoard';
import defaultImg from "../res/img/Person.png"
/* Types */
import User from '../model/User';
import EnigmeDuJourCreator from '../model/EnigmeDuJourCreator';
import Stub from '../model/Stub';
import SessionService from '../services/SessionService';
import { loadImageAsync } from '../ImageHelper';
import { Overlay, ToggleButton, ToggleButtonGroup } from 'react-bootstrap';
import Button from 'react-bootstrap/Button';
import ButtonGroup from 'react-bootstrap/ButtonGroup';
import Info from '../res/icon/infoGreen.png';
import {basePath} from "../AdressSetup"
let cptNavigation = 0
function Play() {
@ -40,66 +40,29 @@ function Play() {
const theme=useTheme()
const {isLoggedIn, login, user, setUserData, manager } = useAuth();
const {setDailyEnigmeData} = useGame()
const {setDailyEnigmeData, setIndicesData, setPersonData, setPersonNetworkData } = useGame()
const target = useRef(null);
const navigationType = useNavigationType()
cptNavigation++
if (cptNavigation % 2 == 0){
if (navigationType.toString() == "POP"){
socket.emit("player quit")
}
}
useEffect(() => {
const fetchUserInformation = async () => {
try {
const sessionData = await SessionService.getSession();
// Vérifie si il y a une session
if (sessionData.user) {
// Il y a une session on récupère les infos du joueur
const updatedPlayer: User = new User(socket.id, sessionData.user.pseudo, sessionData.user.profilePicture, {
nbGames: sessionData.user.soloStats.nbGames,
bestScore: sessionData.user.soloStats.bestScore,
avgNbTry: sessionData.user.soloStats.avgNbTry,
},
{
nbGames: sessionData.user.onlineStats.nbGames,
nbWins: sessionData.user.onlineStats.nbWins,
ratio: sessionData.user.onlineStats.ratio,
})
login();
setUserData(updatedPlayer);
} else {
// Pas de session on génère un guest random
const guestPlayer: User = new User(socket.id, 'Guest_' + Math.floor(Math.random() * 1000000), '',
{
nbGames: 0,
bestScore: 0,
avgNbTry: 0,
},
{
nbGames: 0,
nbWins: 0,
ratio: 0,
})
setUserData(guestPlayer);
}
} catch (error) {
console.error(error);
}
};
fetchUserInformation();
}, [isLoggedIn]);
const { setIndicesData, setPersonData, setPersonNetworkData } = useGame();
useEffect(() => {
if (user == null){
manager.userService.fetchUserInformation().then(([user, loggedIn]) =>{
console.log(user);
if (user!=null){
if (loggedIn){
login()
setUserData(user)
socket.emit("join back game", user)
}
else{
loadImageAsync(defaultImg).then((blob) => {
@ -110,8 +73,20 @@ function Play() {
}
})
}
else{
socket.emit("join back game", user)
}
}, [isLoggedIn]);
const [goBackRoom, setGoBackRoom] = useState(-1)
useEffect(() => {
socket.on("join back game", (room) => {
setGoBackRoom(room)
})
}, [])
const [room, setRoom] = useState(null);
const navigate = useNavigate();
@ -119,17 +94,13 @@ function Play() {
socket.emit("lobby created")
}
useEffect(() => {
console.log(user)
}, [user])
function launchMastermind(){
const [networkPerson, choosenPerson, choosenIndices] = GameCreator.CreateGame(3, 30)
setPersonData(choosenPerson)
setPersonNetworkData(networkPerson)
setIndicesData(choosenIndices)
setIndicesData(choosenIndices)
navigate('/game?solo=true&daily=false');
navigate(`${basePath}/game?solo=true&daily=false`);
}
@ -160,10 +131,14 @@ function Play() {
useEffect(() => {
if (room !== null) {
const nouvelleURL = `/lobby?room=${room}`;
navigate(nouvelleURL);
navigate(`${basePath}${nouvelleURL}`);
}
}, [room, navigate]);
const goBack = () => {
navigate(`${basePath}/lobby?room=${goBackRoom}`)
}
const [showOverlay, setShowOverlay] = useState(false);
@ -184,7 +159,7 @@ function Play() {
setIndicesData(choosenIndices)
setIndicesData(choosenIndices)
navigate('/game?solo=true&daily=true&easy=true');
navigate(`${basePath}/game?solo=true&daily=true&easy=true`);
setShowOverlay(false);
};
@ -202,7 +177,7 @@ function Play() {
const map = EnigmeDuJourCreator.createEnigme(networkPerson, choosenIndices, choosenPerson, Stub.GenerateIndice())
setDailyEnigmeData(map)
}
navigate('/game?solo=true&daily=true&easy=false');
navigate(`${basePath}/game?solo=true&daily=true&easy=false`);
setShowOverlay(false);
};
@ -211,10 +186,22 @@ function Play() {
<div className="MainContainer">
<div className="leftContainer">
{/* <button className='ButtonNav'>
Param
</button> */}
{/* <ButtonImgNav dest='/signup' img={defaultImg} text="Gestion du compte"/> */}
{goBackRoom != -1 &&
<div className='returnDiv'>
<div style={{
display:'flex',
alignItems:'center'
}}>
<img src={Info} alt='info' height='50px' width='50px'/>
<h1>Information</h1>
</div>
<p> Il semblerait que vous avez quitté une partie en cours... <br/> <i>Et si nous y retournions ?</i></p>
<button onClick={goBack} className="ButtonNav"
style={{backgroundColor: theme.colors.primary, borderColor: theme.colors.secondary}}>
Retourner à la partie
</button>
</div>
}
</div>
<div className="MidContainer">
<div>
@ -253,8 +240,7 @@ function Play() {
<button onClick={createLobby} className="ButtonNav" style={{backgroundColor: theme.colors.primary, borderColor: theme.colors.secondary}}> Créer une partie </button>
<button className="ButtonNav" style={{backgroundColor: theme.colors.primary, borderColor: theme.colors.secondary}}> Rejoindre </button>
<button onClick= {() => navigate(`${basePath}/join`)} className="ButtonNav" style={{backgroundColor: theme.colors.primary, borderColor: theme.colors.secondary}}> Rejoindre </button>
</div>
</div>
<div className='rightContainer'>

@ -1,6 +1,5 @@
.mainContainer{
display: flex;
/* flex-direction: column; */
justify-content: center;
align-items: center;
margin: 50px;
@ -46,6 +45,9 @@
border-radius: 15px;
}
.pseudoDisplay{
margin: 0px !important;
}
.username-display{
display: flex;

@ -1,10 +1,7 @@
import React, { useEffect, useState } from 'react';
import React, { useState, useEffect } from 'react';
import ProfilePDP from '../Components/ProfilePDP';
import SessionService from '../services/SessionService';
import { PlayerProps } from '../types/Player';
import { delay, update } from 'lodash';
import { socket } from '../SocketConfig';
import AuthService from '../services/AuthService';
@ -14,8 +11,8 @@ import Edit from "../res/icon/edit-pen.png"
import Coche from '../res/icon/coche.png'
import Cancel from '../res/icon/cancel.png'
/* Model */
import User from '../model/User';
/* Nav */
import { useNavigate } from 'react-router-dom';
/* Context */
import { useAuth } from '../Contexts/AuthContext';
@ -25,24 +22,36 @@ import Button from 'react-bootstrap/Button';
import Alert from 'react-bootstrap/Alert';
import Modal from 'react-bootstrap/Modal';
import Form from 'react-bootstrap/Form';
import { useNavigate } from 'react-router-dom';
import ProgressBar from 'react-bootstrap/ProgressBar';
import {basePath} from "../AdressSetup"
//@ts-ignore
const Profile = () => {
const navigate = useNavigate();
//let player;
const {user, logout} = useAuth()
// let pseudoNotNull;
// if(user?.pseudo != null){
// pseudoNotNull = user.pseudo;
// }
const [editingUsername, setEditingUsername] = useState(false);
const {isLoggedIn, login, logout, user, setUserData, manager } = useAuth();
const [newUsername, setNewUsername] = useState(user?.pseudo);
//@ts-ignore
useEffect(() => {
if (user == null){
manager.userService.fetchUserInformation().then(([user, loggedIn]) =>{
if (user!=null){
if (loggedIn){
login()
setUserData(user)
}
}
})
}
}, [isLoggedIn]);
const handleLogout = () => {
logout();
navigate(`${basePath}/`);
};
// @ts-ignore
const onUsernameChange = (newUsername) => {
if(user?.pseudo != null){
SessionService.UpdatePseudo(user.pseudo, newUsername)
@ -57,7 +66,103 @@ const Profile = () => {
// Désactiver le mode d'édition
setEditingUsername(false);
};
//* Gestion de la modification du mot de passe :
// Modal de modification du mot de passe
const [showPasswordModal, setShowPasswordModal] = useState(false);
const [showWrongPassword, setShowWrongPassword] = useState(false);
const [showCorrectPassword, setShowCorrectPassword] = useState(false);
// Etat du mot de passe
const [oldPassword, setOldPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
// Etat de l'étape
const [step, setStep] = useState(1);
const [DisableNextStep, setDisableNextStep] = useState(true);
// Etat du nouveau mot de passe
const [confirmNewPassword, setConfirmNewPassword] = useState('');
const [ percent, setpercent] = useState(0);
const handleShowPasswordModal = () => {
setShowPasswordModal(true);
};
const handleClosePasswordModal = () => {
setShowPasswordModal(false);
};
//* Vérification de l'ancien mot de passe :
const handleConfirmedAuth = async () => {
// Vérification de l'ancien mot de passe
if(user){
try {
if (await AuthService.validatePassword(user?.pseudo, oldPassword)) {
console.log('Ancien mot de passe correct.');
setShowWrongPassword(false);
setShowCorrectPassword(true);
setDisableNextStep(false);
setpercent(25);
}
} catch (error) {
console.error(error);
setShowWrongPassword(true);
setShowCorrectPassword(false);
setDisableNextStep(true);
}
}
}
const handleChangeStep = () => {
setShowWrongPassword(false);
setShowCorrectPassword(false);
setpercent(50);
setStep(2)
}
//* Modification du mot de passe :
const handlePasswordChange = async () => {
//Effectuer la modification du mot de passe
// sinon, affichez une erreur
if(user){
if (newPassword === confirmNewPassword) {
await AuthService.updatePassword(user.pseudo, newPassword);
console.log('Changement de mot de passe');
setpercent(100);
setTimeout(async () => {
setShowPasswordModal(false);
}, 1250);
} else {
//les mots de passe ne correspondent pas
console.error("Les mots de passe ne correspondent pas.");
setShowWrongPassword(true);
setTimeout(async () => {
setShowWrongPassword(false);
}, 1250);
}
}
};
//@ts-ignore
const handleOldPasswordChange = (e) => {
setOldPassword(e.target.value);
setpercent(13);
};
//@ts-ignore
const handleNewPasswordChange = (e) => {
setpercent(63);
setNewPassword(e.target.value);
};
//@ts-ignore
const handleConfirmNewPasswordChange = (e) => {
setConfirmNewPassword(e.target.value);
setpercent(75);
};
//* Gestion Modal de suppression :
const [showDeleteModal, setShowDeleteModal] = useState(false);
@ -84,20 +189,18 @@ const Profile = () => {
// Verification de la phrase
if (confirmationPhrase.toLowerCase() === 'supprimer mon compte') {
console.log('Compte supprimé !');
console.log(user);
if(user!= null){
const pseudo = user.pseudo;
AuthService.delAccount(pseudo);
AuthService.logout();
logout();
AuthService.delAccount(pseudo);
}
else{
console.error("l'utilisateur ne peut pas être null")
}
handleCloseDeleteModal();
navigate("/play")
navigate(`${basePath}/`)
} else {
console.error('Phrase de confirmation incorrecte.');
@ -107,7 +210,6 @@ const Profile = () => {
}, 3000);
}
};
return (
<>
@ -134,7 +236,7 @@ const Profile = () => {
</div>
) : (
<div className='username-display'>
<h1>{user?.pseudo}</h1>
<h1 className='pseudoDisplay'>{user?.pseudo}</h1>
<button className='editbutton' onClick={() => setEditingUsername(true)}>
<img src={Edit} alt='edit' width='25' height='25'/>
</button>
@ -143,16 +245,95 @@ const Profile = () => {
}
<hr/>
{!editingUsername ? (
<Button variant="secondary">Modifier le mot de passe</Button>
<Button variant="secondary" onClick={() => setShowPasswordModal(true)}>Modifier le mot de passe</Button>
) : (
<Alert key='info' variant='info' style={{width:'100%'}}>
Vous êtes en mode "édition".
</Alert>
)}
{/* Modal de modification de mdp */}
<Modal show={showPasswordModal} onHide={handleClosePasswordModal}>
<Modal.Header closeButton>
<Modal.Title>{`Étape ${step}`}</Modal.Title>
{/* <ProgressBar animated now={50*step} /> */}
</Modal.Header>
<Modal.Body>
<ProgressBar animated now={percent} />
{step === 1 && (
<>
<p>Entrez votre ancien mot de passe :</p>
<Form.Control
type="password"
placeholder="Ancien mot de passe"
value={oldPassword}
onChange={handleOldPasswordChange}
/>
<Button variant="primary" style={{margin:'15px'}} onClick={handleConfirmedAuth}>
Confirmer
</Button>
</>
)}
{step === 2 && (
<>
<p>Entrez votre nouveau mot de passe :</p>
<Form.Control
type="password"
placeholder="Nouveau mot de passe"
value={newPassword}
onChange={handleNewPasswordChange}
/>
<br/>
<p>Confirmez votre nouveau mot de passe :</p>
<Form.Control
type="password"
placeholder="Confirmez le nouveau mot de passe"
value={confirmNewPassword}
onChange={handleConfirmNewPasswordChange}
/>
</>
)}
{showWrongPassword && (
<Alert variant="danger" style={{ width: '100%' }}>
Ancien mot de passe incorrect.
</Alert>
)}
{showCorrectPassword && (
<Alert variant="success" style={{ width: '100%' }}>
Ancien mot de passe correct.
</Alert>
)}
</Modal.Body>
<Modal.Footer>
{step === 1 && (
<Button variant="secondary" onClick={handleClosePasswordModal}>
Annuler
</Button>
)}
{step === 2 ? (
<Button variant="primary" onClick={handlePasswordChange}>
Modifier le mot de passe
</Button>
) : (
<Button variant="primary" onClick={handleChangeStep} disabled={DisableNextStep}>
Étape suivante
</Button>
)}
</Modal.Footer>
</Modal>
<div className='bottom'>
<>
{!editingUsername && (
<Button variant="danger" onClick={handleShowDeleteModal}>Supprimer</Button>
) }
{/* Modal de suppression */}
<Modal show={showDeleteModal} onHide={handleCloseDeleteModal}>
<Modal.Header closeButton>
<Modal.Title>Confirmation de suppression</Modal.Title>
@ -184,7 +365,10 @@ const Profile = () => {
</Modal>
</>
</div>
</div>
{/* Bouton de déconnexion */}
<div className='logout'>
<Button variant="warning" onClick={handleLogout}>Déconnexion</Button>
</div>
</div>
</>

@ -4,9 +4,11 @@ import { useNavigate } from 'react-router-dom';
import AuthService from '../services/AuthService';
import '../Style/Global.css';
import {basePath} from "../AdressSetup"
const SignUp = () => {
const navigate = useNavigate();
const [error, setError] = useState<string | null>(null);
const [showConfirmation, setShowConfirmation] = useState(false);
@ -32,8 +34,8 @@ const SignUp = () => {
setShowConfirmation(true);
setTimeout(() => {
navigate('/login'); // 3 secondes avant de rediriger vers la page de connexion
}, 3000);
navigate(`${basePath}/login`); // 3 secondes avant de rediriger vers la page de connexion
}, 1250);
}
} catch (error: any) {
setError(error.message);
@ -89,7 +91,7 @@ const SignUp = () => {
</button>
</div>
<p className="forgot-password text-right">
Vous avez déjà un <a href="/login">compte</a> ?
Vous avez déjà un <a href={`${basePath}/login`}>compte</a> ?
</p>
</form>
@ -101,7 +103,7 @@ const SignUp = () => {
{showConfirmation && (
<div className="alert alert-success" role="alert">
Inscription réussie ! Vous serez redirigé vers la page de connexion dans 3 secondes.
Inscription réussie ! Vous serez redirigé vers la page de connexion.
</div>
)}
</div>

@ -1,107 +0,0 @@
.upperInfo{
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
width: 30%;
border-radius: 0px 0px 30px 30px;
border: solid;
border-width: 2px 5px;
background-color: white;
font-size: 30px;
top: 20px;;
}
#mainDiv{
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.paramDiv{
z-index: 1;
position: absolute;
top: 10px;
right: 10px;
}
#graphDiv{
display: flex;
flex-direction: row;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
#bottom-container{
bottom: 0;
background-color: white;
padding:20px;
border-radius: 20px 20px 0px 0px;
}
.nbLaps{
position: absolute;
z-index: 1;
left: 10px;
top :50px;
margin: 10px 20px;
padding: 20px;
border-radius: 15px;
border: solid 2px;
font-size: 30px;
color: #fff;
}
#endgamebutton{
position: absolute;
z-index: 1;
bottom: 0;
right: 25%;
}
.upperInfo,
#bottom-container,
.menuGame {
position: absolute;
z-index: 1;
}
.menuGame{
display: flex;
align-items: space-between;
justify-content: end;
flex-direction: column;
top:30%;
right: 0;
}
.menuGame Button {
margin: 10px;
}
.button{
/*background-color: #85C9C2;*/
border: solid 2px #85C9C2;
border-radius: 10px;
width: 100px;
height: 60px;
}

@ -1,236 +0,0 @@
import React, { useState } from 'react';
import Switch from "react-switch";
/* Style */
import "./SoloGame.css"
import {useTheme} from '../Style/ThemeContext'
/* Component */
import GraphContainer from '../Components/GraphContainer';
import ChoiceBar from '../Components/ChoiceBar';
import ButtonImgNav from '../Components/ButtonImgNav';
import PersonStatus from '../Components/PersonStatus';
import PlayerList from '../Components/PlayerList';
/* Icon */
import Leave from "../res/icon/leave.png";
import Param from "../res/icon/param.png";
import Replay from "../res/icon/replay.png";
import Info from "../res/icon/infoGreen.png";
import Check from "../res/icon/checkboxGreen.png";
import Alpha from "../res/GreekLetters/alphaW.png";
/* nav */
import { Link } from 'react-router-dom';
/* Boostrap */
import Button from 'react-bootstrap/Button';
import Offcanvas from 'react-bootstrap/Offcanvas';
/* Model */
import Stub from '../model/Stub';
import { HiLanguage } from 'react-icons/hi2';
import { Nav, NavDropdown } from 'react-bootstrap';
import { FormattedMessage } from 'react-intl';
import Color from '../model/Color';
import TurnBar from '../Components/TurnBar';
import { useGame } from '../Contexts/GameContext';
//@ts-ignore
const SoloGame = ({locale, changeLocale}) => {
const theme = useTheme();
const [showChoiceBar, setShowChoiceBar] = useState(false);
const [showTurnBar, setShowTurnBar] = useState(false);
const handleNodeClick = (shouldShowChoiceBar: boolean) => {
setShowChoiceBar(shouldShowChoiceBar);
};
const handleShowTurnBar = (shouldShowTurnBar: boolean) => {
setShowTurnBar(shouldShowTurnBar);
};
/* offcanvas */
//? faire une fonction pour close et show en fonction de l'etat du canva ?
//? comment faire pour eviter la recopie de tout le code a chaque canvas boostrap ?
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
// const [showP, setShowP] = useState(false);
// const handleCloseP = () => setShowP(false);
// const handleShowP = () => setShowP(true);
const [showS, setShowS] = useState(false);
const handleCloseS = () => setShowS(false);
const handleShowS = () => setShowS(true);
const handleChange = () => {
if (show){
handleClose()
}
else {
handleShow()
}
};
// const handleChangeP = () => {
// if (showP){
// handleCloseP()
// }
// else {
// handleShowP()
// }
// };
const handleChangeS = () => {
if (showS){
handleCloseS()
}
else {
handleShowS()
}
};
/* Windows open */
//@ts-ignore
const openInNewTab = (url) => { //! avec url ==> dangereux
window.open(url);
};
const [SwitchEnabled, setSwitchEnabled] = useState(false)
const indices = Stub.GenerateIndice()
const { indice, players } = useGame();
return (
<div id="mainDiv">
<TurnBar text="je suis dans la page solo"/>
<div id='graphDiv'>
{/* <GraphContainer onNodeClick={handleNodeClick} handleShowTurnBar={handleShowTurnBar} FromSolo={true}/> */}
</div>
<div className='nbLaps' style={{
backgroundColor: theme.colors.primary,
borderColor: theme.colors.secondary
}}>
Tour : 5
</div>
<div className='paramDiv'>
<button className='button'
style={{
backgroundColor: theme.colors.primary,
borderColor: theme.colors.secondary
}}
onClick={handleChangeS}>
<img src={Param} alt="paramètres" height='40'/>
</button>
</div>
<div className='menuGame'>
<Link to='/info' target='_blank'>
<button className='button'
style={{
backgroundColor: theme.colors.primary,
borderColor: theme.colors.secondary
}}>
<img src={Info} alt="info" height="40"/>
</button>
</Link>
{/* <button className='button' onClick={() => openInNewTab('http://localhost:3000/play')}> //! avec url =={'>'} dangereux
<img src={Check} alt="check" height="40"/>
</button> */}
<Link to='/info' target='_blank'>
<button className='button'
style={{
backgroundColor: theme.colors.primary,
borderColor: theme.colors.secondary
}}>
<img src={Check} alt="check" height="40"/>
</button>
</Link>
<button className='button' onClick={handleChange}
style={{
backgroundColor: theme.colors.primary,
borderColor: theme.colors.secondary
}}>
<img src={Alpha} alt="indice" height="40"/>
</button>
</div>
{/* <Offcanvas show={showP}
onHide={handleCloseP}>
<Offcanvas.Header closeButton>
<Offcanvas.Title>Joueurs</Offcanvas.Title>
<h3>Il y a {players.length} joueurs</h3>
</Offcanvas.Header>
<Offcanvas.Body>
<PlayerList players={players} />
</Offcanvas.Body>
</Offcanvas> */}
<Offcanvas show={show}
onHide={handleClose}
placement='end'
scroll={true}
backdrop={false}
style={{ height: '20%', width: '25%', top: '60vh' }}>
<Offcanvas.Header closeButton>
<Offcanvas.Title>Indice</Offcanvas.Title>
</Offcanvas.Header>
<Offcanvas.Body>
{/* Possède les cheveux noir <u>ou</u> joue au basket */}
{indice?.ToString(locale)}
</Offcanvas.Body>
</Offcanvas>
{
//* canva pour les paramètres
}
<Offcanvas show={showS}
onHide={handleCloseS}
placement='top'
style={{height: '30%', width: '30%', left: '70%' }}>
<Offcanvas.Header closeButton>
<Offcanvas.Title><img src={Param} alt='param'/> Paramètres</Offcanvas.Title>
</Offcanvas.Header>
<Offcanvas.Body>
<Nav className="me-auto">
<NavDropdown
title={<span><HiLanguage/> Language </span>}
className="navbar-title" id="basic-nav-dropdown">
<NavDropdown.Item onClick={() => changeLocale('fr')}>
<FormattedMessage id="languageSelector.french"/>
</NavDropdown.Item>
<NavDropdown.Item onClick={() => changeLocale('en')}>
<FormattedMessage id="languageSelector.english"/>
</NavDropdown.Item>
</NavDropdown>
</Nav>
<label>
<Switch checked={SwitchEnabled} onChange={setSwitchEnabled}/>
<p>Afficher les noeuds possibles</p>
</label>
</Offcanvas.Body>
</Offcanvas>
<div id="bottom-container">
{showChoiceBar && <ChoiceBar />}
</div>
{/*
<div id="endgamebutton" > {/* tmp
<ButtonImgNav dest="/endgame" img={Leave} text='endgame'/>
</div>
*/}
</div>
);
};
export default SoloGame;

@ -0,0 +1,754 @@
import React, { useState, useEffect } from 'react';
import Switch from "react-switch";
import {saveAs} from "file-saver"
/* Style */
import "./InGame.css"
import {useTheme} from '../Style/ThemeContext'
/* Component */
import GraphContainer from '../Components/GraphContainer';
import PlayerList from '../Components/PlayerList';
import TurnBar from '../Components/TurnBar';
/* Icon */
import Param from "../res/icon/param.png";
import Info from "../res/icon/infoGreen.png";
import Check from "../res/icon/checkboxGreen.png";
import MGlass from "../res/icon/magnifying-glass.png";
import Reset from "../res/icon/reset.png";
import Oeye from "../res/icon/eye.png";
import Ceye from "../res/icon/hidden.png";
import ImgBot from "../res/img/bot.png";
import detective from "../res/img/tuto/tuto-detective.jpg";
import ava from "../res/img/tuto/tuto-ava2.png";
import indicetxt from "../res/img/tuto/tuto-indiceTxt.png"
import rep from "../res/img/tuto/tuto-rep.png";
import joueurs from "../res/img/tuto/tuto-joueurs.png";
import graph from "../res/img/tuto/tuto-graph.png";
import step1 from "../res/img/tuto/tuto2-1.png";
import step2 from "../res/img/tuto/tuto2-2.png";
import step3 from "../res/img/tuto/tuto2-3.png";
import step4 from "../res/img/tuto/tuto2-4.png";
import step5 from "../res/img/tuto/tuto2-5.png";
import step6 from "../res/img/tuto/tuto2-6.png";
/* nav */
import { Link, Navigate, useNavigate, useNavigationType } from 'react-router-dom';
/* Boostrap */
import Offcanvas from 'react-bootstrap/Offcanvas';
import Modal from 'react-bootstrap/Modal';
import Card from 'react-bootstrap/Card';
import Button from 'react-bootstrap/Button';
/* Model */
import Stub from '../model/Stub';
import { useGame } from '../Contexts/GameContext';
import { socket } from '../SocketConfig';
import { Network } from 'vis-network';
import {generateLatexCode, generateLatexCodeEnigme} from '../Script/LatexScript';
import Pair from '../model/Pair';
import Indice from '../model/Indices/Indice';
import {basePath} from "../AdressSetup"
import TutorialGraph from '../Components/TutorialGraph';
import { useAuth } from '../Contexts/AuthContext';
import EasyBot from '../model/EasyBot';
import { set } from 'lodash';
import { FormattedMessage } from 'react-intl';
let cptNavigation = 0
//@ts-ignore
const Tutorial = ({locale, changeLocale}) => {
const {personNetwork, person, indices, players, setPlayersData, indice, actualPlayerIndex} = useGame()
const {user} = useAuth()
const theme = useTheme();
const navigate = useNavigate()
const [greyForEveryone, setGreyForEveryone] = useState<() => void>(() => {});
const setGreyForEveryoneData = (func: () => void) => {
setGreyForEveryone(func)
}
const navigationType = useNavigationType()
cptNavigation++
if (cptNavigation % 2 == 0){
if (navigationType.toString() == "POP"){
socket.emit("player quit")
navigate(`${basePath}/`)
}
}
//* Historique
const [history, setHistory] = useState<string[]>([]);
const [showLast, setShowLast] = useState(false)
const [askedWrong, setAskedWrong] = useState(false)
const [importToPdf, setImportToPdf] = useState(false)
const [importToJSON, setImportToJSON] = useState(false)
const [tutoStep, setTutoStep] = useState(0)
const setTutoStepData = (step: number) => {
setTutoStep(step)
}
const setImportToJSONData = (imp: boolean) => {
setImportToJSON(imp)
}
const setImportToPdfData = (imp: boolean) => {
setImportToPdf(imp)
}
// Fonction pour ajouter un élément à l'historique
const addToHistory = (message: string) => {
setHistory(prevHistory => [...prevHistory, message]);
};
const setShowLastData = () =>{
setLastVisible(!showLast);
setShowLast(!showLast);
}
const setAskedWrongData = (askedWrong: boolean) => {
setAskedWrong(askedWrong)
}
useEffect(() => {
const historyContainer = document.getElementById('history-container');
if (historyContainer) {
historyContainer.scrollTop = historyContainer.scrollHeight;
}
}, [history]);
useEffect(() => {
if (user == null){
return
}
setPlayersData([user, new EasyBot("Scooby-Doo", "Scooby-Doo", ImgBot), new EasyBot("Batman", "Batman", ImgBot)])
}, [])
const [showChoiceBar, setShowChoiceBar] = useState(false);
const [showTurnBar, setShowTurnBar] = useState(false);
const [turnBarText, setTurnBarText] = useState("");
const [playerTouched, setPlayerTouched] = useState(-2)
const [playerIndex, setPlayerIndex] = useState(-2)
const [network, setNetwork] = useState<Network | null>(null)
const [networkEnigme, setNetworkEnigme] = useState<Map<number, Pair<Indice, boolean>[]> | null>(null)
const setNetworkData = (network: Network) => {
setNetwork(network)
}
const setNetworkEnigmeData = (networkEnigme: Map<number, Pair<Indice, boolean>[]>) => {
setNetworkEnigme(networkEnigme)
}
const handleNodeClick = (shouldShowChoiceBar: boolean) => {
setShowChoiceBar(shouldShowChoiceBar);
};
const handleSetPlayerTouched = (newPlayerTouched: number) => {
setPlayerTouched(newPlayerTouched);
};
const handleShowTurnBar = (shouldShowTurnBar: boolean) => {
setShowTurnBar(shouldShowTurnBar);
};
const handleTurnBarTextChange = (newTurnBarText: string) =>{
setTurnBarText(newTurnBarText)
}
const setPlayerIndexData = (playerIndex: number) => {
setPlayerIndex(playerIndex)
}
const resetGraph = () => {
setisLoading(true);
socket.emit("reset graph", socket.id)
setTimeout(() => {
setisLoading(false);
}, 2000);
}
/* offcanvas */
//? faire une fonction pour close et show en fonction de l'etat du canva ?
//? comment faire pour eviter la recopie de tout le code a chaque canvas boostrap ?
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const [showP, setShowP] = useState(false);
const handleCloseP = () => setShowP(false);
const handleShowP = () => setShowP(true);
const [showS, setShowS] = useState(false);
const handleCloseS = () => setShowS(false);
const handleShowS = () => setShowS(true);
const [cptTour, setcptTour] = useState(0);
const [LastVisible, setLastVisible] = useState(false);
const [isLoading, setisLoading] = useState(false);
//@ts-ignore
const changecptTour = (newcptTour) => {
setcptTour(newcptTour);
};
const handleChange = () => {
if (show){
handleClose()
}
else {
handleShow()
}
};
const handleChangeS = () => {
if (showS){
handleCloseS()
}
else {
handleShowS()
}
};
const eye = LastVisible ? Oeye : Ceye; //icon que l'on affiche pour l'oeil : fermé ou ouvert.
/* Windows open */
//@ts-ignore
const openInNewTab = (url) => { //! avec url ==> dangereux
window.open(url);
};
const [SwitchEnabled, setSwitchEnabled] = useState(false)
const allIndices = Stub.GenerateIndice()
const nbPlayer = players.length;
const navdeduc = 'deduc?actualId=' + actualPlayerIndex + '&nbPlayer=' + nbPlayer;
//* gestion demo */
const [showM, setShowM] = useState(false);
const [showTuto2, setShowTuto2] = useState(false);
const [showTuto21, setShowTuto21] = useState(false);
const [showTuto22, setShowTuto22] = useState(false);
const [showTuto3, setShowTuto3] = useState(false);
const handleCloseM = () => {
setShowM(false);
handleShowHelp()
}
const handleShowM = () => setShowM(true);
const handleCloseHelp = () => {
switch(tutoStep){
case 0:
setShowTuto2(false);
break;
case 1:
setShowTuto21(false);
break;
case 2:
setShowTuto22(false);
break;
case 3:
setShowTuto3(false);
break;
}
}
const handleShowHelp = () => {
switch(tutoStep){
case 0:
setShowTuto2(true);
break;
case 1:
setShowTuto21(true);
break;
case 2:
setShowTuto22(true);
break;
case 3:
setShowTuto3(true);
break;
}
}
const handleCloseTuto2 = () => setShowTuto2(false);
const handleShowTuto2 = () => setShowTuto2(true);
const handleCloseTuto21 = () => setShowTuto21(false);
const handleShowTuto21 = () => setShowTuto21(true);
const handleCloseTuto22 = () => setShowTuto22(false);
const handleShowTuto22 = () => setShowTuto22(true);
const handleCloseTuto3 = () => setShowTuto3(false);
const handleShowTuto3 = () => setShowTuto3(true);
const [step, setStep] = useState(-1);
//@ts-ignore
const displayModalstep = (step: number) => {
//* for step 2
setStep(0); // remise a 0 de step
switch(step) {
case 1:
setShowTuto21(true);
break;
case 2:
setShowTuto22(true);
break;
case 3:
setShowTuto3(true);
break;
}
}
useEffect(() => {
handleShowM();
}, [])
return (
<div id="mainDiv">
{showTurnBar && <TurnBar text={turnBarText}/>}
<div id='graphDiv'>
<TutorialGraph tutoStep={tutoStep}
setTutoStep={setTutoStepData}
handleShowTurnBar={handleShowTurnBar}
handleTurnBarTextChange={handleTurnBarTextChange}
addToHistory={addToHistory}
setPlayerTouched={handleSetPlayerTouched}
playerTouched={playerTouched}
setNetwork={setNetworkData}
showLast={showLast}
setPlayerIndex={setPlayerIndexData}
setGreyForEveryone={setGreyForEveryoneData}
displayModalstep={displayModalstep}/>
</div>
<div className='historique' id="history-container">
{history.map((item, index) => (
<div key={index}>{item}</div>
))}
</div>
<div className='menuGame'>
{/* <Button variant="primary" onClick={handleShowM}>
tuto 1
</Button>
<Button variant="primary" onClick={handleShowTuto2} disabled={tuto1disable}>
tuto 2
</Button> */}
<Button variant="primary" onClick={handleShowHelp}>
<FormattedMessage id='aide'/>
</Button>
<Link to={`${basePath}/info`} target='_blank'>
<button className='button'
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary
}}>
<img src={Info} alt="info" height="40"/>
</button>
</Link>
<button className='button' onClick={handleChange}
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary
}}>
<img src={MGlass} alt="indice" height="40"/>
</button>
<button className='button' onClick={setShowLastData}
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary
}}>
<img src={ eye } alt="indice" height="40"/>
</button>
</div>
<div className='playerlistDiv'>
<PlayerList players={players} setPlayerTouched={handleSetPlayerTouched} playerTouched={playerTouched} playerIndex={playerIndex} askedWrong={askedWrong} greyForEveryone={greyForEveryone}/>
</div>
<Offcanvas show={show}
onHide={handleClose}
placement='end'
scroll={true}
backdrop={false}
style={{ height: '20%', width: '25%', top: '60vh' }}>
<Offcanvas.Header closeButton>
<Offcanvas.Title><FormattedMessage id='indice'/></Offcanvas.Title>
</Offcanvas.Header>
<Offcanvas.Body>
{indice?.ToString(locale)}
</Offcanvas.Body>
</Offcanvas>
<Modal
show={showM}
onHide={handleCloseM}
backdrop="static"
keyboard={false}
size="lg"
style={{ maxHeight: '100vh'}}>
<Modal.Header>
<Modal.Title><FormattedMessage id='tutorial.title'/></Modal.Title>
</Modal.Header>
<Modal.Body style={{ display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
{step === -1 && (
<Card style={{ width: '90%', display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card.Img variant="top" src={detective} style={{width:'300px', height:'300px'}}/>
<Card.Body>
<Card.Title> <FormattedMessage id='tuto.title.1'/> </Card.Title>
<Card.Text>
<FormattedMessage id='tuto.txt.1.1'/> <br/>
<FormattedMessage id='tuto.txt.1.2'/> <br/>
<FormattedMessage id='tuto.txt.1.3'/> <br/>
<FormattedMessage id='tuto.txt.1.4'/> <br/>
<FormattedMessage id='tuto.txt.1.5'/> <br/>
<i><FormattedMessage id='tuto.txt.1.6'/></i>
</Card.Text>
</Card.Body>
</Card>
)}
{step === 0 && (
<Card style={{ width: '90%', display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card.Img variant="top" src={ava} style={{width:'300px', height:'300px'}}/>
<Card.Body>
<Card.Title> <FormattedMessage id='tuto.title.2'/> </Card.Title>
<Card.Text>
<FormattedMessage id='tuto.txt.2.1'/>
<br />
<FormattedMessage id='tuto.txt.2.2' /><b>Ava</b><FormattedMessage id='tuto.txt.2.2.1'/><b>40 <FormattedMessage id='age_indice_end'/></b><FormattedMessage id='tuto.txt.2.2.2'/><b><FormattedMessage id='basketball'/></b><FormattedMessage id='tuto.txt.2.2.3'/><b><FormattedMessage id='tennis'/></b><FormattedMessage id='tuto.txt.2.2.4'/><b><FormattedMessage id='redhead'/></b><FormattedMessage id='tuto.txt.2.2.5'/><b>2 <FormattedMessage id='nb_friends_indice_end'/></b> : Carter <FormattedMessage id='and'/> Liam.
</Card.Text>
</Card.Body>
</Card>
)}
{step === 1 && (
<Card style={{ width: '90%', display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card.Img variant="top" src={indicetxt} style={{width:'300px', height:'auto'}}/>
<Card.Body>
<Card.Title><FormattedMessage id='tuto.title.3'/></Card.Title>
<Card.Text>
<FormattedMessage id='tuto.txt.3.1'/>
<br />
"<u><FormattedMessage id='tuto.txt.3.2'/></u>".
</Card.Text>
</Card.Body>
</Card>
)}
{step === 3 && (
<Card style={{ width: '90%', display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card.Img variant="top" src={joueurs} style={{width:'auto', height:'300px'}}/>
<Card.Body>
<Card.Title><FormattedMessage id='tuto.title.4'/></Card.Title>
<Card.Text>
<FormattedMessage id='tuto.txt.4.1'/>
<br />
<FormattedMessage id='tuto.txt.4.2'/>
</Card.Text>
</Card.Body>
</Card>
)}
{step === 4 && (
<Card style={{ width: '90%', display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card.Img variant="top" src={rep} style={{width:'300px', height:'300px'}}/>
<Card.Body>
<Card.Title><FormattedMessage id='tuto.title.5'/></Card.Title>
<Card.Text>
<FormattedMessage id='tuto.txt.5.1'/>
<br />
<ul>
<li>
<FormattedMessage id='majUn'/> <u><FormattedMessage id='square'/></u> <FormattedMessage id='tuto.txt.5.2'/>.
</li>
<li>
<FormattedMessage id='majUn'/> <u><FormattedMessage id='round'/></u> <FormattedMessage id='tuto.txt.5.3'/>.
</li>
</ul>
<FormattedMessage id='tuto.txt.5.4'/><br />
<FormattedMessage id='tuto.txt.5.5.1'/>(<u><FormattedMessage id='color.green'/></u>) <FormattedMessage id='tuto.txt.5.5.2'/>.
<br/><FormattedMessage id='tuto.txt.5.6'/> (<u><FormattedMessage id='color.yellow'/></u>).
<br/><FormattedMessage id='tuto.txt.5.7.1'/><u><FormattedMessage id='tuto.txt.5.7.2'/></u>.
</Card.Text>
</Card.Body>
</Card>
)}
{step === 5 && (
<Card style={{ width: '90%', display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card.Header>
<button className='button'
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary
}}>
<img src={Info} alt="info" height="40"/>
</button>
</Card.Header>
<Card.Body>
<Card.Title><FormattedMessage id='tuto.title.6'/></Card.Title>
<Card.Text>
<FormattedMessage id='tuto.txt.6.1.1'/><b><FormattedMessage id='tuto.txt.6.1.2'/></b><FormattedMessage id='tuto.txt.6.1.3'/>
</Card.Text>
</Card.Body>
</Card>
)}
{step === 2 && (
<Card style={{ width: '90%', display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card.Header>
<button className='button'
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary
}}>
<img src={MGlass} alt="info" height="40"/>
</button>
</Card.Header>
<Card.Body>
<Card.Title><FormattedMessage id='tuto.title.7'/></Card.Title>
<Card.Text>
<FormattedMessage id='tuto.txt.7.1'/>
</Card.Text>
</Card.Body>
</Card>
)}
{step === 6 && (
<Card style={{ width: '90%', display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card.Img variant="top" src={graph} style={{width:'auto', height:'300px'}}/>
<Card.Body>
<Card.Title><FormattedMessage id='tuto.title.8'/></Card.Title>
<Card.Text>
<FormattedMessage id='tuto.txt.8.1'/>
<br/>
<FormattedMessage id='tuto.txt.8.2'/>
<br/>
<FormattedMessage id='tuto.txt.8.3'/>
</Card.Text>
</Card.Body>
</Card>
)}
</Modal.Body>
<Modal.Footer style={{display:'flex'}}>
<div style={{ width:'100%', display:'flex', justifyContent:'start'}}>
<label style={{ color:'gray'}}><FormattedMessage id='step'/> {step+1}/7</label>
</div>
{ step != -1 && (<Button variant="primary" onClick={() => setStep(step - 1)}><FormattedMessage id='previous'/></Button>)}
{ step === 6 ? (<Button variant="primary" onClick={handleCloseM}><FormattedMessage id='continue'/></Button>) :
<Button variant="primary" onClick={() => setStep(step + 1)}><FormattedMessage id='next'/></Button>}
</Modal.Footer>
</Modal>
<Modal
show={showTuto2}
onHide={handleCloseTuto2}
backdrop="static"
keyboard={false}
size="lg"
style={{ maxHeight: '100vh'}}>
<Modal.Header>
<Modal.Title><FormattedMessage id='tutorial.title'/></Modal.Title>
</Modal.Header>
<Modal.Body style={{ display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card style={{ width: '100%', display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card.Img variant="top" src={step1} style={{width:'auto', height:'300px'}}/>
<Card.Body>
<Card.Title><FormattedMessage id='tuto.title.9'/></Card.Title>
<Card.Text>
<FormattedMessage id='tuto.txt.9.1'/>
<br/>
<FormattedMessage id='tuto.txt.9.2.1'/><b>Scooby-Doo</b><FormattedMessage id='tuto.txt.9.2.2'/><b>Violet</b><FormattedMessage id='tuto.txt.9.2.3'/>
</Card.Text>
</Card.Body>
</Card>
</Modal.Body>
<Modal.Footer style={{display:'flex'}}>
<Button variant="primary" onClick={handleCloseTuto2}><FormattedMessage id='compris'/></Button>
</Modal.Footer>
</Modal>
<Modal
show={showTuto21}
onHide={handleCloseTuto21}
backdrop="static"
keyboard={false}
size="lg"
style={{ maxHeight: '100vh'}}>
<Modal.Header>
<Modal.Title><FormattedMessage id='tutorial.title'/></Modal.Title>
</Modal.Header>
<Modal.Body style={{ display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
{step === 0 && (
<Card style={{ width: '100%', display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card.Img variant="top" src={step2} style={{width:'300px', height:'auto'}}/>
<Card.Body>
<Card.Title><FormattedMessage id='tuto.title.10'/></Card.Title>
<Card.Text>
<FormattedMessage id='tuto.txt.10.1.1'/> <u><FormattedMessage id='tuto.txt.10.1.2'/></u><FormattedMessage id='tuto.txt.10.1.3'/><i><FormattedMessage id='tuto.txt.10.1.4'/></i><FormattedMessage id='tuto.txt.10.1.5'/>
<br/>
<FormattedMessage id='tuto.txt.10.2'/>
</Card.Text>
</Card.Body>
</Card>
)}
{step === 1 && (
<Card style={{ width: '100%', display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card.Img variant="top" src={step3} style={{width:'200px', height:'auto'}}/>
<Card.Body>
<Card.Title><FormattedMessage id='tuto.title.11'/></Card.Title>
<Card.Text>
<FormattedMessage id='tuto.txt.11.1.1'/><b><FormattedMessage id='non'/></b><FormattedMessage id='tuto.txt.11.1.2'/>
<br/>
<FormattedMessage id='tuto.txt.11.2.1'/><b><FormattedMessage id='tuto.txt.11.2.2'/></b><FormattedMessage id='tuto.txt.11.2.3'/>
<br/>
<FormattedMessage id='tuto.txt.11.3'/>
</Card.Text>
</Card.Body>
</Card>
)}
{step === 2 && (
<Card style={{ width: '100%', display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card.Img variant="top" src={step4} style={{width:'300px', height:'auto'}}/>
<Card.Body>
<Card.Title><FormattedMessage id='tuto.title.12'/></Card.Title>
<Card.Text>
<FormattedMessage id='tuto.txt.12.1.1'/><u><FormattedMessage id='tuto.txt.12.1.2'/></u><FormattedMessage id='tuto.txt.12.1.3'/>
<br/>
<FormattedMessage id='tuto.txt.12.2'/>
</Card.Text>
</Card.Body>
</Card>
)}
</Modal.Body>
<Modal.Footer style={{display:'flex'}}>
<div style={{ width:'100%', display:'flex', justifyContent:'start'}}>
<label style={{ color:'gray'}}><FormattedMessage id='step'/> {step+1}/3</label>
</div>
{ step != 0 && (<Button variant="primary" onClick={() => setStep(step - 1)}><FormattedMessage id='previous'/></Button>)}
{ step === 2 ? (<Button variant="primary" onClick={handleCloseTuto21}><FormattedMessage id='close'/></Button>) :
<Button variant="primary" onClick={() => setStep(step + 1)}><FormattedMessage id='next'/></Button>}
</Modal.Footer>
</Modal>
<Modal
show={showTuto22}
onHide={handleCloseTuto22}
backdrop="static"
keyboard={false}
size="lg"
style={{ maxHeight: '100vh'}}>
<Modal.Header>
<Modal.Title><FormattedMessage id='tutorial.title'/></Modal.Title>
</Modal.Header>
<Modal.Body style={{ display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card style={{ width: '100%', display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card.Img variant="top" src={step5} style={{width:'300px', height:'auto'}}/>
<Card.Body>
<Card.Title><FormattedMessage id='tuto.title.13'/></Card.Title>
<Card.Text>
<FormattedMessage id='tuto.txt.13.1.1'/><b><FormattedMessage id='tuto.txt.13.1.2'/></b><FormattedMessage id='tuto.txt.13.1.3'/>
<br/>
<FormattedMessage id='tuto.txt.13.2.1'/><u><FormattedMessage id='tuto.txt.13.2.2'/></u><FormattedMessage id='tuto.txt.13.2.3'/>
<br/>
<FormattedMessage id='tuto.txt.13.3.1'/><b>Liam</b> <FormattedMessage id='tuto.txt.13.3.2'/>
</Card.Text>
</Card.Body>
</Card>
</Modal.Body>
<Modal.Footer style={{display:'flex'}}>
<Button variant="primary" onClick={handleCloseTuto22}><FormattedMessage id='compris'/></Button>
</Modal.Footer>
</Modal>
<Modal
show={showTuto3}
onHide={handleCloseTuto3}
backdrop="static"
keyboard={false}
size="lg"
style={{ maxHeight: '100vh'}}>
<Modal.Header>
<Modal.Title>The End Game</Modal.Title>
</Modal.Header>
<Modal.Body style={{ display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card style={{ width: '100%', display:'flex', justifyContent:'center', alignItems:'center', flexDirection:'column'}}>
<Card.Img variant="top" src={step6} style={{width:'250px', height:'auto'}}/>
<Card.Body>
<Card.Title><FormattedMessage id='tuto.title.14'/></Card.Title>
<Card.Text>
<FormattedMessage id='tuto.txt.14.1'/>
<br/>
<FormattedMessage id='tuto.txt.14.2'/>
<ul>
<li>
<FormattedMessage id='tuto.txt.14.3'/><i>{indices[1]?.ToString(locale)}</i>.
</li>
<li>
<FormattedMessage id='tuto.txt.14.4'/><i>{indices[2]?.ToString(locale)}</i>.
</li>
<li>
<FormattedMessage id='tuto.txt.14.5'/><i>{indices[0]?.ToString(locale)}</i>.
</li>
</ul>
<FormattedMessage id='tuto.txt.14.6.1'/><b><FormattedMessage id='tuto.txt.14.6.2'/></b><FormattedMessage id='tuto.txt.14.6.3'/><b><FormattedMessage id='tuto.txt.14.6.4'/></b><FormattedMessage id='tuto.txt.14.6.5'/>
</Card.Text>
</Card.Body>
</Card>
</Modal.Body>
<Modal.Footer style={{display:'flex'}}>
<Button variant="primary" onClick={handleCloseTuto3}><FormattedMessage id='compris'/></Button>
</Modal.Footer>
</Modal>
</div>
);
};
export default Tutorial;

@ -10,19 +10,19 @@ import Indice from "../model/Indices/Indice";
import { ColorToString, SportToString } from "../model/EnumExtender";
import GraphCreator from "../model/Graph/GraphCreator";
import { DataSet, Network } from "vis-network";
import Pair from "../model/Pair";
function generateLatexCode(personsNet : PersonNetwork, choosenPerson : Person, choosenIndices : Indice[], network: Network): string {
let latexCode = "";
//*Setup
latexCode += "\\documentclass[11pt]{article}\n"
latexCode += "\\usepackage[landscape]{geometry}\n"
latexCode += "\\usepackage{fullpage}\n"
latexCode += "\\usepackage{times}\n"
latexCode += "\\usepackage{tikz}\n"
latexCode += "\\usepackage{paralist}\n"
latexCode += "\\usepackage{geometry}\n"
latexCode += "\\usetikzlibrary {shapes.multipart}\n"
latexCode += "\\geometry{margin=0.5cm}\n"
latexCode += "\\newcommand{\\Basketball}{\\includegraphics[width=.5cm]{ballon-de-basket.png}}\n"
@ -37,7 +37,8 @@ function generateLatexCode(personsNet : PersonNetwork, choosenPerson : Person, c
latexCode+= "\\thispagestyle{empty}\n"
latexCode+= "Voici le graphe de SocialGraphe\n"
latexCode+= "\\begin{center}\n"
latexCode+= "\\begin{tikzpicture}[scale=.17]\n"
latexCode+= "\\resizebox{.65\\textwidth}{!}{\n"
latexCode+= "\\begin{tikzpicture}[scale=.18]\n"
personsNet.getPersons().forEach((person, index) => {
@ -47,8 +48,8 @@ function generateLatexCode(personsNet : PersonNetwork, choosenPerson : Person, c
const nodeId = person.getId().toString();
const position = nodesData[nodeId];
if (position) {
const x = (position.x / 9).toFixed(2); // Arrondir à 2 décimales
const y = (position.y / 9).toFixed(2);
const x = (position.x / 5).toFixed(2); // Arrondir à 2 décimales
const y = (position.y / 5).toFixed(2);
latexCode += ` \\node[draw, circle split, align=center] (${person.getId()}) at (${x},${y}) { ${person.getName()} ${person.getAge()} \\nodepart{lower}`;
latexCode += `${ColorToString(person.getColor(), "fr")} \\\\`
@ -63,10 +64,10 @@ function generateLatexCode(personsNet : PersonNetwork, choosenPerson : Person, c
person.getFriends().forEach((friend) => {
latexCode += ` \\draw (${person.getId()}) -- (${friend.getId()});\n`;
});
console.log(person.getFriends().length);
});
latexCode += "\\end{tikzpicture}\n";
latexCode += "}\n"
latexCode += "\\end{center}\n";
//* Zone d'énoncé :
@ -85,11 +86,123 @@ function generateLatexCode(personsNet : PersonNetwork, choosenPerson : Person, c
latexCode += "\\end{compactitem}\n"
//* Solution
latexCode += "% Solution : " + choosenPerson.getName() + "\n";
latexCode += "Solution : " + choosenPerson.getName() + "\n";
latexCode += "\\end{document}\n"
return latexCode
}
function generateLatexCodeEnigme(personsNet : PersonNetwork, choosenPerson : Person, choosenIndices : Indice[], network: Network, map : Map<number, Pair<Indice, boolean>[]>): string {
let latexCode = "";
//*Setup
latexCode += "\\documentclass[11pt]{article}\n"
latexCode += "\\usepackage[landscape]{geometry}\n"
latexCode += "\\usepackage{fullpage}\n"
latexCode += "\\usepackage{times}\n"
latexCode += "\\usepackage{tikz}\n"
latexCode += "\\usepackage{paralist}\n"
latexCode += "\\usepackage{geometry}\n"
latexCode += "\\usetikzlibrary {shapes.multipart}\n"
latexCode += "\\geometry{margin=0.5cm}\n"
latexCode += "\\newcommand{\\Basketball}{\\includegraphics[width=.5cm]{ballon-de-basket.png}}\n"
latexCode += "\\newcommand{\\Football}{\\includegraphics[width=.4cm]{ballon-de-foot.png}}\n"
latexCode += "\\newcommand{\\Bowling}{\\includegraphics[width=.5cm]{bowling.png}}\n"
latexCode += "\\newcommand{\\Baseball}{\\includegraphics[width=.5cm]{baseball.png}}\n"
latexCode += "\\newcommand{\\Tennis}{\\includegraphics[width=.5cm]{tennis.png}}\n"
//** Header
latexCode+= "\\begin{document}\n"
latexCode+= "\\thispagestyle{empty}\n"
latexCode+= "Voici le graphe de SocialGraphe\n"
latexCode+= "\\begin{center}\n"
latexCode+= "\\resizebox{.65\\textwidth}{!}{\n"
latexCode+= "\\begin{tikzpicture}[scale=.18]\n"
personsNet.getPersons().forEach((person, index) => {
var nodesData = network.getPositions();
// Obtenir les coordonnées du nœud
const nodeId = person.getId().toString();
const position = nodesData[nodeId];
if (position) {
const x = (position.x / 9).toFixed(2); // Arrondir à 2 décimales
const y = (position.y / 9).toFixed(2);
latexCode += ` \\node[draw, circle split, align=center] (${person.getId()}) at (${x},${y}) { ${person.getName()} ${person.getAge()} \\nodepart{lower}`;
latexCode += `${ColorToString(person.getColor(), "fr")} \\\\`
person.getSports().forEach((sport) => { latexCode += ` \\${SportToString(sport, 'fr')}{}` });
latexCode += "};\n";
} else {
console.error(`Les coordonnées du nœud ${nodeId} ne sont pas disponibles.`);
}
});
personsNet.getPersons().forEach((person) => {
person.getFriends().forEach((friend) => {
latexCode += ` \\draw (${person.getId()}) -- (${friend.getId()});\n`;
});
});
latexCode += "\\end{tikzpicture}\n";
latexCode += "}\n"
latexCode += "\\end{center}\n";
//* Zone d'énoncé :
latexCode += "\n\n\\paragraph{Première énigme}\n"
latexCode += "Trouver qui est le coupable avec les indices suivants.\n"
latexCode += "\\begin{compactitem}\n"
const personIndice = new Map<number, string[]>()
choosenIndices.forEach((i, index) => {
personIndice.set(index, [])
})
map.forEach((pairs, index) => {
pairs.forEach((pair) => {
const person = personsNet.getPersons().find((n) => index == n.getId())
const indice = choosenIndices.findIndex((i) => pair.first.getId() == i.getId())
if (person != undefined && indice != -1){
let string = "L'indice numéro " + (indice + 1) + " répond "
if (pair.second){
string += "vrai "
}
else{
string += "faux "
}
string += "pour " + person.getName()
personIndice.get(indice)?.push(string)
}
})
});
personIndice.forEach((indices, index) => {
latexCode += `\\item Indice ${index + 1}:\n`
indices.forEach((string) => {
latexCode += `\\item ${string}.\n`
})
})
latexCode += "\\end{compactitem}\n"
//* Solution
latexCode += "Solution : " + choosenPerson.getName() + "\n";
latexCode += "\\begin{compactitem}\n"
choosenIndices.forEach((indices, index) => {
latexCode += `\\item Indice ${index + 1}: ${indices.ToString("fr")}\n`
})
latexCode += "\\end{compactitem}\n"
latexCode += "\\end{document}\n"
return latexCode
}
export default generateLatexCode
export {generateLatexCode, generateLatexCodeEnigme}

@ -59,6 +59,16 @@
justify-content: center;
}
.playerNameDisplay{
display: flex;
align-items: center;
justify-content: center;
background-color:#fff;
border: solid 3px #0064E0;
border-radius: 15px;
width: 100%;
}
/**Form**/
.form-container {
/* background-color: #D7D4C6; */
@ -74,6 +84,8 @@ form{
columns: 2 auto;
}
/* LeaderBoard */
.LeaderBoardiv{
border: solid;

@ -1,6 +1,9 @@
{
"languageSelector.french": "French",
"languageSelector.english": "English",
"languageSelector.espagnol": "Espagnol",
"languageSelector.portuguese": "Portuguese",
"languageSelector.russian": "Russian",
"home.histoire.title": "HISTORY",
"home.histoire": "Cryptozoology studies the traces of legendary monsters: cryptids. Yetis, Chupacabra, beast of the gevaudan, Dahut, etc. , are very serious topics for you… Cross the clues and be the first to discover them!",
@ -23,6 +26,13 @@
"sign_up":" Sign up ",
"log_out":" Log out ",
"indice":"Hint",
"param":"Settings",
"sfx":"Activate sounds",
"aide":"Help",
"majUn":"A",
"join" : "Join",
"create_room" : "Create room",
"play_solo" : "Single player",
@ -41,6 +51,10 @@
"redhead": "Red",
"brown": "Brown",
"oui": "yes",
"non": "no",
"and": "and",
"or": "or",
"or_sport": "or",
@ -65,15 +79,80 @@
"sport_start": "The suspect plays at least",
"sport_end": "",
"navbar.play" : "Play",
"navbar.presentation":"Presentation",
"navbar.info":"Information",
"hard": "Hard",
"medium": "Medium",
"strong": "Strong",
"weak": "Weak",
"none": "None",
"turn" : "Round",
"square":"square",
"round":"round",
"step": "Step",
"previous": "Previous",
"next": "Next",
"continue": "Continue",
"compris": "Understood!",
"close": "Close",
"regenerate":"Regenarte graph",
"BackHome" : "Back to home",
"askeveryone" : "Ask everyone",
"color.green":"Green",
"color.blue":"Blue",
"color.red":"Red",
"color.yellow":"Yellow",
"play.jouerseul": "Play alone",
"play.enigme": "Solve an enigma",
"play.enigme.easy": "Easy",
"play.enigme.medium": "Intermediate",
"play.enigme.hard": "Hard",
"play.create": "Create a game",
"play.tuto": "Tutorial",
"play.return": "Return to game",
"lobbies.all":"All",
"lobbies.dispo":"Disponible",
"placeholder.searchLobby": "Search for a lobby...",
"score.nbPlayed": "Games Played",
"score.best": "Best Score",
"score.moy": "Average Attempts",
"score.NbWin": "Number of Wins",
"score.ratio": "Win/Loss Ratio",
"score.bestTmp": "Best Time",
"score.moyTmp": "Average Time",
"score.nothing": "Nothing for the moment",
"score.titre.easy": "Easy Enigma",
"score.titre.int": "Intermediate Enigma",
"score.titre.hard": "Hard Enigma",
"score.online": "Online",
"score.tab.stat" : "Your Stats",
"score.tab.quoti" : "Daily",
"score.tab.hebdo" : "Weekly",
"lobby.players" : "Players",
"lobby.bienvenue": "Welcome to your lobby!",
"lobby.wait": "Wait for all your friends to join before starting the game.",
"lobby.invite": "Invite friends",
"lobby.copyLink": "Link copied",
"lobby.nbNode": "Select the number of nodes (between 20 and 60)",
"lobby.start": "Start the game!",
"game.yourTurn":"It's your turn !",
"game.wrong":"Wrong choice, put a square !",
"informations" : "Information",
"info.intro.title":"Introduction to the game :",
@ -111,6 +190,11 @@
"hair.chatain":"Brown",
"hair.noir":"Black",
"param.node": "Select the number of nodes (between 20 and 50)",
"param.clue": "Select the number of clues (between 3 and 6)",
"param.valid": "Validate",
"info.composant.sport.title":"Sports",
"info.composant.sport":"The characters' hobbies are represented by five sports respectively :",
"info.composant.baseball":"Baseball",
@ -129,6 +213,15 @@
"info.objectif.t3":"Counter-manipulation",
"info.objectif.t3.text":"If a player places a square token, the questioner must also play his game by placing a square token of his color on a node of the graph. Countermanipulation becomes a formidable weapon to divert the accusation and sow confusion.",
"info.interface": "Game Interface",
"info.interface.text": "For each game, you will have certain interface elements available:",
"info.interface.param": "The 'Settings' button allows the display and management of various game settings, such as enabling or disabling sound effects. It is also possible to choose the number of nodes for the 'Enigma' game mode using this button.",
"info.interface.reset": "The 'Reset' button resets the current state of the graph. If you have moved it too much or want its display to be neater, this button is for you!",
"info.interface.info": "The 'Information' button redirects to the game rule page (this one).",
"info.interface.fiche": "The 'Clue Deduction Sheet' button displays a table that, along with the progress of the game, allows deducing which clues are most likely for each player.",
"info.interface.indice": "The 'Personal Clue' button is the most important; it displays your secret clue. Only you know it! You'll have to be clever to deceive your friends and keep it a secret for as long as possible!",
"info.interface.vision": "The 'Last Moves Vision' button allows the user to visualize the last 5 moves made. It acts as a visual tool.",
"etape":"Step",
"info.deroulement.e1":"Ask Strategic Questions",
"info.deroulement.e1.text":"Each round begins with a player asking another player a question about a person on the graph. Answers are formulated by placing square or round tokens to indicate certainty or doubt as to the involvement of this person.",
@ -140,5 +233,125 @@
"info.indice-possible.age":"Person's age",
"info.indice-possible.hair":"Hair color of a person",
"info.indice-possible.sport":"Sport(s) of a person",
"info.indice-possible.voisin":"Character of neighbours"
"info.indice-possible.voisin":"Character of neighbours",
"info.mdj": "Game Modes",
"info.mdj.text": "There are various other game modes that allow you to add variety and make the game more or less complex.",
"info.mdj.mastermind": "Mastermind",
"info.mdj.mastermind.text": "The 'Mastermind' game mode is a solo mode where you must be able to guess the culprit using the minimum number of guesses. In this mode, you won't have any clues, but you'll need to guess them during the game. Selecting a suspect reveals the state of all clues for that suspect. It becomes possible to guess the clues and then identify the culprit!",
"info.mdj.enigme": "Enigma",
"info.mdj.enigme.text": "This is a solo mode with three difficulty levels:",
"info.mdj.enigme.easy": "Easy Enigma",
"info.mdj.enigme.easy.txt": "In this game mode, you have access to all clues, and you must guess the culprit that matches all these clues. You can practice guessing the culprit in this mode.",
"info.mdj.enigme.medium": "Intermediate Enigma",
"info.mdj.enigme.medium.txt": "In this game mode, you don't have access to clues, but you have just enough information about some suspects to be able to guess the culprit. This information will help you guess the clues, and the culprit is the suspect for whom all the clues match. If you select the wrong suspect, don't worry! You'll have information about that suspect, making it easier for you to find the culprit. The goal is to find the culprit in the minimum number of guesses.",
"info.mdj.enigme.hard": "Hard Enigma",
"info.mdj.enigme.hard.txt": "This final variant is similar to the intermediate enigma; however, you must find the culprit on the first try, or you lose! It's up to you to play! The goal is to find the culprit in the minimum amount of time.",
"tutorial.title": "Tutorial",
"tuto.title.1": "Welcome to SocialGraph!",
"tuto.txt.1.1": "You play as a glory-seeking detective faced with a crime.",
"tuto.txt.1.2": "However, other investigators are also on the case, all seeking the title of the world's best detective.",
"tuto.txt.1.3": "Each has a crucial clue to identify the culprit; you'll need to deduce your competitors' clues if you want to win!",
"tuto.txt.1.4": "Interrogate your competitors to get answers with yes or no, but beware, a refusal has consequences.",
"tuto.txt.1.5": "Be the first to deduce the clues of others and find the culprit to win the coveted recognition.",
"tuto.txt.1.6": "If you have any doubts, click on the 'help' button to display the current tutorial step.",
"tuto.title.2": "The Suspects",
"tuto.txt.2.1": "Here is how a suspect is represented; each suspect has different characteristics, including their name, age, sport, and hair color.",
"tuto.txt.2.2": "For example, here we have ",
"tuto.txt.2.2.1": ", who is ",
"tuto.txt.2.2.2": ", practicing ",
"tuto.txt.2.2.3": " and ",
"tuto.txt.2.2.4": ", has ",
"tuto.txt.2.2.5": " hair, and possesses ",
"tuto.title.3": "The Clues",
"tuto.txt.3.1": "In this game, each detective has a clue that identifies a characteristic of the culprit. Your clue is:",
"tuto.txt.3.2": "The suspect is between 20 and 29 years old.",
"tuto.title.4": "The Detectives",
"tuto.txt.4.1": "It is possible to see the detectives on the left side of the screen, represented by circles of different colors. The square outline means that the detective is in deep thought.",
"tuto.txt.4.2": "To interrogate a detective about a suspect, select the detective, then click on the suspect you want. The detective will respond with what he thinks of this suspect based on his clue.",
"tuto.title.5": "The Responses",
"tuto.txt.5.1": "Detectives will respond to you only with circles or squares of their color.",
"tuto.txt.5.2": " means that their clue clears the suspect.",
"tuto.txt.5.3": " means that their clue may incriminate the suspect.",
"tuto.txt.5.4": "For example, here:",
"tuto.txt.5.5.1": "Scooby-Doo's clue ",
"tuto.txt.5.5.2": "clears Logan.",
"tuto.txt.5.6": "Eleanor can be suspected based on Batman's clue.",
"tuto.txt.5.7.1": "Evelyn is cleared by the clues of ",
"tuto.txt.5.7.2": "3 different detectives.",
"tuto.title.6": "The Rules of the Game",
"tuto.txt.6.1.1": "This button takes you to the game's ",
"tuto.txt.6.1.2": "information page",
"tuto.txt.6.1.3": ", with all the rules of the game, including objectives, clues, flow, etc.",
"tuto.title.7": "The Clue",
"tuto.txt.7.1": "This button allows you to display your personal clue; keep it a secret! It is your best asset to win.",
"tuto.title.8": "Time to Practice!",
"tuto.txt.8.1": "Well done! You now have all the basics of a real detective.",
"tuto.txt.8.2": "You will now have a practical exercise for solving a case, alongside our dear Batman and Scooby-Doo.",
"tuto.txt.8.3": "Click 'Continue' to start your first game.",
"tuto.title.9": "First Steps",
"tuto.txt.9.1": "Welcome to this second part, where we will learn the flow of a real investigation.",
"tuto.txt.9.2.1": "First, select player ",
"tuto.txt.9.2.2": " and question them about the suspect named ",
"tuto.txt.9.2.3": " by clicking on the latter.",
"tuto.title.10": "Your First Turn",
"tuto.txt.10.1.1": "Great, ",
"tuto.txt.10.1.2": "Violet has been identified by Scooby-Doo's clue",
"tuto.txt.10.1.3": ", which is essential information! However, this does not necessarily ",
"tuto.txt.10.1.4": "mean",
"tuto.txt.10.1.5": " that she is guilty.",
"tuto.txt.10.2": "Now, it's the other players' turn to play; let's see what they did.",
"tuto.title.11": "Other Players' First Turn",
"tuto.txt.11.1.1": "It seems that Scooby-Doo also questioned Batman about Violet, and Batman responded with ",
"tuto.txt.11.1.2": "a square. This means Violet is not guilty and is innocent!",
"tuto.txt.11.2.1": "Scooby-Doo made a mistake by questioning someone who could clear Violet. As a ",
"tuto.txt.11.2.2": "punishment",
"tuto.txt.11.2.3": ", he must also place a square on another player, revealing more information about his clue. So now we know that Scooby-Doo's clue does not identify Sebastian.",
"tuto.txt.11.3": "Next, Batman questioned Scooby-Doo about Charlotte, identified by Scooby-Doo's clue.",
"tuto.title.12": "Second Turn",
"tuto.txt.12.1.1": "You notice that ",
"tuto.txt.12.1.2": "your clue also identifies Charlotte",
"tuto.txt.12.1.3": ", so what if we ask Batman if he thinks Charlotte is the culprit?",
"tuto.txt.12.2": "This would allow us to end the game!",
"tuto.title.13": "The Punishment",
"tuto.txt.13.1.1": "Oh no, it seems that Batman's clue clears Charlotte, and you have made a mistake; the ",
"tuto.txt.13.1.2": "punishment",
"tuto.txt.13.1.3": " applies!",
"tuto.txt.13.2.1": "So, you must place a ",
"tuto.txt.13.2.2": "square on another player",
"tuto.txt.13.2.3": ", revealing more information about your clue.",
"tuto.txt.13.3.1": "But all is not lost! Let's place our square on ",
"tuto.txt.13.3.2": "for that, select the desired suspect directly.",
"tuto.title.14": "The End of the Game",
"tuto.txt.14.1": "This turn is also rich in information!",
"tuto.txt.14.2": "You now have enough information to guess the clues of others:",
"tuto.txt.14.3": "Scooby-Doo seems to have:",
"tuto.txt.14.4": "Batman seems to have:",
"tuto.txt.14.5": "And your clue is:",
"tuto.txt.14.6.1": "You now have all the cards in hand to guess who the culprit is. Click the ",
"tuto.txt.14.6.2": "Ask Everyone",
"tuto.txt.14.6.3": " button, then select a suspect to make an ",
"tuto.txt.14.6.4": "accusation",
"tuto.txt.14.6.5": " to guess. Good luck!",
"history.mis": " placed a ",
"à": "to",
"history.cantPose": " can no longer place a square",
"history.NotCoupable": " is not the culprit!"
}

@ -0,0 +1,163 @@
{
"languageSelector.french": "Francés",
"languageSelector.english": "Inglés",
"languageSelector.espagnol": "Español",
"languageSelector.portuguese": "Portugués",
"languageSelector.russian": "Ruso",
"home.histoire.title": "HISTORIA",
"home.histoire": "La criptozoología estudia las huellas de monstruos legendarios: criptidos. Yetis, Chupacabra, bestia de Gévaudan, Dahut, etc. son temas muy serios para ti... Cruza las pistas y sé el primero en descubrirlos.",
"home.jeu.title": "EL JUEGO",
"home.jeu": "Cada jugador tiene una pista en el campo donde se encuentra la criatura. Cruzando tu información, solo puede haber una casilla. Pero el objetivo es ser el primero en encontrarla. Pregunta a tus colegas y, sin embargo, competidores. Solo pueden responderte con un 'no' o un 'quizás', con mucha lógica y un poco de audacia, ¡puedes entrar en la leyenda!",
"home.plus.title": "FORTALEZAS",
"home.plus.1": "Un mecanismo de deducción asombroso.",
"home.plus.2": "Una inmensa rejugabilidad.",
"home.plus.3": "Un tema sorprendente y fuerte.",
"game.time": "Tiempo: 45 minutos",
"game.players": "Jugadores: de 3 a 5 jugadores",
"game.age": "Edad: 10 años en adelante",
"game.createdBy": "Creado por:",
"game.illustratedBy": "Ilustrado por:",
"log_in": "Iniciar sesión",
"sign_up": "Registrarse",
"log_out": "Cerrar sesión",
"join": "Unirse",
"create_room": "Crear sala",
"play_solo": "Jugador individual",
"play": "JUGAR",
"football": "Fútbol",
"basketball": "Baloncesto",
"baseball": "Béisbol",
"tennis": "Tenis",
"bowling": "Bolos",
"white": "Blanco",
"black": "Negro",
"blond": "Rubio",
"redhead": "Pelirrojo",
"brown": "Marrón",
"and": "y",
"or": "o",
"or_sport": "o",
"age_indice_start": "El sospechoso tiene entre",
"age_indice_more_start": "El sospechoso tiene al menos",
"age_indice_end": "años",
"color_edges_start": "El sospechoso tiene al menos un amigo con cabello",
"color_edges_end": "",
"color_start": "El sospechoso tiene cabello",
"color_end": "",
"nb_friends_indice_start": "El sospechoso tiene",
"nb_friends_indice_end": "amigos",
"nb_sports_indice_start": "El sospechoso juega",
"nb_sports_indice_end": "deporte(s)",
"sport_start": "El sospechoso juega al menos",
"sport_end": "",
"navbar.play" : "Jugar",
"navbar.presentation":"Presentación",
"navbar.info":"Información",
"round":"Vuelta",
"informations": "Información",
"info.intro.title": "Introducción al juego:",
"info.intro.text": "¡Bienvenido a nuestro emocionante juego de deducción, donde la intriga y la travesura se combinan en una emocionante aventura! Sumérgete en un mundo de misterio e intriga, donde cada interacción cuenta y cada pista te acerca a la verdad. Imagina un gráfico complejo donde cada vértice representa a una persona, cada eje una relación, y cada detalle cuenta. Te sumerges en un desafiante reto para descubrir quién entre estas personas es el misterioso asesino. Cada jugador tiene una pista crucial y solo el intercambio estratégico de estas pistas te llevará a resolver el misterio. Explora nuestra página de reglas para entender las complejidades del juego, descubre pistas que pueden guiarte y desarrolla estrategias inteligentes para identificar al culpable. ¡Manipula a tus amigos para ser el primero en descubrir quién es el asesino oculto en el gráfico! ¿Estás listo para aceptar el desafío y desenmascarar al asesino oculto en el gráfico? ¡Que comience la investigación!",
"info.sommaire": "Contenidos",
"info.title.composant": "Componentes del juego",
"info.title.objectif": "Objetivo del juego",
"info.title.deroulement": "Desarrollo del juego",
"info.title.indice_possible": "Posible pista del juego",
"info.pions": "Peones",
"info.composant.text": "A cada jugador se le asignará un color específico que distinguirá las acciones representadas por los peones de la siguiente manera:",
"info.composant.carre.title": "Fichas cuadradas",
"info.composant.carre": "Estas fichas indican una negación. Cuando un jugador coloca una ficha cuadrada, significa que su pista elimina a la persona designada.",
"info.composant.rond.title": "Fichas redondas",
"info.composant.rond": "Estas fichas representan un 'quizás'. El jugador que deposita esta ficha afirma que la persona es sospechosa, pero esto no garantiza su culpabilidad. ¡Solo hay un sospechoso que lleva una ficha redonda para todos los jugadores en el juego, y ese es el culpable!",
"info.car_perso": "Características",
"info.composant.textcar": "Además de sus nombres, los personajes están representados con otras características:",
"info.composant.age.title": "Edades",
"info.composant.age": "Cada persona tiene una edad para autenticarlos, que varía entre 0 y 60 años. La edad es una característica que se confirmará con pistas en forma de ",
"info.composant.age.link": "grupos de edad",
"info.composant.hair_col.title": "Colores de cabello",
"info.composant.hair_col": "Los personajes también tienen un color de cabello, que puede ser de los siguientes colores:",
"hair.blanc": "Blanco",
"hair.blond": "Rubio",
"hair.roux": "Pelirrojo",
"hair.chatain": "Marrón",
"hair.noir": "Negro",
"info.composant.sport.title": "Deportes",
"info.composant.sport": "Los hobbies de los personajes están representados por cinco deportes respectivamente:",
"info.composant.baseball": "Béisbol",
"info.composant.basketball": "Baloncesto",
"info.composant.bowling": "Bowling",
"info.composant.football": "Fútbol",
"info.composant.tennis": "Tenis",
"info.composant.sport.bis": "Entre estos deportes, cada personaje puede tener entre 0 y 3 deportes, lo que facilita su identificación utilizando las pistas que tienes.",
"info.objectif.intro": "Bienvenido al ingenioso universo de nuestro juego de deducción, donde el engaño y la astucia son las claves del éxito. Tu misión es desentrañar el misterio detrás de cada interacción del complejo gráfico que representa las relaciones entre individuos.",
"info.objectif.t1": "Manipulación Sutil",
"info.objectif.t1.text": "El objetivo final es descubrir quién entre los individuos es el asesino, pero no a través de una colaboración abierta. Por el contrario, utilizarás una manipulación sutil para difuminar las líneas y distraer a tus oponentes. Haz preguntas estratégicas, responde con malicia y siembra pistas engañosas para acercarte al resultado.",
"info.objectif.t2": "Trucos",
"info.objectif.t2.text": "Cada ronda ofrece la oportunidad de sembrar dudas entre tus oponentes. Cuando un jugador te hace preguntas, responde colocando inteligentemente un token cuadrado para indicar que 'según tu pista, esa persona no puede ser el culpable' o un token redondo para sugerir que permanezcan en la lista de sospechosos. Ten cuidado, porque cada gesto puede interpretarse, y la verdad a menudo está oculta detrás de una fachada de pistas engañosas.",
"info.objectif.t3": "Contramanipulación",
"info.objectif.t3.text": "Si un jugador coloca un token cuadrado, el que hace la pregunta también debe jugar su juego colocando un token cuadrado de su color en un nodo del gráfico. La contramanipulación se convierte en un arma formidable para desviar la acusación y sembrar la confusión.",
"info.interface": "Interfaz de juego",
"info.interface.text": "Para cada juego, tendrás disponibles ciertos elementos de interfaz:",
"info.interface.param": "El botón 'Configuración' permite mostrar y gestionar varias configuraciones del juego, como activar o desactivar efectos de sonido. También es posible elegir el número de nodos para el modo de juego 'Enigma' con este botón.",
"info.interface.reset": "El botón 'Restablecer' restablece el estado actual del gráfico. Si lo has movido demasiado o quieres que su visualización sea más ordenada, ¡este botón es para ti!",
"info.interface.info": "El botón 'Información' redirige a la página de reglas del juego (esta).",
"info.interface.fiche": "El botón 'Hoja de Deducción de Pistas' muestra una tabla que, junto con el progreso del juego, permite deducir qué pistas son más probables para cada jugador.",
"info.interface.indice": "El botón 'Pista Personal' es el más importante; muestra tu pista secreta. ¡Solo tú lo sabes! Tendrás que ser astuto para engañar a tus amigos y mantenerlo en secreto el mayor tiempo posible!",
"info.interface.vision": "El botón 'Visión de los Últimos Movimientos' permite al usuario visualizar los últimos 5 movimientos realizados. Actúa como una herramienta visual.",
"etape": "Paso",
"info.deroulement.e1": "Hacer preguntas estratégicas",
"info.deroulement.e1.text": "Cada ronda comienza con un jugador haciendo preguntas a otro jugador sobre una persona en el gráfico. Las respuestas se formulan colocando fichas cuadradas o redondas para indicar certeza o duda sobre la participación de esa persona.",
"info.deroulement.e2": "Contramanipulación y Contra-pregunta",
"info.deroulement.e2.text": "Si un jugador coloca una ficha cuadrada, el que hace la pregunta también debe colocar una ficha cuadrada en un nodo del gráfico. Las contra-preguntas son una forma de confundir a los jugadores y desviar la acusación.",
"info.deroulement.e3": "La Adivinanza Final",
"info.deroulement.e3.text": "El juego alcanza su clímax cuando un jugador intenta la 'Adivinanza' final, afirmando que tal persona es el asesino. Otros jugadores",
"info.indice-possible.age": "Edad de la persona",
"info.indice-possible.hair": "Color de pelo de una persona",
"info.indice-possible.sport": "Deporte(s) de una persona",
"info.indice-possible.voisin": "Personaje de los vecinos",
"info.mdj": "Modos de juego",
"info.mdj.text": "Existen varios modos de juego que te permiten agregar variedad y hacer el juego más o menos complejo.",
"info.mdj.mastermind": "Mastermind",
"info.mdj.mastermind.text": "El modo de juego 'Mastermind' es un modo para un solo jugador donde debes ser capaz de adivinar al culpable utilizando el menor número de suposiciones. En este modo, no tendrás ninguna pista, pero deberás adivinarlas durante el juego. Seleccionar a un sospechoso revela el estado de todas las pistas para ese sospechoso. ¡Se vuelve posible adivinar las pistas y luego identificar al culpable!",
"info.mdj.enigme": "Enigma",
"info.mdj.enigme.text": "Este es un modo para un solo jugador con tres niveles de dificultad:",
"info.mdj.enigme.easy": "Enigma Fácil",
"info.mdj.enigme.easy.txt": "En este modo de juego, tienes acceso a todas las pistas y debes adivinar al culpable que coincide con todas estas pistas. Puedes practicar adivinando al culpable en este modo.",
"info.mdj.enigme.medium": "Enigma Intermedio",
"info.mdj.enigme.medium.txt": "En este modo de juego, no tienes acceso a pistas, pero tienes información suficiente sobre algunos sospechosos para poder adivinar al culpable. Esta información te ayudará a adivinar las pistas, y el culpable es el sospechoso para el cual todas las pistas coinciden. Si seleccionas al sospechoso equivocado, ¡no te preocupes! Tendrás información sobre ese sospechoso, lo que facilitará encontrar al culpable. El objetivo es encontrar al culpable en el menor número de suposiciones.",
"info.mdj.enigme.hard": "Enigma Difícil",
"info.mdj.enigme.hard.txt": "Esta variante final es similar al enigma intermedio; sin embargo, ¡debes encontrar al culpable en el primer intento, o pierdes! ¡Depende de ti jugar! El objetivo es encontrar al culpable en el menor tiempo posible."
}

@ -1,6 +1,9 @@
{
"languageSelector.french": "Français",
"languageSelector.english": "Anglais",
"languageSelector.espagnol": "Espagnol",
"languageSelector.portuguese": "Portugais",
"languageSelector.russian": "Russe",
"home.histoire.title": "L'HISTOIRE",
"home.histoire": "La cryptozoologie étudie les traces des monstres de légende : les cryptides. Yétis, Chupacabra, bête du gévaudan, Dahut, etc., sont des sujets très sérieux pour vous… Croisez les indices et soyez le premier à les découvrir!",
@ -23,6 +26,12 @@
"sign_up":" S'inscrire ",
"log_out":" Se déconnecter ",
"indice":"Indice",
"param":"Paramètres",
"sfx":"Activer les sons",
"aide":"Aide",
"majUn":"Un",
"join" : "Rejoindre",
"create_room" : "Créer une partie",
@ -41,6 +50,10 @@
"redhead": "Roux",
"brown": "Brun",
"oui": "oui",
"non": "non",
"and": "et",
"or": "ou",
"or_sport": "ou du",
@ -64,19 +77,85 @@
"sport_start": "Le suspect pratique au moins du",
"sport_end": "",
"navbar.play" : "Jouer",
"navbar.presentation":"Présentation",
"navbar.info":"Informations",
"hard" : "Difficile",
"medium" : "Intermédiaire",
"strong" : "Fort",
"weak" : "Faible",
"none" : "Aucun",
"turn" : "Tour",
"square":"carré",
"round":"rond",
"step" : "Étape",
"previous" : "Précédent",
"next" : "Suivant",
"continue" : "Poursuivre",
"compris":"Compris !",
"close" : "Fermer",
"askeveryone" : "Demandez à tous",
"regenerate":"Regénérer le graph",
"BackHome" : "Retour à l'accueil",
"color.green":"Vert",
"color.blue":"Bleu",
"color.red":"Rouge",
"color.yellow":"Jaune",
"play.jouerseul": "Mastermind",
"play.enigme": "Résoudre une énigme",
"play.enigme.easy": "Facile",
"play.enigme.medium": "Intermédiaire",
"play.enigme.hard": "Difficile",
"play.create":"Créer une partie",
"play.tuto": "Tutoriel",
"play.return": "Retourner à la partie",
"lobbies.all":"Tous",
"lobbies.dispo":"Disponibles",
"placeholder.searchLobby": "Rechercher un lobby...",
"score.nbPlayed" : "Parties Jouées",
"score.best" : "Best-Score",
"score.moy" : "Moyenne d'essai",
"score.NbWin" : "Nombre de victoire",
"score.ratio" : "Ratio V/D",
"score.bestTmp" : "Meilleur temps",
"score.moyTmp" : "Moyenne de temps",
"score.nothing" : "Rien pour le moment",
"score.titre.easy" : "Énigme facile",
"score.titre.int" : "Énigme intermédiaire",
"score.titre.hard" : "Énigme difficile",
"score.online" : "En ligne",
"score.tab.stat" : "Vos Stats",
"score.tab.quoti" : "Quotidien",
"score.tab.hebdo" : "Hebdomadaire",
"lobby.players" : "Joueurs",
"lobby.bienvenue" : "Bienvenue dans votre lobby !",
"lobby.wait" : "Attendez que tous vos amis rejoignent avant de lancer la partie.",
"lobby.invite" : "Inviter des amis",
"lobby.copyLink" : "Lien copié",
"lobby.nbNode" : "Sélectionner le nombre de noeud (entre 20 et 60) ",
"lobby.start" : "Démarrer la partie !",
"game.yourTurn":"À vous de jouer !",
"game.wrong":"Mauvais choix, posez un carré !",
"informations" : "Informations",
"info.intro.title":"Introduction au jeu :",
"info.intro.text":"Bienvenue dans notre jeu de déduction captivant, où l'intrigue et la malice se rejoignent dans une aventure palpitante ! Plongez-vous dans un monde de mystère et d'intrigue, où chaque interaction compte, et chaque indice vous rapproche de la vérité.Imaginez un graphique complexe où chaque sommet représente une personne, chaque axe une relation, et chaque détail compte. Vous êtes plongé dans un défi stimulant pour découvrir qui parmi ces individus est le mystérieux tueur. Chaque joueur détient un indice crucial, et seul le partage stratégique de ces indices vous mènera à la résolution du mystère. Explorez notre page de règles pour comprendre les subtilités du jeu, découvrez les indices qui peuvent vous guider, et élaborez des stratégies intelligentes pour identifier le coupable. Manipuler vos amis, afin d'être le premier à découvrir qui est le meurtrier ! Êtes-vous prêt à relever le défi et à démasquer le tueur caché dans le graphe ? Que l'enquête commence !",
"info.intro.text":"Bienvenue dans notre jeu de déduction captivant, où l'intrigue et la malice se rejoignent dans une aventure palpitante ! Plongez-vous dans un monde de mystère et d'intrigue, où chaque interaction compte, et chaque indice vous rapproche de la vérité.Imaginez un graphique complexe où chaque sommet représente une personne, chaque axe une relation, et chaque détail compte. Vous êtes plongé dans un défi stimulant pour découvrir qui parmi ces individus est le mystérieux coupable. Chaque joueur détient un indice crucial, et seul le partage stratégique de ces indices vous mènera à la résolution du mystère. Explorez notre page de règles pour comprendre les subtilités du jeu, découvrez les indices qui peuvent vous guider, et élaborez des stratégies intelligentes pour identifier le coupable. Manipuler vos amis, afin d'être le premier à découvrir qui est le meurtrier ! Êtes-vous prêt à relever le défi et à démasquer le coupable caché dans le graphe ? Que l'enquête commence !",
"info.sommaire":"Sommaire",
@ -110,6 +189,12 @@
"hair.chatain":"Châtain",
"hair.noir":"Noir",
"param.node":"Sélectionner le nombre de noeuds (entre 20 et 50)",
"param.clue":"Sélectionner le nombre d'indices (entre 3 et 6)",
"param.valid":"Valider",
"info.composant.sport.title":"Les Sports",
"info.composant.sport":"Les loisirs des personnages sont représentés par cinq sports respectivement :",
@ -122,12 +207,21 @@
"info.objectif.intro":"Bienvenue dans l'univers astucieux de notre jeu de déduction, où la tromperie et la ruse sont les clés du succès. Votre mission est de démêler le mystère qui se cache derrière chaque interaction du graphique complexe représentant les relations entre les individus.",
"info.objectif.t1":"Manipulation Subtile",
"info.objectif.t1.text":"Le but ultime est de découvrir qui parmi les individus est le tueur, mais pas par une collaboration ouverte. Au contraire, vous utiliserez la manipulation subtile pour brouiller les pistes et détourner l'attention de vos adversaires. Posez des questions stratégiques, répondez avec malice, et plantez des indices trompeurs pour vous rapprocher du dénouement.",
"info.objectif.t1.text":"Le but ultime est de découvrir qui parmi les individus est le coupable, mais pas par une collaboration ouverte. Au contraire, vous utiliserez la manipulation subtile pour brouiller les pistes et détourner l'attention de vos adversaires. Posez des questions stratégiques, répondez avec malice, et plantez des indices trompeurs pour vous rapprocher du dénouement.",
"info.objectif.t2":"Jeu de Duperie",
"info.objectif.t2.text":"Chaque tour offre l'opportunité de semer le doute parmi vos adversaires. Lorsqu'un joueur vous interroge, répondez en plaçant habilement un jeton carré pour indiquer que 'selon votre indice, cette personne ne peut être le coupable' ou un jeton rond pour suggérer qu'elle reste dans la liste des suspects. Soyez prudent, car chaque geste peut être interprété, et la vérité est souvent cachée derrière une façade d'indices trompeurs.",
"info.objectif.t3":"Contre-manipulation",
"info.objectif.t3.text":"Si un joueur place un jeton carré, le questionneur doit également jouer son jeu en posant un jeton carré de sa couleur sur un nœud du graphique. La contre-manipulation devient une arme redoutable pour détourner l'accusation et semer la confusion.",
"info.interface":"Interface du jeu",
"info.interface.text":"Pour chacune des parties, vous aurez certains éléments d'interface à disposition :",
"info.interface.param":"Le bouton 'Paramètre' permet l'affichage et la gestion de différents paramètres de la partie, comme par exemple l'option d'activé ou non l' effect sonore. Il est aussi possible de choisir le nombre de nœud du mode de jeu 'Enigme' à l'aide de ce bouton.",
"info.interface.reset":"Le bouton 'Reset' permet de réinitialiser l'état du graphe actuel. Si jamais vous avez trop déplacé ce dernier, ou que vous souhaite que son affichage soit plus propre, ce bouton est fait pour vous !",
"info.interface.info":"Le bouton 'Information' permet de rediriger vers la page de règle du jeu (celle-ci).",
"info.interface.fiche":"Le bouton 'Fiche de déduction d'indice' permet l'affichage de tableau permettant, avec le déroulé de la partie, de déduire quels indices sont les plus probables pour chaque joueur.",
"info.interface.indice":"Le bouton 'Indice personnel' est le plus important, en effet, il permet d'afficher quel est votre indice secret. Vous seul le connaissez ! Il va falloir ruser pour tromper vos amis et le garder secret le plus longtemps possible !",
"info.interface.vision":"Le bouton 'Vision des derniers mouvements' permet à l'utilisateur de visualiser les 5 derniers coup effectué. Il agit donc vraiment comme un outil visuel.",
"etape":"Étape",
"info.deroulement.e1":"Poser des Questions Stratégiques",
@ -135,10 +229,131 @@
"info.deroulement.e2":"Contre-manipulation et Contre-questions",
"info.deroulement.e2.text":"Si un joueur place un jeton carré, le questionneur doit également poser un jeton carré sur un nœud du graphique. Les contre-questions sont un moyen de semer la confusion parmi les joueurs et de détourner l'accusation.",
"info.deroulement.e3":"Le 'Guess' Final",
"info.deroulement.e3.text":"La partie atteint son apogée lorsqu'un joueur tente le 'Guess' final, affirmant que telle personne est le tueur. Les autres joueurs peuvent alors contredire cette affirmation en plaçant leurs propres jetons carrés. Si aucune réfutation n'est faite, le joueur ayant fait le 'Guess' remporte la partie, démontrant ainsi sa maîtrise dans l'art de la manipulation.",
"info.deroulement.e3.text":"La partie atteint son apogée lorsqu'un joueur tente le 'Guess' final, affirmant que telle personne est le coupable. Les autres joueurs peuvent alors contredire cette affirmation en plaçant leurs propres jetons carrés. Si aucune réfutation n'est faite, le joueur ayant fait le 'Guess' remporte la partie, démontrant ainsi sa maîtrise dans l'art de la manipulation.",
"info.indice-possible.age":"Âge d'une personne",
"info.indice-possible.hair":"Couleur de cheveux d'une personne",
"info.indice-possible.sport":"Sport(s) d'une personne",
"info.indice-possible.voisin":"Caractèristique des voisins"
"info.indice-possible.voisin":"Caractèristique des voisins",
"info.mdj":"Modes de jeu",
"info.mdj.text":"Il existe différents autres modes de jeu, qui permettent de varier les plaisirs et de rendre le jeu plus ou moins complexe.",
"info.mdj.mastermind":"Mastermind",
"info.mdj.mastermind.text":"Le mode de jeu 'Mastermind' est un mode de jeu solo. Où vous devez être capable de deviner qui est le coupable en utilisant le minimum de coup. Pour cela, vous n'aurez aucun indice, mais vous allez devoir être capable de les deviner au cours de la partie. Sélectionner un suspect révéle l'état de tous les indices pour ce dernier. Il devient donc possible de deviner les indices, pour ensuite deviner le coupable !",
"info.mdj.enigme":"Énigme",
"info.mdj.enigme.text":"Il s'agit d'un mode solo, pour lequel on retrouve 3 modes de difficultés :",
"info.mdj.enigme.easy":"L'énigme facile",
"info.mdj.enigme.easy.txt":"Dans ce mode de jeu, vous avez accès à tous les indices, et vous devez deviner le coupable qui répond à tous ces indices. Vous pouvez donc vous entraîner à deviner le coupable dans ce mode.",
"info.mdj.enigme.medium":"L'énigme intermédiaire",
"info.mdj.enigme.medium.txt":"Dans ce mode de jeu, vous n'avez pas accès au indice, mais vous avez juste assez d'informations sur certains suspects pour pouvoir deviner le coupable. Ces informations vont vous permettre de deviner les indices, et le coupable est le suspect pour lequel tous les indices correspondent. Si jamais vous sélectionnez le mauvais suspect, pas de panique ! Vous aurez les informations relatives à ce dernier, ce qui vous facilitera le travail pour trouver le coupable. L'objectif est donc de trouver le coupable en un minimum de coup.",
"info.mdj.enigme.hard":"L'énigme difficile",
"info.mdj.enigme.hard.txt":"Cette dernière variante est similaire à l'énigme intermédiaire, cependant, il faudra trouver du premier coup sinon, vous aurez perdu ! À vous de jouer ! L'objectif est donc de trouver le coupable en un minimum de temps.",
"tutorial.title":"Tutoriel",
"tuto.title.1":"Bienvenue dans SocialGraph !",
"tuto.txt.1.1":"Vous incarnez un détective assoiffé de gloire, confronté à un crime.",
"tuto.txt.1.2":"Cependant, d'autres enquêteurs sont également sur le coup, tous cherchant à décrocher le titre de meilleur détective du monde.",
"tuto.txt.1.3":"Chacun possède un indice crucial pour identifier le coupable, il va falloir déduire l'indice de vos concurrents si vous souhaitez l'emporter !",
"tuto.txt.1.4":"Interrogez vos concurrents pour obtenir des réponses par oui ou non, mais méfiez-vous, un refus a des conséquences.",
"tuto.txt.1.5":"Soyez le premier à déduire les indices des autres et à trouver le coupable pour remporter la reconnaissance tant convoitée.",
"tuto.txt.1.6":"Si vous avez le moindre doute, cliquer sur le bouton 'aide' pour afficher l'étape actuel du tuto",
"tuto.title.2":"Les Suspects",
"tuto.txt.2.1":"Voici comment est représenté un suspect, chaque suspect possède différentes caractéristiques, que ce soit leur nom, âge, sport et leur couleur de cheveux.",
"tuto.txt.2.2":"Par exemple, ici, nous avons ",
"tuto.txt.2.2.1":", qui a ",
"tuto.txt.2.2.2":", qui pratique du ",
"tuto.txt.2.2.3":" et du ",
"tuto.txt.2.2.4":", qui a les cheveux ",
"tuto.txt.2.2.5":" et qui possède ",
"tuto.title.3":"Les indices",
"tuto.txt.3.1":"Dans ce jeu, chaque détective possède un indice, qui permet d'identifier une caractéristique du coupable, votre indice est le suivant :",
"tuto.txt.3.2":"Le suspect a entre 20 et 29 ans",
"tuto.title.4":"Les Détectives",
"tuto.txt.4.1":"Il est possible de voir les détectives sur le côté gauche de l'écran, ils sont représentés par des cercles de couleurs différentes. Le contour carré signifie que ce détective est en pleine réflexion.",
"tuto.txt.4.2":"Pour interroger un détective à propos d'un suspect, il suffit de le sélectionner, puis de cliquer sur le suspect que vous souhaitez. Il vous répondra donc ce qu'il pense de ce suspect selon son indice.",
"tuto.title.5":"Les réponses",
"tuto.txt.5.1":"Les détéctives vous répondrons que par des ronds ou des carrés de leur couleur.",
"tuto.txt.5.2":" signifie que son indice innocente le suspect",
"tuto.txt.5.3":" signifie que son indice peut incréminer le suspect",
"tuto.txt.5.4":"Par exemple, ici :",
"tuto.txt.5.5.1":"l'indice du détéctive Scooby-Doo ",
"tuto.txt.5.5.2":" permet d'innocenter Logan",
"tuto.txt.5.6":"Eleanor peut être suspectée par l'indice du détective Batman ",
"tuto.txt.5.7.1":"Evelyn est innocentée par l'indice de ",
"tuto.txt.5.7.2":"3 détéctives différents",
"tuto.title.6":"Les règles du jeu",
"tuto.txt.6.1.1":"Ce bouton vous mène à la page d'",
"tuto.txt.6.1.2":"information du jeu",
"tuto.txt.6.1.3":", avec toutes les règles du jeu, que ce soit les objectifs, les indices, le déroulement, etc.",
"tuto.title.7":"L'indice",
"tuto.txt.7.1":"Ce bouton vous permet d'afficher votre indice personnel, gardez le secret ! Il s'agit de votre meilleur atout pour gagner.",
"tuto.title.8":"Place à la pratique !",
"tuto.txt.8.1":"Bien joué ! Vous avez maintenant toutes les bases d'un veritable détéctive.",
"tuto.txt.8.2":"Vous allez à présent avoir un exercice pratique pour la résolution d'une enquête, au côté de ces très chère Batman et Scooby-Doo.",
"tuto.txt.8.3":"Cliquer sur 'Poursuivre' pour commencer votre première partie.",
"tuto.title.9":"Premier pas",
"tuto.txt.9.1":"Bienvenue dans cette seconde partie, où nous allons apprendre le déroulé d'une veritable enquête.",
"tuto.txt.9.2.1":"Dans un premier temps, sélectionnez le joueur ",
"tuto.txt.9.2.2":" et questionnez le à propos du suspect nommé ",
"tuto.txt.9.2.3":" en cliquant sur cette dernière.",
"tuto.title.10":"Votre premier tour",
"tuto.txt.10.1.1":"Super, ",
"tuto.txt.10.1.2":"Violet a été identifié par l'indice de Scooby-Doo",
"tuto.txt.10.1.3":", c'est une information essentielle ! Cependant, cela ne signifie ",
"tuto.txt.10.1.4":"pas forcément",
"tuto.txt.10.1.5":" qu'elle est coupable.",
"tuto.txt.10.2":"C'est à présent le tour aux autres joueurs de jouer, regardons ce qu'ils ont fait.",
"tuto.title.11":"Premier tour des autres joueurs",
"tuto.txt.11.1.1":"Il semblerait que Scooby-Doo ait lui aussi interrogé Batman à propos de Violet, et que ce dernier ait répondu ",
"tuto.txt.11.1.2":" par un carré. Cela signifie que Violet n'est pas coupable, et qu'elle est donc innocente !",
"tuto.txt.11.2.1":"Scooby-Doo a donc fait une erreur, en questionnant quelqu'un pouvant innocenter Violet. En guise de ",
"tuto.txt.11.2.2":"punition",
"tuto.txt.11.2.3":", il doit, lui aussi, poser un carré sur un autre joueur, révélant aussi plus d'information sur son indice.Nous savons donc maintenant que l'indice de Scooby-Doo ne permet pas d'identifier Sebastian.",
"tuto.txt.11.3":"Ensuite, Batman a questionné Scooby-Doo à propos de Charlotte, qui est identifié par l'indice de Scooby-Doo.",
"tuto.title.12":"Second tour",
"tuto.txt.12.1.1":"Vous remarquez que ",
"tuto.txt.12.1.2":"votre indice identifie lui aussi Charlotte",
"tuto.txt.12.1.3":", et si nous demandions à Batman, si ce dernier pense que Charlotte est la coupable ?",
"tuto.txt.12.2":"Cela nous permettrait donc de mettre fin à la partie !",
"tuto.title.13":"La punition",
"tuto.txt.13.1.1":"Mince, il semblerait que l'indice de Batman innocente Charlotte, et que vous avez donc commit une erreur, la ",
"tuto.txt.13.1.2":"punition",
"tuto.txt.13.1.3":" s'applique !",
"tuto.txt.13.2.1":"Vous devez donc poser un ",
"tuto.txt.13.2.2":"carré sur un autre joueur",
"tuto.txt.13.2.3":", révélant ainsi plus d'information sur votre indice.",
"tuto.txt.13.3.1": "Mais rien n'est joué ! Posons notre carré sur ",
"tuto.txt.13.3.2": "pour cela, sélectionnez directement le suspect désiré.",
"tuto.title.14":"La fin du jeu",
"tuto.txt.14.1":"Ce tour est lui aussi riche en informations !",
"tuto.txt.14.2":"Vous avez à présent assez d'information pour deviner les indices des autres : ",
"tuto.txt.14.3":"Scooby-Doo semble avoir : ",
"tuto.txt.14.4":"Batman semble avoir : ",
"tuto.txt.14.5":"Et votre indice est : ",
"tuto.txt.14.6.1":"Vous avez à présent toutes les cartes en main pour deviner qui est le coupable, cliquer sur le bouton ",
"tuto.txt.14.6.2":"Ask Everyone",
"tuto.txt.14.6.3":", puis séléctionné un suspect pour émettre une ",
"tuto.txt.14.6.4":"accusation",
"tuto.txt.14.6.5":" pour deviner, bonne chance !",
"history.mis":" a mis un ",
"à":"à",
"history.cantPose":" ne peut plus poser de carré",
"history.NotCoupable":" n'est pas le coupable !"
}

@ -0,0 +1,167 @@
{
"languageSelector.french": "Francês",
"languageSelector.english": "Inglês",
"languageSelector.espagnol": "Espanhol",
"languageSelector.portuguese": "Português",
"languageSelector.russian": "Russo",
"home.histoire.title": "HISTÓRIA",
"home.histoire": "A criptozoologia estuda as pegadas de monstros lendários: criptídeos. Yetis, Chupacabra, a besta de Gévaudan, Dahut, etc., são tópicos muito sérios para você... Cruze as pistas e seja o primeiro a descobri-los!",
"home.jeu.title": "O JOGO",
"home.jeu": "Cada jogador tem uma pista no campo onde a criatura está localizada. Ao cruzar suas informações, só pode haver uma caixa. Mas o objetivo é ser o primeiro a encontrá-la. Pergunte aos seus colegas, e ainda assim aos concorrentes. Eles só podem responder com 'não' ou 'talvez', com muita lógica e um pouco de audácia, você pode entrar na lenda!",
"home.plus.title": "FORÇA",
"home.plus.1": "Um mecanismo de dedução incrível.",
"home.plus.2": "Uma imensa rejogabilidade.",
"home.plus.3": "Um tema surpreendente e forte.",
"game.time": "Tempo: 45 minutos",
"game.players": "Jogadores: 3 a 5 jogadores",
"game.age": "Idade: 10 anos ou mais",
"game.createdBy": "Criado por:",
"game.illustratedBy": "Ilustrado por:",
"log_in":" Entrar ",
"sign_up":" Inscrever-se ",
"log_out":" Sair ",
"join" : "Participar",
"create_room" : "Criar sala",
"play_solo" : "Jogador único",
"play" : "JOGAR",
"football": "Futebol",
"basketball": "Basquete",
"baseball": "Beisebol",
"tennis": "Tênis",
"bowling": "Boliche",
"white": "Branco",
"black": "Preto",
"blond": "Loiro",
"redhead": "Ruivo",
"brown": "Castanho",
"and": "e",
"or": "ou",
"or_sport": "ou",
"age_indice_start": "O suspeito tem entre",
"age_indice_more_start": "O suspeito tem pelo menos",
"age_indice_end": "anos",
"color_edges_start": "O suspeito tem pelo menos um amigo com",
"color_edges_end": "cabelo",
"color_start": "O suspeito tem",
"color_end": "cabelo",
"nb_friends_indice_start": "O suspeito tem",
"nb_friends_indice_end": "amigos",
"nb_sports_indice_start": "O suspeito pratica",
"nb_sports_indice_end": "esporte(s)",
"sport_start": "O suspeito joga pelo menos",
"sport_end": "",
"navbar.play" : "Jogar",
"navbar.presentation":"Apresentação",
"navbar.info":"Informação",
"round":"ronda",
"informations" : "Informação",
"info.intro.title":"Introdução ao jogo:",
"info.intro.text":"Bem-vindo ao nosso empolgante jogo de dedução, onde intriga e malícia se unem em uma aventura emocionante! Mergulhe em um mundo de mistério e intriga, onde cada interação conta, e cada pista o aproxima da verdade. Imagine um gráfico complexo onde cada vértice representa uma pessoa, cada eixo uma relação, e cada detalhe conta. Você está imerso em um desafio desafiador para descobrir quem, entre esses indivíduos, é o misterioso assassino. Cada jogador tem uma pista crucial, e apenas o compartilhamento estratégico dessas pistas o levará a resolver o mistério. Explore nossa página de regras para entender as complexidades do jogo, descobrir pistas que podem orientá-lo e desenvolver estratégias inteligentes para identificar o culpado. Manipule seus amigos para ser o primeiro a descobrir quem é o assassino escondido no gráfico. Você está pronto para aceitar o desafio e desmascarar o assassino oculto no gráfico? Que a investigação comece!",
"info.sommaire":"Conteúdo",
"info.title.composant":"Componentes do Jogo",
"info.title.objectif":"Objetivo do Jogo",
"info.title.deroulement":"Desenvolvimento do Jogo",
"info.title.indice_possible":"Possíveis Pistas do Jogo",
"info.pions" : "Peões",
"info.composant.text" : "Cada jogador será associado a uma cor específica que distinguirá as ações representadas pelos peões da seguinte forma:",
"info.composant.carre.title":"Fichas quadradas",
"info.composant.carre":"Essas fichas indicam uma negação. Quando um jogador coloca uma ficha quadrada, significa que sua pista elimina a pessoa designada.",
"info.composant.rond.title":"Fichas redondas",
"info.composant.rond":"Essas fichas representam um 'talvez'. O jogador que deposita esta ficha afirma que a pessoa é suspeita, mas isso não garante sua culpa. Há apenas um suspeito com uma ficha redonda para todos os jogadores no jogo, e este é o culpado!",
"info.car_perso":"Características",
"info.composant.textcar":"Além de seus nomes, os personagens são representados com outras características:",
"info.composant.age.title":"Idades",
"info.composant.age":"Cada pessoa tem uma idade para autenticá-la, variando entre 0 e 60 anos. A idade é uma característica que será confirmada por pistas na forma de ",
"info.composant.age.link":"grupos etários",
"info.composant.hair_col.title":"Cores de cabelo",
"info.composant.hair_col":"Os personagens também têm uma cor de cabelo, que pode ser encontrada nas seguintes cores:",
"hair.blanc":"Branco",
"hair.blond":"Loiro",
"hair.roux":"Ruivo",
"hair.chatain":"Castanho",
"hair.noir":"Preto",
"info.composant.sport.title":"Esportes",
"info.composant.sport":"Os hobbies dos personagens são representados por cinco esportes, respectivamente:",
"info.composant.baseball":"Beisebol",
"info.composant.basketball":"Basquete",
"info.composant.bowling":"Boliche",
"info.composant.football":"Futebol",
"info.composant.tennis":"Tênis",
"info.composant.sport.bis":"Entre esses esportes, cada personagem pode ter entre 0 e 3 esportes, o que facilita sua identificação usando as pistas que você possui.",
"info.objectif.intro":"Bem-vindo ao universo inteligente do nosso jogo de dedução, onde a decepção e a astúcia são as chaves para o sucesso. Sua missão é desvendar o mistério por trás de cada interação do gráfico complexo que representa as relações entre os indivíduos.",
"info.objectif.t1":"Manipulação Sutil",
"info.objectif.t1.text":"O objetivo final é descobrir quem, entre os indivíduos, é o assassino, mas não por meio de colaboração aberta. Pelo contrário, você usará manipulação sutil para turvar as linhas e distrair seus oponentes. Faça perguntas estratégicas, responda com malícia e plante pistas enganosas para se aproximar do resultado.",
"info.objectif.t2":"Trapaça",
"info.objectif.t2.text":"Cada rodada oferece a oportunidade de semear dúvidas entre seus oponentes. Quando um jogador faz perguntas a você, responda colocando astutamente uma ficha quadrada para indicar que 'dependendo da sua pista, essa pessoa não pode ser o culpado' ou uma ficha redonda para sugerir que eles permanecem na lista de suspeitos. Cuidado, porque cada gesto pode ser interpretado, e a verdade muitas vezes está escondida por trás de uma fachada de pistas enganosas.",
"info.objectif.t3":"Contra-manipulação",
"info.objectif.t3.text":"Se um jogador colocar uma ficha quadrada, o questionador também deve jogar seu jogo colocando uma ficha quadrada de sua cor em um nó do gráfico. A contra-manipulação se torna uma arma formidável para desviar a acusação e semear confusão.",
"info.interface": "Interface do Jogo",
"info.interface.text": "Para cada jogo, você terá certos elementos de interface disponíveis:",
"info.interface.param": "O botão 'Configurações' permite exibir e gerenciar várias configurações do jogo, como ativar ou desativar efeitos sonoros. Também é possível escolher o número de nós para o modo de jogo 'Enigma' usando este botão.",
"info.interface.reset": "O botão 'Redefinir' redefine o estado atual do gráfico. Se você o moveu demais ou deseja que sua exibição fique mais organizada, este botão é para você!",
"info.interface.info": "O botão 'Informações' redireciona para a página de regras do jogo (esta aqui).",
"info.interface.fiche": "O botão 'Folha de Dedução de Pistas' exibe uma tabela que, junto com o progresso do jogo, permite deduzir quais pistas são mais prováveis para cada jogador.",
"info.interface.indice": "O botão 'Pista Pessoal' é o mais importante; ele exibe sua pista secreta. Só você a conhece! Você terá que ser esperto para enganar seus amigos e mantê-la em segredo o máximo possível!",
"info.interface.vision": "O botão 'Últimas Jogadas' permite ao usuário visualizar as últimas 5 jogadas feitas. Ele age como uma ferramenta visual.",
"etape":"Etapa",
"info.deroulement.e1":"Faça Perguntas Estratégicas",
"info.deroulement.e1.text":"Cada rodada começa com um jogador fazendo perguntas a outro jogador sobre uma pessoa no gráfico. As respostas são formuladas colocando fichas quadradas ou redondas para indicar certeza ou dúvida quanto ao envolvimento dessa pessoa.",
"info.deroulement.e2":"Contra-manipulação e Contra-perguntas",
"info.deroulement.e2.text":"Se um jogador colocar uma ficha quadrada, o questionador também deve colocar uma ficha quadrada em um nó no gráfico. Contra-perguntas são uma maneira de confundir os jogadores e desviar a acusação.",
"info.deroulement.e3":"O Palpite Final",
"info.deroulement.e3.text":"O jogo atinge seu clímax quando um jogador tenta o 'Palpite' final, alegando que tal pessoa é o assassino. Outros jogadores podem contradizer essa declaração colocando suas próprias fichas quadradas. Se nenhuma refutação for feita, o jogador que fez o 'Palpite' vence o jogo, demonstrando sua maestria na arte da manipulação.",
"info.indice-possible.age":"Idade da Pessoa",
"info.indice-possible.hair":"Cor do cabelo de uma pessoa",
"info.indice-possible.sport":"Esporte(s) de uma pessoa",
"info.indice-possible.voisin":"Personagem dos vizinhos",
"info.mdj": "Modos de Jogo",
"info.mdj.text": "Existem vários outros modos de jogo que permitem adicionar variedade e tornar o jogo mais ou menos complexo.",
"info.mdj.mastermind": "Mestre das Pistas",
"info.mdj.mastermind.text": "O modo de jogo 'Mestre das Pistas' é um modo solo onde você deve ser capaz de adivinhar o culpado usando o número mínimo de palpites. Neste modo, você não terá pistas, mas precisará adivinhá-las durante o jogo. Selecionar um suspeito revela o estado de todas as pistas para esse suspeito. Torna-se possível adivinhar as pistas e depois identificar o culpado!",
"info.mdj.enigme": "Enigma",
"info.mdj.enigme.text": "Este é um modo solo com três níveis de dificuldade:",
"info.mdj.enigme.easy": "Enigma Fácil",
"info.mdj.enigme.easy.txt": "Neste modo de jogo, você tem acesso a todas as pistas e deve adivinhar o culpado que corresponde a todas essas pistas. Você pode praticar adivinhando o culpado neste modo.",
"info.mdj.enigme.medium": "Enigma Intermediário",
"info.mdj.enigme.medium.txt": "Neste modo de jogo, você não tem acesso a pistas, mas tem informações suficientes sobre alguns suspeitos para poder adivinhar o culpado. Essas informações o ajudarão a adivinhar as pistas, e o culpado é o suspeito para quem todas as pistas correspondem. Se você selecionar o suspeito errado, não se preocupe! Você terá informações sobre aquele suspeito, o que facilitará encontrar o culpado. O objetivo é encontrar o culpado no mínimo de palpites.",
"info.mdj.enigme.hard": "Enigma Difícil",
"info.mdj.enigme.hard.txt": "Esta variante final é semelhante ao enigma intermediário; no entanto, você deve encontrar o culpado na primeira tentativa, ou você perde! Depende de você jogar! O objetivo é encontrar o culpado no mínimo de tempo."
}

@ -0,0 +1,165 @@
{
"languageSelector.french": "Французский",
"languageSelector.english": "Английский",
"languageSelector.espagnol": "Испанский",
"languageSelector.portuguese": "Португальский",
"languageSelector.russian": "Русский",
"home.histoire.title": "ИСТОРИЯ",
"home.histoire": "Криптозоология изучает следы легендарных монстров: криптидов. Иети, Чупакабра, зверь из Жеводана, Дахут и т. д., это очень серьезные темы для вас... Пересеките улики и первым откройте их!",
"home.jeu.title": "ИГРА",
"home.jeu": "У каждого игрока есть улика на поле, где находится существо. Путем кросс-референцирования вашей информации может быть только одна клетка. Но цель - быть первым, кто ее найдет. Спрашивайте своих коллег, а также конкурентов. Они могут отвечать только «нет» или «возможно». С логикой и дерзостью вы можете войти в легенду!",
"home.plus.title": "СИЛА",
"home.plus.1": "Умопомрачительный механизм дедукции.",
"home.plus.2": "Огромная возможность переигрывания.",
"home.plus.3": "Удивительная и крепкая тема.",
"game.time": "Время: 45 минут",
"game.players": "Игроки: от 3 до 5 человек",
"game.age": "Возраст: от 10 лет",
"game.createdBy": "Создано:",
"game.illustratedBy": "Иллюстрировано:",
"log_in":" Войти ",
"sign_up":" Зарегистрироваться ",
"log_out":" Выйти ",
"join" : "Присоединиться",
"create_room" : "Создать комнату",
"play_solo" : "Один игрок",
"play" : "ИГРАТЬ",
"football": "Футбол",
"basketball": "Баскетбол",
"baseball": "Бейсбол",
"tennis": "Теннис",
"bowling": "Боулинг",
"white": "Белый",
"black": "Черный",
"blond": "Блондин",
"redhead": "Рыжий",
"brown": "Каштановый",
"and": "и",
"or": "или",
"or_sport": "или",
"age_indice_start": "Подозреваемый находится в возрасте от",
"age_indice_more_start": "Подозреваемый по крайней мере",
"age_indice_end": "лет",
"color_edges_start": "У подозреваемого есть хотя бы один друг с",
"color_edges_end": "волосами",
"color_start": "У подозреваемого есть",
"color_end": "волосы",
"nb_friends_indice_start": "У подозреваемого есть",
"nb_friends_indice_end": "друзей",
"nb_sports_indice_start": "Подозреваемый играет в",
"nb_sports_indice_end": "вида(ов) спорта",
"sport_start": "Подозреваемый играет по меньшей мере в",
"sport_end": "",
"navbar.play" : "Разыграть",
"navbar.presentation":"Презентационный",
"navbar.info":"Информация",
"round" : "Раунд",
"informations" : "Информация",
"info.intro.title":"Введение в игру :",
"info.intro.text":"Добро пожаловать в нашу захватывающую игру-дедукцию, где интрига и хитрость соединяются в захватывающем приключении! Погрузитесь в мир тайны и интриги, где каждое взаимодействие имеет значение, и каждая улика приближает вас к правде. Представьте себе сложный граф, где каждая вершина представляет собой человека, каждая ось - отношение, и каждая деталь имеет значение. Вы бросаетесь в сложное испытание, чтобы открыть тайну, кто из этих людей - загадочный убийца. У каждого игрока есть важная улика, и только стратегический обмен этими уликами приведет вас к разгадке тайны. Изучите нашу страницу с правилами, чтобы понять тонкости игры, обнаружьте улики, которые могут вас направить, и разрабатывайте умные стратегии для выявления виновного. Манипулируйте своими друзьями, чтобы быть первым, кто узнает, кто убийца! Готовы ли вы принять вызов и разоблачить убийцу, скрытого в графе? Пусть начнется расследование!",
"info.sommaire":"Содержание",
"info.title.composant":"Игровые компоненты",
"info.title.objectif":"Цель игры",
"info.title.deroulement":"Ход игры",
"info.title.indice_possible":"Возможное игровое подсказка",
"info.pions" : "Фишки",
"info.composant.text" : "Каждому игроку будет присвоен определенный цвет, который будет отличать действия, представленные фишками, следующим образом :",
"info.composant.carre.title":"квадратные фишки",
"info.composant.carre":"Эти фишки указывают на отрицание. Когда игрок ставит квадратную фишку, это означает, что его улика исключает указанного человека.",
"info.composant.rond.title":"круглые фишки",
"info.composant.rond":"Эти фишки представляют собой 'возможно'. Игрок, укладывающий эту фишку, утверждает, что человек является подозреваемым, но это не гарантирует его вину. Есть только один подозреваемый с круглой фишкой для всех игроков в игре, и это убийца!",
"info.car_perso":"Характеристики",
"info.composant.textcar":"Помимо их имен, персонажи представлены другими характеристиками :",
"info.composant.age.title":"Возраст",
"info.composant.age":"У каждого человека есть возраст для его идентификации, варьирующийся от 0 до 60 лет. Возраст - это характеристика, которая будет подтверждена уликами в виде ",
"info.composant.age.link":"возрастных групп",
"info.composant.hair_col.title":"Цвет волос",
"info.composant.hair_col":"У персонажей также есть цвет волос, который может быть следующими цветами :",
"hair.blanc":"Белый",
"hair.blond":"Блондин",
"hair.roux":"Рыжий",
"hair.chatain":"Каштановый",
"hair.noir":"Черный",
"info.composant.sport.title":"Спорт",
"info.composant.sport":"Увлечения персонажей представлены пятью видами спорта :",
"info.composant.baseball":"Бейсбол",
"info.composant.basketball":"Баскетбол",
"info.composant.bowling":"Боулинг",
"info.composant.football":"Футбол",
"info.composant.tennis":"Теннис",
"info.composant.sport.bis":"Среди этих видов спорта каждый персонаж может заниматься от 0 до 3 видов спорта, что облегчит их идентификацию с использованием имеющихся у вас улик.",
"info.objectif.intro":"Добро пожаловать в умный мир нашей игры-дедукции, где обман и хитрость - ключи к успеху. Ваша миссия - раскрывать тайну за каждым взаимодействием сложного графа, представляющего отношения между людьми.",
"info.objectif.t1":"Тонкая манипуляция",
"info.objectif.t1.text":"Основная цель - узнать, кто из людей является убийцей, но не через открытое сотрудничество. Напротив, вы будете использовать тонкую манипуляцию, чтобы размыть границы и отвлечь своих оппонентов. Задавайте стратегические вопросы, отвечайте злонамеренно и сеивайте вводные улики, чтобы приблизиться к результату.",
"info.objectif.t2":"Хитрость",
"info.objectif.t2.text":"Каждый раунд предоставляет возможность сеять сомнения среди ваших оппонентов. Когда вас допрашивают, отвечайте, умно ставя квадратную фишку, чтобы указать, что 'в зависимости от вашей улики этот человек не может быть убийцей' или круглую фишку, чтобы предположить, что они остаются в списке подозреваемых. Будьте осторожны, потому что каждый жест может быть истолкован, и правда часто скрыта за фасадом вводных улик.",
"info.objectif.t3":"Контрманипуляция",
"info.objectif.t3.text":"Если игрок ставит квадратную фишку, допрашивающий также должен играть свою игру, ставя квадратную фишку своего цвета на узле графа. Контрманипуляция становится мощным оружием для отвода обвинения и посева смуты.",
"info.interface": "Игровой интерфейс",
"info.interface.text": "Для каждой игры у вас будут доступны определенные элементы интерфейса:",
"info.interface.param": "Кнопка 'Настройки' позволяет отображать и управлять различными настройками игры, такими как включение или выключение звуковых эффектов. С ее помощью также можно выбирать количество узлов для режима игры 'Загадка'.",
"info.interface.reset": "Кнопка 'Сброс' сбрасывает текущее состояние графа. Если вы передвинули его слишком сильно или хотите, чтобы его отображение было аккуратнее, эта кнопка для вас!",
"info.interface.info": "Кнопка 'Информация' перенаправляет на страницу правил игры (эту).",
"info.interface.fiche": "Кнопка 'Таблица вывода улик' отображает таблицу, которая, вместе с ходом игры, позволяет делать выводы о том, какие улики наиболее вероятны для каждого игрока.",
"info.interface.indice": "Кнопка 'Личная улика' самая важная; она отображает вашу секретную улику. Только вы знаете ее! Вам придется быть умным, чтобы обмануть своих друзей и держать ее в секрете как можно дольше!",
"info.interface.vision": "Кнопка 'Последние движения' позволяет пользователю визуализировать последние 5 сделанных ходов. Она действует как визуальный инструмент.",
"etape": "Шаг",
"info.deroulement.e1": "Задавайте стратегические вопросы",
"info.deroulement.e1.text": "Каждый раунд начинается с того, что один игрок задает другому игроку вопрос о человеке на графике. Ответы формулируются путем установки квадратных или круглых фишек, чтобы указать уверенность или сомнение в причастности этого человека.",
"info.deroulement.e2": "Контролирование и контропросы",
"info.deroulement.e2.text": "Если игрок устанавливает квадратную фишку, задающий вопрос также должен установить квадратную фишку на узле графика. Контропросы - это способ сбить с толку игроков и отклонить обвинение.",
"info.deroulement.e3": "Окончательное предположение",
"info.deroulement.e3.text": "Игра достигает своего пика, когда игрок пытается сделать окончательное 'Предположение', утверждая, что такой-то человек - убийца. Другие игроки могут затем противоречить этому заявлению, устанавливая свои собственные квадратные фишки. Если не последует опровержение, игрок, сделавший 'Предположение', выигрывает игру, продемонстрировав свое мастерство в искусстве манипуляции.",
"info.indice-possible.age": "Возраст человека",
"info.indice-possible.hair": "Цвет волос человека",
"info.indice-possible.sport": "Спорт(ы) человека",
"info.indice-possible.voisin": "Характер соседей",
"info.mdj": "Режимы игры",
"info.mdj.text": "Существует различные режимы игры, которые позволяют вам добавить разнообразие и сделать игру более или менее сложной.",
"info.mdj.mastermind": "Мастер-майнд",
"info.mdj.mastermind.text": "Режим игры 'Мастер-майнд' - это сольный режим, в котором вы должны угадать убийцу, используя минимальное количество догадок. В этом режиме у вас не будет никаких улик, но вам нужно будет угадать их в ходе игры. Выбор подозреваемого позволяет узнать состояние всех улик для этого подозреваемого. Становится возможным угадать улики, а затем определить убийцу!",
"info.mdj.enigme": "Загадка",
"info.mdj.enigme.text": "Это сольный режим с тремя уровнями сложности:",
"info.mdj.enigme.easy": "Легкая загадка",
"info.mdj.enigme.easy.txt": "В этом режиме у вас есть доступ ко всем уликам, и вы должны угадать убийцу, соответствующего всем этим уликам. Вы можете практиковаться в угадывании убийцы в этом режиме.",
"info.mdj.enigme.medium": "Средняя загадка",
"info.mdj.enigme.medium.txt": "В этом режиме у вас нет доступа к уликам, но у вас есть достаточно информации о некоторых подозреваемых, чтобы угадать убийцу. Эта информация поможет вам угадать улики, и убийца - подозреваемый, для которого все улики совпадают. Если вы выберете неправильного подозреваемого, не волнуйтесь! У вас будет информация о том подозреваемом, что облегчит вам поиск убийцы. Цель - найти убийцу минимальным количеством догадок.",
"info.mdj.enigme.hard": "Трудная загадка",
"info.mdj.enigme.hard.txt": "Этот последний вариант аналогичен средней загадке; однако вы должны найти убийцу с первой попытки, иначе вы проиграли! Решайте сами! Цель - найти убийцу минимальным количеством времени."
}

@ -1,3 +1,4 @@
import { random } from "lodash";
import SessionService from "../../services/SessionService";
import { socket } from "../../SocketConfig";
import User from "../User";
@ -10,33 +11,77 @@ class DbUserService implements IUserService{
// Vérifie si il y a une session
if (sessionData.user) {
// Il y a une session on récupère les infos du joueur
const updatedPlayer: User = new User(socket.id, sessionData.user.pseudo, sessionData.user.profilePicture, {
nbGames: sessionData.user.soloStats.nbGames,
bestScore: sessionData.user.soloStats.bestScore,
avgNbTry: sessionData.user.soloStats.avgNbTry,
const currentUser = new User(socket.id, sessionData.user.pseudo, sessionData.profilePicture,
{
nbGames: sessionData.user.mastermindStats.nbGames,
bestScore: sessionData.user.mastermindStats.bestScore,
avgNbTry: sessionData.user.mastermindStats.avgNbTry
},
{
nbGames: sessionData.user.easyEnigmaStats.nbGames,
nbWins: sessionData.user.easyEnigmaStats.nbWins,
ratio: sessionData.user.easyEnigmaStats.ratio,
bestTime: sessionData.user.easyEnigmaStats.bestTime,
avgTime: sessionData.user.easyEnigmaStats.avgTime
},
{
nbGames: sessionData.user.mediumEnigmaStats.nbGames,
bestScore: sessionData.user.mediumEnigmaStats.bestScore,
avgNbTry: sessionData.user.mediumEnigmaStats.avgNbTry
},
{
nbGames: sessionData.user.hardEnigmaStats.nbGames,
nbWins: sessionData.user.hardEnigmaStats.nbWins,
ratio: sessionData.user.hardEnigmaStats.ratio,
bestTime: sessionData.user.hardEnigmaStats.bestTime,
avgTime: sessionData.user.hardEnigmaStats.avgTime
},
{
nbGames: sessionData.user.onlineStats.nbGames,
nbWins: sessionData.user.onlineStats.nbWins,
ratio: sessionData.user.onlineStats.ratio,
})
return [updatedPlayer, true]
} else {
// Pas de session on génère un guest random
const guestPlayer: User = new User(socket.id, 'Guest_' + Math.floor(Math.random() * 1000000), '',
});
currentUser.nbNodes = sessionData.user.nbNodes || 25
currentUser.nbIndices = sessionData.user.nbIndices || 3
return [currentUser, true];
}
else{
const guestUser = new User(socket.id, "Guest_" + random(1000, 9999), "",
{
nbGames: 0,
bestScore: 0,
avgNbTry: 0
},
{
nbGames: 0,
nbWins: 0,
ratio: 0,
bestTime: 0,
avgTime: 0
},
{
nbGames: 0,
bestScore: 0,
avgNbTry: 0,
avgNbTry: 0
},
{
nbGames: 0,
nbWins: 0,
ratio: 0,
})
return [guestPlayer, false]
bestTime: 0,
avgTime: 0
},
{
nbGames: 0,
nbWins: 0,
ratio: 0,
});
guestUser.nbNodes = 25
guestUser.nbIndices = 3
return [guestUser, false];
}
} catch (error) {
console.error(error);
@ -44,9 +89,9 @@ class DbUserService implements IUserService{
}
}
async updateSoloStats(pseudo: string, nbGames: number, bestScore: number, avgNbTry: number): Promise<void> {
async addMastermindStats(pseudo: string, score: number, time: number): Promise<void> {
try {
const result = await SessionService.updateSoloStats(pseudo, nbGames, bestScore, avgNbTry);
const result = await SessionService.addMastermindStats(pseudo, score, time);
if (result) {
console.log("Stats solo updated");
} else {
@ -57,9 +102,48 @@ class DbUserService implements IUserService{
}
}
async updateOnlineStats(pseudo: string, nbGames: number, bestScore: number, ratio: number): Promise<void> {
async addEasyEnigmaStats(pseudo: string, win: number, time: number): Promise<void> {
try {
const result = await SessionService.updateOnlineStats(pseudo, nbGames, bestScore, ratio);
const result = await SessionService.addEasyEnigmaStats(pseudo, win, time);
if (result) {
console.log("Stats easy updated");
} else {
console.log("Stats easy not updated");
}
} catch (error) {
console.error(error);
}
}
// async addMediumEnigmaStats(pseudo: string, win: number, time: number): Promise<void> {
// try {
// const result = await SessionService.addMediumEnigmaStats(pseudo, win, time);
// if (result) {
// console.log("Stats medium updated");
// } else {
// console.log("Stats medium not updated");
// }
// } catch (error) {
// console.error(error);
// }
// }
async addHardEnigmaStats(pseudo: string, win: number, time: number): Promise<void> {
try {
const result = await SessionService.addHardEnigmaStats(pseudo, win, time);
if (result) {
console.log("Stats hard updated");
} else {
console.log("Stats hard not updated");
}
} catch (error) {
console.error(error);
}
}
async addOnlineStats(pseudo: string, win: number, time: number): Promise<void> {
try {
const result = await SessionService.addOnlineStats(pseudo, win, time);
if (result) {
console.log("Stats online updated");
} else {
@ -69,6 +153,19 @@ class DbUserService implements IUserService{
console.error(error);
}
}
async changeNodesIndices(nbNodes: number, nbIndices: number): Promise<void> {
try {
const result = await SessionService.changeNodesIndices(nbNodes, nbIndices);
if (result) {
console.log("Nodes updated");
} else {
console.log("Nodes not updated");
}
} catch (error) {
console.error(error);
}
}
}
export default DbUserService

@ -2,8 +2,12 @@ import User from "../User";
interface IUserService{
fetchUserInformation(): Promise<[User | null, boolean]>
updateSoloStats(pseudo: string, nbGames: number, bestScore: number, avgNbTry: number): Promise<void>
updateOnlineStats(pseudo: string, nbGames: number, bestScore: number, ratio: number): Promise<void>
changeNodesIndices(nbNodes: number, nbIndices: number): Promise<void>
addMastermindStats(pseudo: string, score: number, time: number): Promise<void>
addEasyEnigmaStats(pseudo: string, win: number, time: number): Promise<void>
// addMediumEnigmaStats(pseudo: string, win: number, time: number): Promise<void>
addHardEnigmaStats(pseudo: string, win: number, time: number): Promise<void>
addOnlineStats(pseudo: string, win: number, time: number): Promise<void>
}

@ -98,6 +98,9 @@ class EasyBot extends Bot{
tabFilterPerson.push(p)
}
})
if (tabFilterPerson.length == 0){
return -1
}
return tabFilterPerson[Math.floor(Math.random() * tabFilterPerson.length)].getId()
}

@ -13,8 +13,6 @@ class EnigmeDuJourCreator{
map.set(p.getId(), [])
})
console.log("START ENIGME")
choosenIndices.forEach((choosenIndice) => {
const choosenIndiceTester = IndiceTesterFactory.Create(choosenIndice)
const modifiedPersons: Pair<Person, boolean>[] = []
@ -31,7 +29,7 @@ class EnigmeDuJourCreator{
possibleIndices.forEach((possibleIndice, index) =>{
const tester = IndiceTesterFactory.Create(possibleIndice)
if (tester.Works(person)){
const t = veryTmpIndice.findIndex((tmpIndice) => tmpIndice.getId() == possibleIndice.getId())
const t = veryTmpIndice.findIndex((tmpIndice) => tmpIndice.getId() == possibleIndice.getId())
if (t != -1){
veryTmpIndice.splice(t, 1)
}
@ -60,10 +58,7 @@ class EnigmeDuJourCreator{
}
possibleIndices = [...tmpPossibleIndices]
modifiedPersons.push(choosenPair)
console.log(possibleIndices)
}
console.log("choosenIndice => " + choosenIndice.ToString("fr"))
console.log("possibleIndices => " + possibleIndices[0].ToString("fr"))
modifiedPersons.forEach((pair) =>{
map.get(pair.first.getId())?.push(new Pair(choosenIndice, pair.second))
})

@ -2,6 +2,7 @@ class Edge{
public from: number
public to: number
public color: string = "black"
constructor(from: number, to: number){
this.from = from

@ -17,7 +17,7 @@ class NetworkGenerator{
const tabPerson: Person[] = []
const tabNames = json.names
const tabNames = [...json.names]
/*
let id = 0

@ -2,12 +2,20 @@ import Player from "./Player";
import defaultImg from "../res/img/Person.png"
class User extends Player{
public soloStats: any
public mastermindStats: any
public easyEnigmaStats: any
public mediumEnigmaStats: any
public hardEnigmaStats: any
public onlineStats: any
public nbNodes: number = 25
public nbIndices: number = 3
constructor(id: string, pseudo: string, profilePicture: string, soloStats: any, onlineStats: any){
constructor(id: string, pseudo: string, profilePicture: string, soloStats: any, easyEnigmaStats: any, mediumEnigmaStats: any, hardEnigmaStats: any, onlineStats: any){
super(id, pseudo, profilePicture || defaultImg)
this.soloStats=soloStats
this.mastermindStats=soloStats
this.easyEnigmaStats=easyEnigmaStats
this.mediumEnigmaStats=mediumEnigmaStats
this.hardEnigmaStats=hardEnigmaStats
this.onlineStats=onlineStats
}
@ -18,7 +26,9 @@ class User extends Player{
id: this.id,
profilePicture: this.profilePicture,
pseudo: this.pseudo,
soloStats: this.soloStats,
easyEnigmaStats: this.easyEnigmaStats,
mediumEnigmaStats: this.mediumEnigmaStats,
hardEnigmaStats: this.hardEnigmaStats,
onlineStats: this.onlineStats
};
}

@ -2,3 +2,5 @@ declare module "*.png";
declare module "*.svg";
declare module "*.jpeg";
declare module "*.jpg";
declare module '*.mp3';
declare module '*.wav';

@ -0,0 +1,22 @@
{
"names": [
"Sherlock Holmes",
"Inspecteur Gadget",
"Scooby-Doo",
"Marple",
"Spocky",
"Phoenix",
"Batman",
"James Bond",
"Sherlockio",
"Verra",
"Hercule Poirot",
"Colombo",
"MacGyver",
"Lupin",
"D. Conan",
"Pr. Layton",
"Dr. Watson"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save