pull/90/head
Pierre Ferreira 2 years ago
commit f56ee8b0e4

10
.gitignore vendored

@ -41,4 +41,12 @@ bower_components
psd
thumb
sketch
sketch
### modules ###
yarn.lock
package-lock.json
# db
socialgraph.db

@ -1,2 +1,110 @@
# Cryptid
(:construction: ***Le readme n'est pas a sa version final et est susceptible de changer*** :construction:)
# Présentation :
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 !
## Comment utiliser notre projet :
*Prérequis :* Avoir **git** et **npm** (et donc **Node.js**) sur son poste.
### Etape 1 : Récupérer le projet
Dans un premier terminal, exécutez les commandes suivantes :
```bash
git clone https://codefirst.iut.uca.fr/git/Crypteam/Cryptid.git
cd cryptide_project
npm i --force
```
### Etape 2 : Configurer le réseau
1. Pour jouer en distant, vous devez vous trouver sur le même réseau. (Il faut quand même éviter les réseaux tels qu' eduroam)
2. Récupérez votre adresse IP :
Sur **Windows**
> - Ouvrez l'invite de commandes (`cmd`) et tapez la commande `ipconfig`. Repérez la section de votre connexion sans fil et notez l'adresse IPv4.
Sur **MacOS** / **Linux**
> - Ouvrez le terminal et tapez la commande `ifconfig` ou `ip addr`. Recherchez la section de votre connexion sans fil et notez l'adresse IP.
### Etape 3 : Configurer les serveurs
1. **Ouvrez le fichier `./src/AdressSetup.ts` :**
- Localisez le fichier dans le répertoire de votre application.
2. **Modifiez l'adresse des serveurs :**
- Remplacez les adresses IP existantes par celle que vous avez notée à l'étape 2.
Exemple :
```typescript
// ./AdressSetup.ts
const ADRESSE_WEBSERVER = "http://{VOTRE_IP}:3002"
const ADRESSE_DBSERVER = "http://{VOTRE_IP}:3003"
const ADRESSE_WEBSITE = ""
export {ADRESSE_DBSERVER, ADRESSE_WEBSERVER, ADRESSE_WEBSITE}
```
3. **Ouvrez le fichier `./server/server.js` :**
- Localisez le fichier dans le répertoire de votre application.
4. **Modifiez les adresses qui peuvent accèder aux serveurs :**
- Ajoutez votre adresse notée à l'étape 2 dans le cors.
Exemple :
```typescript
const app = express();
const server = http.createServer(app);
const io = socketIO(server, {
cors: {
origin: ["http://{VOTRE_IP}:3000", "http://localhost:3000"], // Remplacez par l'URL de votre application React
methods: ["GET", "POST"],
credentials: true
}
});
```
5. **Apportez la même modification au fichier `./src/server/server.js` :**
- Une fois le fichier ouvert, appuyez-vous sur l'exemple précédent pour apporter les modifications necéssaire.
### Etape 4 : Démarrer les serveurs
1. **Ouvrez un second terminal :**
- Exécutez le script `./startServer.sh`.
### Etape 5 : Démarrer l'application
1. Revenez dans le premier terminal et exécutez la commande suivante :
```bash
npm start
```
2. L'application va s'ouvrir dans votre navigateur et vous pouvez jouez à notre jeu.
### Etape bonus : Jouez avec ces amis
1. Sur la première page de l'application, cliquez sur **Jouez** > **Créer une partie**.
2. Une fois dans le lobby, **copiez le lien** de la partie et l'envoyez à vos amis.
3. Lancez la partie et **amusez-vous** !
# ::construction_worker:: Développeurs
- Thomas Chazot : thomas.chazot@etu.uca.fr
- Pierre Ferreira : pierre.ferreira@etu.uca.fr
- Baptiste Marcel : baptise.marcel@etu.uca.fr
<div align="center">
<a href = "https://codefirst.iut.uca.fr/git/thomas.chazot2">
<img src="https://codefirst.iut.uca.fr/git/avatars/4ce5054250e693b04367d853b3c469b1?size=870" width="50" >
</a>
<a href = "https://codefirst.iut.uca.fr/git/pierre.ferreira">
<img src="https://codefirst.iut.uca.fr/git/avatars/edbacace5f621ae77077f206ebdcee27?size=870" width="50" >
</a>
<a href = "https://codefirst.iut.uca.fr/git/baptiste.marcel">
<img src="https://codefirst.iut.uca.fr/git/avatars/6b1f2a8b8f636d8f4d315b060075578f?size=870" width="50" >
</a>
© IUT - Auvergne
</div>

File diff suppressed because it is too large Load Diff

@ -10,11 +10,19 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/lodash": "^4.14.200",
"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",
"jest": "^27.5.1",
"express-session": "^1.17.3",
"file-saver": "^2.0.5",
"jszip": "^3.10.1",
"jzip": "^1.0.0",
"lodash": "^4.17.21",
"mysql": "^2.18.1",
"react": "^18.2.0",
"react-bootstrap": "^2.9.1",
"react-country-flag": "^3.1.0",
@ -27,7 +35,7 @@
"react-switch": "^7.0.0",
"socket.io": "^4.7.2",
"socket.io-client": "^4.7.2",
"ts-node": "^10.9.1",
"sqlite3": "^5.1.6",
"typescript": "^5.2.2",
"vis-network": "^9.1.9",
"web-vitals": "^2.1.4"
@ -57,11 +65,7 @@
]
},
"devDependencies": {
"@babel/core": "^7.23.3",
"@babel/preset-env": "^7.23.3",
"@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.23.3",
"@jest/types": "^29.6.3",
"@types/file-saver": "^2.0.7",
"@types/react-router-hash-link": "^2.4.9",
"@types/uuid": "^9.0.7",
"babel-jest": "^29.7.0"

@ -7,7 +7,7 @@ const app = express();
const server = http.createServer(app);
const io = socketIO(server, {
cors: {
origin: ["http://localhost:3000", "http://localhost:3001"], // Remplacez par l'URL de votre application React
origin: ["http://172.20.10.4:3000", "http://localhost:3000"], // Remplacez par l'URL de votre application React
methods: ["GET", "POST"],
credentials: true
}
@ -15,38 +15,57 @@ const io = socketIO(server, {
const map = new Map()
// ... le reste de votre configuration du serveur
server.listen(3002, () => {
console.log('Serveur Socket.IO écoutant sur le port 3001');
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) =>{
io.to(room).emit("game created", network, person, indices, Math.floor(Math.random() * map.get(room).length))
socket.on('network created', (network, person, indices, room, start) =>{
io.to(room).emit("game created", network, person, indices, start)
});
socket.on("lobby joined", (room, name) =>{
socket.join(room)
socket.on("lobby joined", (room, player) =>{
console.log(player)
if (player.type=="User"){
socket.join(room)
}
if (map.get(room) == undefined){
map.set(room, [{id: socket.id, name: name}])
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){
if (tab[i].id === socket.id && player.type==="User"){
tab.splice(i, 1)
}
}
map.get(room).push({id: socket.id, name: name})
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))
})
@ -55,8 +74,8 @@ io.on('connection', (socket) => {
io.to(askingPlayer.id).emit("already asked", nodeId, askedPlayer)
})
socket.on("ask player", (nodeId, playerId, askingPlayer, askingPlayerIndex) =>{
io.to(playerId).emit("asked", nodeId, askingPlayer, askingPlayerIndex)
socket.on("ask player", (nodeId, playerId, askingPlayer) =>{
io.to(playerId).emit("asked", nodeId, askingPlayer)
})
socket.on("asked all 1by1", (id, playerId) =>{
@ -73,15 +92,41 @@ io.on('connection', (socket) => {
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) =>{
console.log(playerIndex)
io.to(room).emit("node checked", id, works, color, 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,30 @@
#!/bin/sh
if lsof -Pi :3000 -sTCP:LISTEN -t >/dev/null; then
# Tuer le processus associé au port
pid=$(lsof -Pi :3000 -sTCP:LISTEN -t)
kill -9 $pid
fi
if lsof -Pi :3002 -sTCP:LISTEN -t >/dev/null; then
# Tuer le processus associé au port
pid=$(lsof -Pi :3002 -sTCP:LISTEN -t)
kill -9 $pid
fi
if lsof -Pi :3003 -sTCP:LISTEN -t >/dev/null; then
# Tuer le processus associé au port
pid=$(lsof -Pi :3003 -sTCP:LISTEN -t)
kill -9 $pid
fi
npm start &
node server/server.js &
node src/server/server.js

@ -0,0 +1,7 @@
const ADRESSE_WEBSERVER = "http://localhost:3002"
const ADRESSE_DBSERVER = "http://localhost:3003"
const ADRESSE_WEBSITE = ""
export {ADRESSE_DBSERVER, ADRESSE_WEBSERVER, ADRESSE_WEBSITE}

@ -3,21 +3,27 @@ import React from 'react';
import { useState } from 'react';
import { IntlProvider } from 'react-intl';
import { GameProvider } from './Contexts/GameContext';
import { AuthProvider } from './Contexts/AuthContext';
/* Page */
import Home from './Pages/Home';
import Login from './Pages/LoginForm';
import SignUp from './Pages/SignUpForm';
import Play from './Pages/Play';
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 SoloGame from './Pages/SoloGame';
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";
@ -33,7 +39,6 @@ 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';
const messages = {
fr: messagesFr,
@ -47,7 +52,7 @@ function App() {
const [locale, setLocale] = useState('fr');
//@ts-ignore
const changeLocale = (newLocale) => {
const changeLocale = async (newLocale) => {
setLocale(newLocale);
};
@ -63,9 +68,10 @@ function App() {
// <img src={logo} className="App-logo" alt="logo" />
// </header>
// </div>
<AuthProvider>
<GameProvider>
{/*@ts-ignore*/}
{/*@ts-ignore*/}
<IntlProvider locale={locale} messages={messages[locale]}>
<ThemeProvider>
<BrowserRouter>
@ -79,13 +85,15 @@ function App() {
<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="/join" element={<Lobbies/>}/>
{/* <Route path="/solo" element={<SoloGame locale={locale} changeLocale={changeLocale} />}/> */}
</Routes>
</BrowserRouter>
</ThemeProvider>
</IntlProvider>
</GameProvider>
</AuthProvider>
);
}

@ -53,4 +53,41 @@ function colorToEmoji(color: string, works: boolean): string{
}
}
export {colorToEmoji, positionToColor}
function positionToEmoji(pos: number, works: boolean): string{
if (works){
switch (pos) {
case 0:
return "🔵";
case 1:
return "🟢";
case 2:
return "🟡";
case 3:
return "🟣";
case 4:
return "🔴";
default:
return "🟤";
}
}
else{
switch (pos) {
case 0:
return "🟦";
case 1:
return "🟩";
case 2:
return "🟨";
case 3:
return "🟪";
case 4:
return "🟥";
default:
return "🟫";
}
}
}
export {colorToEmoji, positionToColor, positionToEmoji}

@ -1,5 +1,6 @@
.buttonNabImg{
margin: 15px 0;
padding: 10px;
width: auto;
height: 65px;

@ -29,7 +29,7 @@ const ChoiceBar = () => {
let playerIndex = actualPlayerIndex + 1
if (indiceTester.Works(person)){
socket.emit("node checked", nodeId, true, positionToColor(actualPlayerIndex), room, nextPlayerIndex)
socket.emit("node checked", nodeId, true, actualPlayerIndex, room, nextPlayerIndex)
while(playerIndex != actualPlayerIndex){
if (playerIndex == players.length){
playerIndex = 0
@ -38,7 +38,7 @@ const ChoiceBar = () => {
const works = tester.Works(person)
await delay(500);
socket.emit("asked all 1by1", person.getId(), players[playerIndex].id)
socket.emit("node checked", nodeId, works, positionToColor(playerIndex), room, nextPlayerIndex)
socket.emit("node checked", nodeId, works, playerIndex, room, nextPlayerIndex)
if(!works){
return
}
@ -61,7 +61,7 @@ const ChoiceBar = () => {
{players.map((player, index) => (
player.id !== socket.id &&
<button key={index} className="choice-bar-button" onClick={() => askPlayer(player.id)} style={{ backgroundColor: theme.colors.primary }}>
{player.name}
{player.pseudo}
</button>
))}
</div>

@ -11,4 +11,4 @@
align-items: center;
background-color: #f5f5f5;
}
}

@ -1,50 +1,130 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import { DataSet, Network} from "vis-network/standalone/esm/vis-network";
import EdgesCreator from "../model/EdgesCreator";
import GraphCreator from "../model/Graph/GraphCreator";
import IndiceChooser from "../model/IndiceChooser";
import SportIndice from "../model/Indices/SportIndice";
import NetworkGenerator from "../model/NetworkGenerator";
import Sport from "../model/Sport";
import Stub from "../model/Stub";
import "./GraphContainer.css";
import NodePerson from "../model/Graph/NodePerson";
import IndiceTesterFactory from "../model/Factory/IndiceTesterFactory";
import GameCreator from "../model/GameCreator";
import io from 'socket.io-client';
import JSONParser from "../JSONParser";
import PersonNetwork from "../model/PersonsNetwork";
import Person from "../model/Person";
import Indice from "../model/Indices/Indice";
import { useLocation } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import { useGame } from "../Contexts/GameContext";
import { socket } from "../SocketConfig"
import { colorToEmoji, positionToColor } from "../ColorHelper";
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";
interface MyGraphComponentProps {
onNodeClick: (shouldShowChoiceBar: boolean) => void;
handleShowTurnBar: (shouldShowTurnBar: boolean) => void
handleTurnBarTextChange: (newTurnBarText: string) => void
setPlayerTouched: (newPlayerTouch: number) => void;
playerTouched: number
changecptTour: (newcptTour : number) => void
addToHistory: (message : string) => void
solo : boolean
isDaily : boolean
isEasy: boolean
setNetwork: (network: Network) => void
showLast: boolean
setNetworkEnigme: (networkEnigme: Map<number, Pair<Indice, boolean>[]>) => void
setPlayerIndex: (playerIndex: number) => void
}
let lastAskingPlayer = 0
let lastNodeId = -1
let first = true
let askedWrong = false
let cptTour: number = 0
let mapIndexPersons: Map<number, Person[]> = new Map<number, Person[]>()
let touchedPlayer = -1
let botIndex = -1
let askedWrongBot = false
let lastSocketId= ""
let firstLap = true
let cptHistory = 0
let lastNodes: NodePerson[] = []
let cptEndgame = 0
let firstEnigme = true
let firstIndex = true
let endgame= false
let firstHistory = true
const MyGraphComponent: React.FC<MyGraphComponentProps> = ({onNodeClick, handleShowTurnBar, handleTurnBarTextChange, changecptTour, solo}) => {
const MyGraphComponent: React.FC<MyGraphComponentProps> = ({onNodeClick, handleShowTurnBar, handleTurnBarTextChange, playerTouched, setPlayerTouched, changecptTour, solo, isDaily, isEasy, addToHistory, showLast, setNetwork, setNetworkEnigme, setPlayerIndex}) => {
let cptTour: number = 0
const { indices, indice, person, personNetwork, setNodeIdData, players, askedPersons, setActualPlayerIndexData, room, actualPlayerIndex, turnPlayerIndex, onlyFalse, setOnlyFalseData } = useGame();
//* Gestion du temps :
let initMtn = 0
const {isLoggedIn, user, manager} = useAuth();
const { indices, indice, person, personNetwork, setNodeIdData, players, askedPersons, setActualPlayerIndexData, room, actualPlayerIndex, turnPlayerIndex, setTurnPlayerIndexData, setWinnerData, dailyEnigme, setNbCoupData, settempsData, setNetworkDataData, setSeedData} = useGame();
const params = new URLSearchParams(window.location.search);
const navigate = useNavigate();
const [lastIndex, setLastIndex] = useState(-1)
const [elapsedTime, setElapsedTime] = useState(0);
useEffect(() => {
// Démarrez le timer au montage du composant
const intervalId = setInterval(() => {
setElapsedTime((prevElapsedTime) => prevElapsedTime + 0.5);
settempsData(elapsedTime)
// Vérifiez si la durée est écoulée, puis arrêtez le timer
if (endgame) {
clearInterval(intervalId);
}
}, 500);
// Nettoyez l'intervalle lorsque le composant est démonté
return () => clearInterval(intervalId);
}, [elapsedTime, endgame]);
useEffect(() =>{
touchedPlayer=playerTouched
if (touchedPlayer == -1){
if (!askedWrong){
socket.emit("put correct background", socket.id)
}
}
else if (touchedPlayer < players.length && touchedPlayer>=0){
if(!askedWrong){
socket.emit("put correct background", socket.id)
socket.emit("put grey background", socket.id, touchedPlayer)
}
}
else if(touchedPlayer == players.length){
socket.emit("put imossible grey", socket.id)
}
}, [playerTouched])
useEffect(() => {
const tab: NodePerson[] = []
for(const n of lastNodes.reverse()){
if (!tab.find((node) => node.id == n.id)){
tab.push(n)
if (tab.length > players.length * 2) break
}
}
lastNodes = tab
if (showLast){
socket.emit("opacity activated", socket.id)
}
else{
socket.emit("opacity deactivated", socket.id)
}
}, [showLast])
let playerIndex: number = turnPlayerIndex
if (firstIndex){
firstIndex=false
setPlayerIndex(playerIndex)
}
let index = 0
for (let i=0; i<players.length; i++){
if(players[i].id == socket.id){
@ -52,14 +132,120 @@ const MyGraphComponent: React.FC<MyGraphComponentProps> = ({onNodeClick, handleS
break
}
}
let thisPlayerIndex = index
useEffect(() =>{
if (actualPlayerIndex==0){
const bot = players[lastIndex]
if(bot instanceof Bot && botIndex != lastIndex){
botIndex = lastIndex
if (personNetwork!=null){
const [choosedPlayerIndex, personIndex] = bot.playRound(personNetwork, players)
const person = personNetwork.getPersons().find((p) => p.getId() == personIndex)
if (choosedPlayerIndex == players.length && person != undefined){
console.log(lastIndex + " All in sur => " + personNetwork.getPersons().find((p) => p.getId() == personIndex)?.getName())
let nextPlayerIndex = lastIndex + 1
if (nextPlayerIndex == players.length){
nextPlayerIndex = 0
}
let playerIndex = lastIndex + 1
let i = 0
socket.emit("node checked", personIndex, true, lastIndex, room, lastIndex)
while(playerIndex != lastIndex){
i++
if (playerIndex == players.length){
playerIndex = 0
}
const tester = IndiceTesterFactory.Create(indices[playerIndex])
const works = tester.Works(person)
socket.emit("asked all 1by1", person.getId(), players[playerIndex].id)
if (i==players.length){
socket.emit("node checked", personIndex, works, playerIndex, room, nextPlayerIndex)
}
else{
socket.emit("node checked", personIndex, works, playerIndex, room, lastIndex)
}
if(!works){
socket.emit("node checked", personIndex, works, playerIndex, room, nextPlayerIndex)
const ind = bot.placeSquare(personNetwork, players)
console.log(lastIndex + " pose carré sur " + personNetwork.getPersons()[ind].getName())
playerIndex = lastIndex + 1
if(playerIndex == players.length){
playerIndex = 0
}
socket.emit("node checked", ind, false, lastIndex, room, playerIndex)
return
}
playerIndex ++
}
socket.emit("end game", lastIndex, room)
}
else{
if (person!=undefined){
if (players[choosedPlayerIndex] instanceof Bot){
console.log("BOT")
const tester = IndiceTesterFactory.Create(indices[choosedPlayerIndex])
const works = tester.Works(person)
if (works){
playerIndex = lastIndex + 1
if(playerIndex == players.length){
playerIndex = 0
}
console.log(lastIndex + " interroge " + choosedPlayerIndex + " a propos de " + person.getName() + " et dit oui")
socket.emit("node checked", personIndex, true, choosedPlayerIndex, room, playerIndex)
}
else{
console.log(lastIndex + " interroge " + choosedPlayerIndex + " a propos de " + person.getName() + " et dit non")
socket.emit("node checked", personIndex, false, choosedPlayerIndex, room, lastIndex)
const ind = bot.placeSquare(personNetwork, players)
console.log(lastIndex + " pose carré sur " + personNetwork.getPersons()[ind].getName())
playerIndex = lastIndex + 1
if(playerIndex == players.length){
playerIndex = 0
}
socket.emit("node checked", ind, false, lastIndex, room, playerIndex)
}
}
else{
console.log(choosedPlayerIndex + " => Pas bot" )
socket.emit("ask player", personIndex, players[choosedPlayerIndex].id, players[lastIndex])
console.log(lastIndex + " interroge " + +choosedPlayerIndex + " sur " + personNetwork.getPersons()[personIndex].getName())
const tester = IndiceTesterFactory.Create(indices[choosedPlayerIndex])
if (!tester.Works(person)){
const ind = bot.placeSquare(personNetwork, players)
console.log(lastIndex + " pose carré sur " + personNetwork.getPersons()[ind].getName())
playerIndex = lastIndex + 1
if(playerIndex == players.length){
playerIndex = 0
}
socket.emit("node checked", ind, false, lastIndex, room, playerIndex)
}
}
}
}
}
}
}
}, [lastIndex])
if (first){
first = false
endgame= false
if (!solo){
for(let i = 0; i<indices.length; i++){
mapIndexPersons.set(i, [])
}
if (actualPlayerIndex==0){
players.forEach((p, index) =>{
if (p instanceof Bot && personNetwork!=null){
p.index=index
p.initiateMap(personNetwork)
}
})
}
setActualPlayerIndexData(index)
if (playerIndex == thisPlayerIndex){
if (playerIndex == actualPlayerIndex){
handleTurnBarTextChange("À vous de jouer")
handleShowTurnBar(true)
}
@ -102,13 +288,16 @@ const MyGraphComponent: React.FC<MyGraphComponentProps> = ({onNodeClick, handleS
// 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'
}
},
distanceMin: 500, // Set the minimum distance between nodes
//randomSeed: 2
},
physics: {
enabled: true,
@ -116,13 +305,50 @@ const MyGraphComponent: React.FC<MyGraphComponentProps> = ({onNodeClick, handleS
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)
setSeedData(network.getSeed())
if (isDaily){
setNetworkEnigme(dailyEnigme)
if (!isEasy){
dailyEnigme.forEach((pairs, index) => {
pairs.forEach((pair) => {
const i = indices.findIndex((indice) => pair.first.getId() === indice.getId())
const node = networkData.nodes.get().find((n) => index == n.id)
if (node != undefined){
networkData.nodes.update({id: node.id, label: node.label + positionToEmoji(i, pair.second)})
}
})
});
}
else{
if (firstHistory){
firstHistory=false
indices.forEach((indice, index) => {
addToHistory("Indice " + positionToEmoji(index, true) + " : " + indice.ToString("fr"))
})
}
}
}
socket.on("reset graph", () => {
console.log("reset graph")
initialOptions.physics.enabled = true
network.setOptions(initialOptions)
})
if (!solo){
socket.on("asked all", (id) =>{
const pers = personNetwork.getPersons().find((p) => p.getId() == id)
@ -130,17 +356,52 @@ const MyGraphComponent: React.FC<MyGraphComponentProps> = ({onNodeClick, handleS
askedPersons.push(pers)
}
})
socket.on("opacity activated", () => {
nodes.forEach(node => {
if (!lastNodes.find((n) => n.id == node.id)){
networkData.nodes.update({id: node.id, opacity: 0.2})
}
});
})
socket.on("opacity deactivated", () => {
nodes.forEach(node => {
networkData.nodes.update({id: node.id, opacity: 1})
});
})
socket.on("node checked",(id, works, color, newPlayerIndex) => {
//@ts-ignore
socket.on("node checked",(id, works, askedIndex, newPlayerIndex, socketId) => {
const node = nodes.get().find((n) => id == n.id)
if (node!=undefined){
onNodeClick(false)
playerIndex = newPlayerIndex
if (!node.label.includes(colorToEmoji(color, works))){
networkData.nodes.update({id: id, label: node.label + colorToEmoji(color, works)})
setPlayerIndex(playerIndex)
if (mapIndexPersons.get(askedIndex)?.find((p) => p.getId() == id) == undefined){
const p = personNetwork.getPersons().find((p)=> p.getId() == id)
const tab = mapIndexPersons.get(askedIndex)
if (p!=undefined && tab != undefined){
tab.push(p)
if (actualPlayerIndex == 0){
players.forEach((player) => {
if (player instanceof Bot){
player.newInformation(p, askedIndex, works)
}
})
}
}
}
if (playerIndex === thisPlayerIndex){
if (!node.label.includes(colorToEmoji(positionToColor(askedIndex), works))){
networkData.nodes.update({id: id, label: node.label + positionToEmoji(askedIndex, works)})
cptHistory++
if (cptHistory % 2 == 0){
lastNodes.push(node)
addToHistory(players[askedIndex].pseudo + " à mis un " + positionToEmoji(askedIndex, works) + " à " + personNetwork.getPersons()[id].getName())
}
}
if (playerIndex === actualPlayerIndex){
handleTurnBarTextChange("À vous de jouer")
handleShowTurnBar(true)
}
@ -148,8 +409,10 @@ const MyGraphComponent: React.FC<MyGraphComponentProps> = ({onNodeClick, handleS
handleShowTurnBar(false)
}
}
lastSocketId = socketId
lastAskingPlayer = 0
lastNodeId = -1
setLastIndex(newPlayerIndex)
})
socket.on("already asked", (nodeId, askedPlayer) =>{
@ -157,15 +420,15 @@ const MyGraphComponent: React.FC<MyGraphComponentProps> = ({onNodeClick, handleS
})
socket.on("asked wrong", () =>{
setOnlyFalseData(true)
askedWrong = true
askedWrongBot=true
handleShowTurnBar(true)
handleTurnBarTextChange("Mauvais choix, posez un carré !")
socket.emit("put grey background", socket.id, actualPlayerIndex)
})
socket.on("asked", (nodeId, askingPlayer, askingPlayerIndex) => {
console.log(askingPlayerIndex)
socket.on("asked", (nodeId, askingPlayer) => {
if (askingPlayer.id !== lastAskingPlayer || nodeId !== lastNodeId ){
lastAskingPlayer = askingPlayer.id
lastNodeId = nodeId
@ -181,21 +444,30 @@ const MyGraphComponent: React.FC<MyGraphComponentProps> = ({onNodeClick, handleS
const node = nodes.get().find((n) => nodeId == n.id)
if (node != undefined && indice != null){
var tester = IndiceTesterFactory.Create(indice)
let maybe = thisPlayerIndex
playerIndex = playerIndex + 1
if(playerIndex == players.length){
playerIndex = 0
}
let maybe = actualPlayerIndex
if (tester.Works(pers)){
socket.emit("node checked", nodeId, true, positionToColor(thisPlayerIndex), room, playerIndex)
playerIndex = playerIndex + 1
if(playerIndex == players.length){
playerIndex = 0
}
socket.emit("node checked", nodeId, true, actualPlayerIndex, room, playerIndex)
}
else{
maybe = maybe - 1
maybe = actualPlayerIndex - 1
if(maybe == 0){
maybe = players.length - 1
}
socket.emit("node checked", nodeId, false, positionToColor(thisPlayerIndex), room, maybe)
socket.emit("asked wrong", askingPlayer, room)
let index = players.findIndex((p) => p.id == askingPlayer.id)
if (players[index] instanceof Bot){
index = playerIndex + 1
if(index == players.length){
index = 0
}
}
if (index != undefined){
socket.emit("node checked", nodeId, false, actualPlayerIndex, room, index)
socket.emit("asked wrong", askingPlayer, room)
}
}
}
}
@ -204,7 +476,127 @@ const MyGraphComponent: React.FC<MyGraphComponentProps> = ({onNodeClick, handleS
})
}
else {
if (firstLap){
firstLap=false
if (!isDaily){
addToHistory("<----- [Tour " + 1 +"/"+networkData.nodes.length + "] ----->");
}
}
}
socket.on("put correct background", () =>{
if (personNetwork != null){
for(const person of personNetwork.getPersons()){
networkData.nodes.update({id: person.getId(), color: ColorToHexa(person.getColor())})
}
}
})
socket.on("put grey background", (player) =>{
if (personNetwork != null){
const tab = mapIndexPersons.get(player)
if (tab != undefined){
if (player != actualPlayerIndex){
for(const person of personNetwork.getPersons().filter((p) => tab.includes(p))){
networkData.nodes.update({id: person.getId(), color: "#808080"})
}
}
else if(indice != null){
const tester = IndiceTesterFactory.Create(indice)
for(const person of personNetwork.getPersons().filter((p) => tab.includes(p) || tester.Works(p))){
networkData.nodes.update({id: person.getId(), color: "#808080"})
}
}
}
}
})
socket.on("put imossible grey", ()=>{
if (personNetwork != null && indice!=null){
const tabNodes: any = []
const tester = IndiceTesterFactory.Create(indice)
for (const pers of personNetwork.getPersons()){
const node = nodes.get().find((n) => pers.getId() == n.id)
if (node != undefined){
for(let i=0; i<players.length; i++){
if (node.label.includes(positionToEmoji(i, false)) || !tester.Works(pers)){
tabNodes.push(node)
break
}
}
}
}
for(const n of tabNodes){
networkData.nodes.update({id: n.id, color: "#808080"})
}
}
})
socket.on("end game", (winnerIndex) =>{
if (cptEndgame % 2 == 0){
cptEndgame++;
const currentPlayer = players[actualPlayerIndex];
const winner = players[winnerIndex];
setNetworkDataData(networkData)
setNodeIdData(-1)
setActualPlayerIndexData(-1)
setLastIndex(-1)
setPlayerTouched(-1)
setWinnerData(winner)
setElapsedTime(0)
first = true
cptHistory = 0
askedWrong=false
askedWrongBot=false
endgame = true
firstHistory=true
try{
if(isLoggedIn){
if(!solo){
if(user && user.onlineStats){
// console.log("nbGames: " + user.onlineStats.nbGames + " nbWins: " + user.onlineStats.nbWins);
if(winner.id === currentPlayer.id){
// Ajouter une victoire
user.onlineStats.nbWins = null ? user.onlineStats.nbWins = 1 : user.onlineStats.nbWins += 1;
}
// Update les stats
user.onlineStats.nbGames = null ? user.onlineStats.nbGames = 1 : user.onlineStats.nbGames += 1;
user.onlineStats.ratio = user.onlineStats.nbWins / user.onlineStats.nbGames;
manager.userService.updateOnlineStats(user.pseudo, user.onlineStats.nbGames, user.onlineStats.nbWins, user.onlineStats.ratio);
}
else{
console.error("User not found");
}
}
}
}
catch(e){
console.log(e);
}
finally{
socket.off("end game")
socket.off("asked all")
socket.off("opacity activated")
socket.off("opacity deactivated")
socket.off("reset graph")
socket.off("node checked")
socket.off("already asked")
socket.off("asked wrong")
socket.off("asked")
socket.off("put correct background")
socket.off("put grey background")
socket.off("put imossible grey")
navigate("/endgame")
}
}
})
@ -230,6 +622,7 @@ const MyGraphComponent: React.FC<MyGraphComponentProps> = ({onNodeClick, handleS
// Un nœud a été cliqué
initialOptions.physics.enabled = false;
network.setOptions(initialOptions);
setNetwork(network)
}
});
@ -237,6 +630,7 @@ const MyGraphComponent: React.FC<MyGraphComponentProps> = ({onNodeClick, handleS
if(params.nodes.length > 0){
setNodeIdData(params.nodes[0])
// addToHistory("Le joueur a cliqué") //! TEST DEBUG
if (!solo){
if (askedWrong){
//@ts-ignore
@ -244,41 +638,156 @@ const MyGraphComponent: React.FC<MyGraphComponentProps> = ({onNodeClick, handleS
if (person !== undefined && indice !== null){
const tester = IndiceTesterFactory.Create(indice)
if (!tester.Works(person) && !askedPersons.includes(person)){
playerIndex = thisPlayerIndex + 1
playerIndex = actualPlayerIndex + 1
if(playerIndex == players.length){
playerIndex = 0
}
socket.emit("node checked", params.nodes[0], false, positionToColor(thisPlayerIndex), room, playerIndex)
socket.emit("node checked", params.nodes[0], false, actualPlayerIndex, room, playerIndex)
socket.emit("put correct background", socket.id)
touchedPlayer=-1
askedPersons.push(person)
askedWrong = false
}
}
}
else if (thisPlayerIndex == playerIndex){
onNodeClick(true)
else if(touchedPlayer != -1 && playerIndex == actualPlayerIndex && touchedPlayer<players.length){
botIndex = -1
if (players[touchedPlayer] instanceof Bot){
const ind = indices[touchedPlayer]
const test = IndiceTesterFactory.Create(ind)
const person = personNetwork?.getPersons().find((p) => p.getId() == params.nodes[0])
if (person != undefined){
if (test.Works(person)){
let nextPlayerIndex = actualPlayerIndex + 1
if (nextPlayerIndex == players.length){
nextPlayerIndex = 0
}
socket.emit("node checked", params.nodes[0], true, touchedPlayer, room, nextPlayerIndex)
setPlayerTouched(-1)
}
else{
socket.emit("node checked", params.nodes[0], false, touchedPlayer, room, actualPlayerIndex)
socket.emit("asked wrong", players[actualPlayerIndex])
setPlayerTouched(-1)
}
}
}
else{
if (touchedPlayer > 0){
console.log(touchedPlayer)
socket.emit("ask player", params.nodes[0], players[touchedPlayer].id, players.find((p) => p.id === socket.id, actualPlayerIndex))
socket.emit("put correct background", socket.id)
touchedPlayer=-1
setPlayerTouched(-1)
}
}
}
else{
onNodeClick(false)
else if(playerIndex == actualPlayerIndex && touchedPlayer==players.length){
botIndex = -1
const person = personNetwork?.getPersons().find((p) => p.getId() == params.nodes[0])
if (person != undefined){
const indiceTester = IndiceTesterFactory.Create(indices[actualPlayerIndex])
let nextPlayerIndex = actualPlayerIndex + 1
if (nextPlayerIndex == players.length){
nextPlayerIndex = 0
}
let playerIndex = actualPlayerIndex + 1
if (indiceTester.Works(person)){
let i = 0
socket.emit("node checked", params.nodes[0], true, actualPlayerIndex, room, actualPlayerIndex)
while(playerIndex != actualPlayerIndex){
if (playerIndex == players.length){
playerIndex = 0
}
const tester = IndiceTesterFactory.Create(indices[playerIndex])
const works = tester.Works(person)
await delay(500);
socket.emit("asked all 1by1", person.getId(), players[playerIndex].id)
if (works){
socket.emit("node checked", params.nodes[0], true, playerIndex, room, actualPlayerIndex)
}
if(!works){
socket.emit("node checked", params.nodes[0], works, playerIndex, room, actualPlayerIndex)
socket.emit("put correct background", socket.id)
socket.emit("asked wrong", players[actualPlayerIndex])
touchedPlayer=-1
setPlayerTouched(-1)
return
}
if (i == players.length - 1){
socket.emit("put correct background", socket.id)
await delay(1000)
socket.emit("end game", actualPlayerIndex, room)
return
}
playerIndex ++
i++
}
touchedPlayer=-1
setPlayerTouched(-1)
socket.emit("put correct background", socket.id)
await delay(1000)
socket.emit("end game", actualPlayerIndex, room)
}
}
}
}
else{ // si solo -> Mastermind
//@ts-ignore
}
else{
const person = personNetwork?.getPersons().find((p) => p.getId() == params.nodes[0]) //person sélectionnée
if (person != undefined){
let index = 0;
// indices.forEach(async (i, index) =>{
for(const i of indices){
let index =0
let works = true
for (const i of indices){
const tester = IndiceTesterFactory.Create(i)
const test = tester.Works(person)
//@ts-ignore
const node = nodes.get().find((n) => params.nodes[0] == n.id)
if (node!=undefined){
networkData.nodes.update({id: params.nodes[0], label: node.label + colorToEmoji(positionToColor(index), test)})
await delay(500);
if (!node.label.includes(positionToEmoji(index, test))){
networkData.nodes.update({id: params.nodes[0], label: node.label + positionToEmoji(index, test)})
await delay(500)
if(!test){
works = false
}
if (index == indices.length - 1 && works){
if (user!=null){
setWinnerData(user)
setNetworkDataData(networkData)
}
cptTour ++;
setNbCoupData(cptTour)
setElapsedTime(0)
endgame = true
try{
if(user && user.soloStats){
user.soloStats.nbGames = null ? user.soloStats.nbGames = 1 : user.soloStats.nbGames += 1;
if(cptTour < user.soloStats.bestScore || user.soloStats.bestScore == 0 || user.soloStats.bestScore == null){
user.soloStats.bestScore = cptTour;
}
user.soloStats.avgNbTry = (user.soloStats.avgNbTry * (user.soloStats.nbGames - 1) + cptTour) / user.soloStats.nbGames;
manager.userService.updateSoloStats(user.pseudo, user.soloStats.nbGames, user.soloStats.bestScore, user.soloStats.avgNbTry);
}
}
catch(error){
console.log(error);
}
navigate("/endgame?solo=true&daily=" + isDaily)
}
}
}
index ++;
index++
}
addToHistory(person.getName() + " n'est pas le tueur !"); //TODO préciser le nombre d'indice qu'il a de juste
cptTour ++; // On Incrémente le nombre de tour du joueur
const tour = cptTour+1;
addToHistory("<----- [Tour " + tour +"/"+networkData.nodes.length + "] ----->");
changecptTour(cptTour); // On le transmet a la page précédente avec la fonction
}
}
@ -287,6 +796,7 @@ const MyGraphComponent: React.FC<MyGraphComponentProps> = ({onNodeClick, handleS
else{
// Renvoyer un false pour cacher la choice bar
onNodeClick(false)
setPlayerTouched(-1)
}
});
}, []); // Le tableau vide signifie que cela ne s'exécutera qu'une fois après le premier rendu
@ -300,9 +810,15 @@ const MyGraphComponent: React.FC<MyGraphComponentProps> = ({onNodeClick, handleS
</>
);
function delay(ms: number) {
function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
function putGreyBackgroud(index: number){
/*
*/
}
}

@ -0,0 +1,26 @@
import React from 'react';
import { NavDropdown } from 'react-bootstrap';
import LanguageNavItem from './LangNavItem';
import { HiLanguage } from 'react-icons/hi2';
// @ts-ignore
const LangDropdown = ({ changeLocale }) => {
return (
<NavDropdown title={<HiLanguage/>} id="language-dropdown" align='end' drop='down-centered'>
<LanguageNavItem
countryCode="FR"
languageKey="languageSelector.french"
onClick={() => changeLocale('fr')}
/>
<LanguageNavItem
countryCode="GB"
languageKey="languageSelector.english"
onClick={() => changeLocale('en')}
/>
{/* Ajoutez d'autres langues selon vos besoins */}
</NavDropdown>
);
};
export default LangDropdown;

@ -0,0 +1,21 @@
import React from 'react';
import { NavDropdown } from 'react-bootstrap';
import { FormattedMessage } from 'react-intl';
import ReactCountryFlag from 'react-country-flag';
/*@ts-ignore*/
const LanguageNavItem = ({ countryCode, languageKey, onClick }) => {
return (
<NavDropdown.Item onClick={onClick}>
<ReactCountryFlag
className="custom-flag"
countryCode={countryCode}
svg
style={{ margin: 'auto 10px 3px auto' }}
/>
<FormattedMessage id={languageKey} />
</NavDropdown.Item>
);
};
export default LanguageNavItem;

@ -0,0 +1,90 @@
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';
interface LobbyContainerProps {
roomNum : string
HeadPlayer : Player
nbPlayer : number
//? mettre un "nbplayermax" si le nombre de joueur max peut etre fixé ?
}
const LobbyContainer: React.FC<LobbyContainerProps> = ({roomNum, HeadPlayer, nbPlayer}) => {
const theme=useTheme();
const navigate = useNavigate();
const dest = '/lobby?room=' + roomNum;
//* Modal
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const handleContainerClick = () => {
if (show){
handleClose()
}
else{
if (nbPlayer < 6) {
navigate(dest);
} else {
handleShow()
//alert('La salle est pleine. Impossible de rejoindre.');
}
}
};
let stylefull;
let colorfull;
if (nbPlayer >= 6) {
stylefull = "darkred"
colorfull = "darkred"
}
else {
stylefull = "whitesmoke"
colorfull = "black"
}
return(
<div className='lobbyMainContainer' onClick={handleContainerClick} style={{borderColor:stylefull}}>
<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>
{/*
<Button onClick={handleContainerClick} variant='danger'>
click
</Button> */}
<Modal show={show} 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>
</div>
);
}
export default LobbyContainer;

@ -1,5 +1,4 @@
.custom-navbar{
.custom-navbar {
/* background-color: #85C9C2; */
color: #fff;
text-emphasis-color: #fff;
@ -15,23 +14,20 @@
border-radius: 15px;
}
.navbar-title-dd a {
color: #fff;
border-radius: 15;
border-radius: 15px;
padding: 10px 20px; /* Ajustez le rembourrage selon vos préférences */
text-decoration: none; /* Pour supprimer le soulignement des liens */
display: inline-block; /* Pour que les styles de fond s'appliquent aux liens */
margin-right: 10px; /* Pour ajouter de l'espace entre les boutons */
}
.centerdiv{
.centerdiv {
display: flex;
justify-content: center;
justify-content: center;
}
.leftdiv{
.leftdiv {
display: flex;
justify-content: start;
}

@ -1,4 +1,4 @@
import React from 'react';
import React, {useEffect, useState} from 'react';
/* Naviagtion */
import { Navbar, Container, Nav, NavDropdown } from 'react-bootstrap';
@ -7,82 +7,99 @@ import { Navbar, Container, Nav, NavDropdown } from 'react-bootstrap';
import { FormattedMessage } from 'react-intl';
/* Icon */
import { BiLogInCircle } from 'react-icons/bi';
import { BiDoorOpen, BiLogInCircle } from 'react-icons/bi';
import { BsFillPersonPlusFill } from 'react-icons/bs';
import { HiLanguage } from 'react-icons/hi2';
/* Images */
import logo from '../res/img/logo2_preview_rev_1.png';
/* Components */
import ReactCountryFlag from "react-country-flag"
import LanguageNavItem from './LangNavItem';
import LangDropDown from './LangDropDown';
/* Style */
import './NavBar.css';
/* Style */
import { useTheme } from '../Style/ThemeContext';
import { useAuth } from '../Contexts/AuthContext';
import { useNavigate } from 'react-router-dom';
// @ts-ignore
function AppNavbar({changeLocale}) {
const theme = useTheme();
const {user, isLoggedIn, logout} = useAuth();
const navigate = useNavigate();
useEffect(() => {
console.log(user)
}, [user])
function navigateToProfile(){
navigate("/profile")
}
function navigateToHome(){
navigate("/")
}
return (
<Navbar expand="lg" className="custom-navbar" style={{ backgroundColor: theme.colors.primary }}>
<Container>
<Navbar.Brand href="/">
<img src={logo} alt="logo" className="logo"/>
</Navbar.Brand>
<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>
<div className='leftdiv'>
<Nav className="ml-auto navbar-title-dd">
<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>
</Nav>
<Nav className="me-auto">
<NavDropdown
title={<span><HiLanguage /></span>}
className="navbar-title" id="basic-nav-dropdown">
<NavDropdown.Item onClick={() => changeLocale('fr')}>
<ReactCountryFlag countryCode="FR"
svg
style={{
width: '30px',
height: '20px',
margin: 'auto 10px 3px auto',
}}/>
<FormattedMessage id="languageSelector.french"/>
</NavDropdown.Item>
<NavDropdown.Item onClick={() => changeLocale('en')}>
<ReactCountryFlag countryCode="GB"
svg
style={{
width: '30px',
height: '20px',
margin: 'auto 10px 3px auto',
}}/>
<FormattedMessage id="languageSelector.english"/>
</NavDropdown.Item>
</NavDropdown>
</Nav>
</div>
</Navbar.Collapse>
</Container>
<Container>
<Navbar.Brand onClick={navigateToHome}>
<img src={logo} alt="logo" className="logo" />
</Navbar.Brand>
<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>
<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}/>
</>
)}
</Nav>
</div>
</Navbar.Collapse>
</Container>
</Navbar>
);
}

@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
/* Style */
import '../Style/Global.css'
@ -6,17 +6,72 @@ import { useTheme } from '../Style/ThemeContext';
/* Ressources */
import Person from '../res/img/Person.png'
import leave from '../res/img/bot.png'
import BotImg from '../res/img/bot.png'
import { useGame } from '../Contexts/GameContext';
import Bot from '../model/Bot';
interface PlayerStatusProps {
img: any
state: any
name: string
index: number
setPlayerTouched: (newPlayerTouch: number) => void;
playerTouched: number
showCircle: boolean
playerIndex: number
}
let touchedPlayer = -1
//@ts-ignore
function PersonStatus({img = Person, state= Person, name = "Dummy"}) {
const PersonStatus: React.FC<PlayerStatusProps> = ({img = Person, state= Person, name = "Dummy", index, playerTouched, setPlayerTouched, showCircle, playerIndex}) => {
const theme=useTheme();
const {players, actualPlayerIndex} = useGame()
if (players[index] instanceof Bot){
img = BotImg
}
const [buffer, setBuffer] = useState("")
const [touchedPlayer, setTouchedPlayer] = useState(-2)
useEffect(() =>{
setTouchedPlayer(playerTouched)
}, [playerTouched])
let IsActualPlayer = index != actualPlayerIndex
useEffect(() => {
if (playerIndex===index){
setBuffer('solid 1px green')
}
else{
setBuffer('')
}
}, [playerIndex])
function onTouch(){
if (IsActualPlayer){
setPlayerTouched(index)
}
}
return (
<div className='centerDivV'>
<img src={img} alt="player" height="100" width="100"/>
<h4>{name}</h4>
<div className='statusDiv' style={{ backgroundColor: theme.colors.primary }}>
<img src={state} alt="state" height="30" width="30"/>
<div style={{border:buffer}}>
<div className='centerDivV' onClick={() => onTouch()}>
<img src={img} alt="player" height="60" width="60"/>
<h5>{name}</h5>
{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>
)
)}
</div>
</div>
);

@ -1,31 +1,69 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
/* img */
import BotPDP from '../res/img/bot.png';
import PersonPDP from '../res/img/Person.png';
import Trash from '../res/icon/trash.png';
/* style */
import '../Style/Global.css';
import Bot from '../res/img/bot.png';
/* Boostrap */
import ToggleButton from 'react-bootstrap/ToggleButton';
import ToggleButtonGroup from 'react-bootstrap/ToggleButtonGroup';
import Button from 'react-bootstrap/Button';
/* model */
import Player from '../model/Player';
import Bot from '../model/Bot';
/* server */
import { socket } from '../SocketConfig';
interface MyPlayerItemListProps {
player : Player,
room : string | null
}
//@ts-ignore
function PlayerItemList({ pdp, name, id}) {
const isBot = pdp === Bot;
const PlayerItemList:React.FC<MyPlayerItemListProps> =({ player, room }) => {
// const isBot = pdp === Bot;
let pdp;
const isBot = player instanceof Bot;
isBot ? pdp = BotPDP : pdp = player.profilePicture;
const delBot = () => {
if (isBot && room != null) {
console.log(room);
socket.emit("bot deleted", player, room);
}
};
return (
<div className='item-horizontal-div-container'>
<div className='item-horizontal-div'>
<img src={pdp} alt='player-image' height='100' width='100' />
<h4>{name}</h4>
<div>
<img src={pdp} alt='player-image' height='100' width='100' />
<h4>{player.pseudo}</h4>
</div>
{isBot && (
<Button className='suprButton' onClick={delBot} variant="danger">
<img src={Trash} alt='Trash-icon' height='30' width='30' />
</Button>
)}
</div>
{isBot && (
<ToggleButtonGroup type='radio' name={`options-${id}`} defaultValue={1}>
<ToggleButton id={`tbg-radio-1-${id}`} value={1}>
<ToggleButtonGroup type='radio' name={`options-${player.id}`} defaultValue={1}>
<ToggleButton id={`tbg-radio-1-${player.id}`} value={1}>
Facile
</ToggleButton>
<ToggleButton id={`tbg-radio-2-${id}`} value={2}>
<ToggleButton id={`tbg-radio-2-${player.id}`} value={2}>
Intermédiaire
</ToggleButton>
<ToggleButton id={`tbg-radio-3-${id}`} value={3}>
<ToggleButton id={`tbg-radio-3-${player.id}`} value={3}>
Fort
</ToggleButton>
</ToggleButtonGroup>

@ -1,19 +1,78 @@
import React from 'react';
import { colorToEmoji, positionToColor } from '../ColorHelper';
import Player from '../model/Player';
import { useTheme } from '../Style/ThemeContext';
import PersonStatus from './PersonStatus';
import Person from '../res/img/Person.png'
import { socket } from '../SocketConfig';
//@ts-ignore
function PlayerList({ players }) {
interface PlayerListProps {
players: Player[];
playerTouched: number
setPlayerTouched: (newPlayerTouch: number) => void;
playerIndex: number
}
const PlayerList: React.FC<PlayerListProps> = ({ players, playerTouched, setPlayerTouched, playerIndex}) => {
const theme = useTheme();
return (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '16px' }}>
{
//@ts-ignore
players.map((player, index) => (
<PersonStatus key={index} state={player.state} name={player.name + " " + colorToEmoji(positionToColor(index), true)} />
))
<div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '16px' }}>
{
//@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}
playerIndex={playerIndex}/>
))
}
</div>
<div
style={{display: 'flex',
justifyContent: "center",
alignSelf: "center",
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>
)
}
</div>
</div>
);
}

@ -0,0 +1,52 @@
import React, { useState } from 'react';
import '../Pages/Profile.css'
import dl from '../res/icon/download.png'
import defaultImg from '../res/img/Person.png'
import { useAuth } from '../Contexts/AuthContext';
//@ts-ignore
const ProfilePDP = () => {
const [selectedFile, setSelectedFile] = useState(null);
const {user} = useAuth()
// @ts-ignores
const handleFileChange = (event) => {
let file = event.target.files[0];
setSelectedFile(file);
if (file) {
const pdpUrl = URL.createObjectURL(file);
if (user!=null){
user.profilePicture = pdpUrl
}
}
};
return (
<div className='mainPDPContainer'>
{selectedFile ? (
<div >
{/* @ts-ignore */}
{/* <p>Selected File: {selectedFile.name}</p> */}
<img src={URL.createObjectURL(selectedFile)} alt="Preview" className='imgContainer' width='100px' height='100px' />
</div>
) : (
<div >
<img src={user?.profilePicture} alt="Preview" className='imgContainer' width='100px' height='100px' />
</div>
)}
<div className="parent">
<div className="file-upload">
<img src={dl} alt="upload" width='35px' height='35px'/>
{/* <h6>Cliquer ici pour ajouter une image</h6> */}
{/* <p>Taille recommandée : 100px</p> */}
<input type="file" accept="image/*" onChange={handleFileChange}/>
</div>
</div>
{/* <input type="file" accept="image/*" onChange={handleFileChange} /> */}
</div>
);
};
export default ProfilePDP;

@ -0,0 +1,113 @@
import React from 'react';
/* Style */
import '../Pages/Play.css';
import '../Style/Global.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';
/* Boostrap */
import Tab from 'react-bootstrap/Tab';
import Tabs from 'react-bootstrap/Tabs';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
/* Component */
import ButtonImgNav from './ButtonImgNav';
import User from '../model/User';
/* Types */
//@ts-ignore
const ScoreBoard: React.FC<{ Player: User }> = ({ Player }) => {
const theme=useTheme();
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>
);
}
export default ScoreBoard;

@ -0,0 +1,62 @@
// AuthContext.js
import React, { createContext, useContext, useState, ReactNode } from 'react';
import DbUserService from '../model/DataManagers/DbUserService';
import Manager from '../model/DataManagers/Manager';
import Player from '../model/Player';
import User from '../model/User';
import AuthService from '../services/AuthService';
interface AuthContextProps {
isLoggedIn: boolean;
login: () => void;
logout: () => void;
user: User | null
setUserData: (newPlayer: User) => void
manager: Manager
}
const AuthContext = createContext<AuthContextProps | undefined>(undefined);
const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
const [user, setUser] = useState<User| null>(null)
const [manager] = useState<Manager>(new Manager(new DbUserService()))
const login = async () => {
setIsLoggedIn(true);
const [u, bool] = await manager.userService.fetchUserInformation()
setUser(u)
};
const setUserData = (newPlayer: User) => {
setUser(newPlayer)
}
const logout = async() => {
try {
await AuthService.logout();
setIsLoggedIn(false);
const [u, bool] = await manager.userService.fetchUserInformation()
setUser(u)
}
catch (error) {
console.log(error);
};
};
return (
<AuthContext.Provider value={{ isLoggedIn, login, logout, user, setUserData, manager }}>
{children}
</AuthContext.Provider>
);
};
const useAuth = (): AuthContextProps => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
export { AuthProvider, useAuth };

@ -1,5 +1,6 @@
import React, { createContext, useContext, useState, ReactNode } from 'react';
import Indice from '../model/Indices/Indice';
import Pair from '../model/Pair';
import Person from '../model/Person';
import PersonNetwork from '../model/PersonsNetwork';
import Player from '../model/Player';
@ -16,6 +17,12 @@ interface GameContextProps {
turnPlayerIndex: number;
room: string;
onlyFalse: boolean
winner: Player | null
dailyEnigme: Map<number, Pair<Indice, boolean>[]>
nbCoup : number
temps : number
networkData: any
seed: number | string;
setIndicesData: (newIndices: Indice[]) => void;
setIndiceData: (newIndice: Indice) => void;
setPersonData: (newPerson: Person) => void;
@ -27,6 +34,13 @@ interface GameContextProps {
setTurnPlayerIndexData: (newTurnPlayerIndex: number) => void;
setRoomData: (newRoom: string) => void;
setOnlyFalseData: (newOnlyFalse: boolean) => void
setWinnerData: (winner: Player) => 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
}
const GameContext = createContext<GameContextProps | undefined>(undefined);
@ -47,7 +61,20 @@ export const GameProvider: React.FC<GameProviderProps> = ({ children }) => {
const [turnPlayerIndex, setTurnPlayerIndex] = useState<number>(-1)
const [room, setRoom] = useState<string>("")
const [onlyFalse, setOnlyFalse] = useState<boolean>(false)
const [winner, setWinner] = useState<Player | null>(null)
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 setNetworkDataData = (networkData: any) => {
setNetworkData(networkData);
}
const setSeedData = (seed: number | string) => {
setSeed(seed);
}
const setIndicesData = (newIndices: Indice[]) => {
setIndices(newIndices);
@ -94,8 +121,41 @@ export const GameProvider: React.FC<GameProviderProps> = ({ children }) => {
setOnlyFalse(newOnlyFalse)
}
const setWinnerData = (winner: Player) =>{
setWinner(winner)
}
const setDailyEnigmeData = (map: Map<number, Pair<Indice, boolean>[]>) => {
setDailyEnigme(map)
}
const setNbCoupData = (newNbCoup : number) => {
setNbCoup(newNbCoup);
}
const settempsData = (newtemps : number) => {
settemps(newtemps);
}
const reset = () => {
setIndices([])
setActualPlayerIndex(-1)
setAskedPersons([])
setPlayers([])
setPerson(null)
setPersonNetwork(null)
setRoom("")
setWinner(null)
setTurnPlayerIndex(-1)
setNodeId(-1)
setIndice(null)
setNbCoup(0)
settemps(0)
}
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 }}>
<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}}>
{children}
</GameContext.Provider>
);

@ -0,0 +1,14 @@
async function loadImageAsync(url: string): Promise<string> {
try {
const response = await fetch(url);
const blob = await response.blob();
// Faire quelque chose avec le blob, par exemple, créer une URL blob
const blobUrl = URL.createObjectURL(blob);
return blobUrl
} catch (error) {
throw new Error("Erreur lors du chargement de l'image :");
}
}
export {loadImageAsync}

@ -1,3 +1,5 @@
import EasyBot from "./model/EasyBot";
import Human from "./model/User";
import AgeIndice from "./model/Indices/AgeIndice";
import ColorEdgesIndice from "./model/Indices/ColorEdgesIndice";
import ColorIndice from "./model/Indices/ColorIndice";
@ -7,6 +9,8 @@ import NbSportIndice from "./model/Indices/NbSportIndice";
import SportIndice from "./model/Indices/SportIndice";
import Person from "./model/Person";
import PersonNetwork from "./model/PersonsNetwork";
import Player from "./model/Player";
import User from "./model/User";
class JSONParser{
@ -78,6 +82,17 @@ class JSONParser{
});
return tabIndice
}
static JSONToPlayer(json: any): Player{
switch (json.type){
case "User":
return new User(json.id, json.pseudo, json.profilePicture, json.soloStats, json.onlineStats)
case "EasyBot":
return new EasyBot(json.id, json.pseudo, json.profilePicture)
default:
throw new Error("PARSER unable to parse player: " + json.type);
}
}
}
export default JSONParser

@ -21,7 +21,7 @@
padding: 20px;
}
.bottom{
.bottomEnd{
display: flex;
justify-content: space-around;
}
@ -31,4 +31,94 @@
justify-content: center;
flex-direction: column;
align-items: center;
}
}
.playersContainer {
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;
border-radius: 15px;
background-color: white;
}
.losingPlayersContainer{
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;
align-items: center;
border: solid 1px whitesmoke;
border-radius: 15px;
background-color: white;
max-width: 50%;
}
.indicesolo{
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
/* 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;
}
#vis-graph {
height: 500px;
margin: 50px;
border: 2px solid #ccc;
overflow: hidden;
}

@ -1,4 +1,4 @@
import React from 'react';
import React, {useEffect} from 'react';
/* Style */
@ -15,41 +15,196 @@ import Replay from '../res/icon/replay.png';
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';
/* Model */
import Player from '../model/Player';
/* Context */
import { useGame } from '../Contexts/GameContext';
/* Boostrap */
import { Button } from 'react-bootstrap';
import Bot from '../model/Bot';
function EndGame() {
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(() => {
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
const solotmp = params.get('solo');
if (solotmp == "true"){
IsSolo=true
}
//* Gestion daily
let IsDaily: boolean = false
const dailytmp = params.get('daily');
if (dailytmp == "true"){
IsDaily=true
}
const {reset} = useGame()
const resetAll = () => {
reset()
}
const {winner, person, players, indices, nbCoup, temps} =useGame()
let indice = indices[0]
let losingPlayers : Player[];
if(!IsSolo){
const index = players.findIndex((p) => p.id == winner?.id)
if (index != -1) {
indice = indices[index]
}
if (winner != null) {
losingPlayers = players.filter(player => player.id !== winner.id);
} else {
losingPlayers = players;
}
}
else{
losingPlayers = [];
}
const theme = useTheme();
console.log(winner)
console.log(indices)
let indicenull = false;
if (indices.length == 0){
indicenull = true;
}
return (
<div>
<div className="head">
<header className='leaderboard-header' style={{ borderColor: theme.colors.primary }}>
<h1>Dummy a gagné !</h1>
<h3>Le tueur était <u>Bob</u></h3>
</header>
</div>
<div className='winner'>
<img src={Person} width='300' height='300'/>
</div>
<div className='bottom'>
<div className='centerDivH'>
<BigButtonNav dest="/play" img={Leave}/>
{!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'/>
{!indicenull && (<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} playerIndex={-2} showCircle={false}/>
{!indicenull && (<h6 className='indiceDisplay'>{indices[players.findIndex((p) => p.id == player?.id)].ToString("fr")}</h6>)}
</div>
)}
</div>
))}
</div>
</div>
</div>
<div className='centerDivH'>
<PersonStatus state={Replay} name="Dummy"/>
<PersonStatus state={Leave} name="bot2"/>
<PersonStatus state={Leave} name="bot3"/>
): (
<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='centerDivH'>
<BigButtonNav dest="/lobby" img={Replay}/>
<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>
)}
<div id="vis-graph"/>
<div className='centerDivH' onClick={resetAll} style={{margin: "20px"}}>
<Button href='/'>Retour à l'accueil</Button>
</div>
</div>
);
}

@ -1,14 +1,41 @@
import React from 'react';
import React, {useEffect} from 'react';
import { useAuth } from '../Contexts/AuthContext';
import SessionService from '../services/SessionService';
import './Home.css';
import '../App.css';
import { useTheme } from '../Style/ThemeContext';
import { Link } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import ButtonImgNav from '../Components/ButtonImgNav';
import defaultImg from "../res/img/Person.png"
import {loadImageAsync} from "../ImageHelper"
// @ts-ignore
function Home() {
const theme=useTheme();
const {isLoggedIn, login, user, setUserData, manager } = useAuth();
useEffect(() => {
if (user == null){
manager.userService.fetchUserInformation().then(([user, loggedIn]) =>{
if (user!=null){
if (loggedIn){
login()
setUserData(user)
}
else{
loadImageAsync(defaultImg).then((blob) => {
user.profilePicture=blob
setUserData(user)
})
}
}
})
}
}, [isLoggedIn]);
return (
<div className="home-container">
@ -16,36 +43,59 @@ function Home() {
<div className="left-section">
<div>
<h2><FormattedMessage id="home.histoire.title"/></h2>
{/* <h2><FormattedMessage id="home.histoire.title"/></h2> */}
<h2>Introduction</h2>
<p>
<FormattedMessage id="home.histoire" />
{/* <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" />
{/* <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.
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.
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.
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/>
</div>
<div>
<h2><FormattedMessage id="home.plus.title"/></h2>
{/* <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>
</ul> */}
<h2>Présentation Video :</h2>
(ici il y aura la vidéo)
</div>
</div>
<div className="vertical-divider"></div>
<div className="right-section">
<h3><FormattedMessage id="game.time"/></h3>
{/* <h3><FormattedMessage id="game.time"/></h3>
<h3><FormattedMessage id="game.players"/></h3>
<h3><FormattedMessage id="game.age"/></h3>
<p>-------------------------------</p>
<h3> <u><FormattedMessage id="game.createdBy"/></u><br/> Hal Duncan & Ruth Veevers</h3>
<h3> <u><FormattedMessage id="game.illustratedBy"/></u><br/> Kwanchai Moriya</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'

@ -32,6 +32,22 @@
right: 10px;
}
.opacityDiv{
z-index: 1;
position: absolute;
top: 100px;
right: 10px;
}
.resetDiv{
right: 10px;
display: flex;
justify-content: center;
align-items: center;
}
#graphDiv{
display: flex;
@ -117,4 +133,25 @@
/* #playerCanvasBody{
columns: 2 auto;
grid-row: 2;
} */
} */
/** 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;
height: 250px;
width: 20%;
overflow-y:auto;
}

@ -1,5 +1,6 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import Switch from "react-switch";
import {saveAs} from "file-saver"
/* Style */
import "./InGame.css"
@ -20,6 +21,11 @@ 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 Reset from "../res/icon/reset.png";
import Oeye from "../res/icon/eye.png";
import Ceye from "../res/icon/hidden.png";
import JSZip from 'jszip';
/* nav */
import { Link } from 'react-router-dom';
@ -31,12 +37,18 @@ 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 { Nav, NavDropdown, Spinner } from 'react-bootstrap';
import { FormattedMessage } from 'react-intl';
import Color from '../model/Color';
import { useGame } from '../Contexts/GameContext';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { NavLink } from 'react-router-dom';
import { last } from 'lodash';
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';
//@ts-ignore
const InGame = ({locale, changeLocale}) => {
@ -53,15 +65,69 @@ const InGame = ({locale, changeLocale}) => {
IsSolo=false
}
//* Gestion daily
let isDaily: boolean = true
const isDailytmp = params.get('daily');
if (isDailytmp == "false"){
isDaily=false
}
let isEasy: boolean = true
const isEasytmp = params.get('easy');
if (isEasytmp == "false"){
isEasy=false
}
//* Historique
const [history, setHistory] = useState<string[]>([]);
const [showLast, setShowLast] = useState(false)
// Fonction pour ajouter un élément à l'historique
const addToHistory = (message: string) => {
setHistory(prevHistory => [...prevHistory, message]);
};
const setShowLastData = () =>{
setLastVisible(!showLast);
setShowLast(!showLast);
}
useEffect(() => {
const historyContainer = document.getElementById('history-container');
if (historyContainer) {
historyContainer.scrollTop = historyContainer.scrollHeight;
}
}, [history]);
const {personNetwork, person, indices} = useGame()
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);
@ -71,6 +137,58 @@ const InGame = ({locale, changeLocale}) => {
setTurnBarText(newTurnBarText)
}
const setPlayerIndexData = (playerIndex: number) => {
setPlayerIndex(playerIndex)
}
const generateTEX = async () => {
if (network != null && personNetwork != null && person != null){
const zip = new JSZip();
if (isDaily && 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 imagesFolder = 'Script';
for (const imageName of imageNames) {
const imageUrl = process.env.PUBLIC_URL + `/${imagesFolder}/${imageName}`;
const response = await fetch(imageUrl);
if (response.ok) {
const imageBlob = await response.blob();
zip.file(`${imageName}`, imageBlob);
} 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
}
}
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 ?
@ -88,6 +206,11 @@ const InGame = ({locale, changeLocale}) => {
const [cptTour, setcptTour] = useState(0);
const [LastVisible, setLastVisible] = useState(false);
const [isLoading, setisLoading] = useState(false);
//@ts-ignore
const changecptTour = (newcptTour) => {
setcptTour(newcptTour);
@ -120,6 +243,11 @@ const InGame = ({locale, changeLocale}) => {
}
};
const changeVisibility = () => {
setLastVisible(!LastVisible);
}
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
@ -127,7 +255,7 @@ const InGame = ({locale, changeLocale}) => {
};
const [SwitchEnabled, setSwitchEnabled] = useState(false)
const indices = Stub.GenerateIndice()
const allIndices = Stub.GenerateIndice()
const { indice, players } = useGame();
@ -135,31 +263,39 @@ const InGame = ({locale, changeLocale}) => {
<div id="mainDiv">
{showTurnBar && <TurnBar text={turnBarText}/>}
<div id='graphDiv'>
<GraphContainer onNodeClick={handleNodeClick} handleShowTurnBar={handleShowTurnBar} handleTurnBarTextChange={handleTurnBarTextChange} changecptTour={changecptTour} solo={IsSolo} />
<GraphContainer onNodeClick={handleNodeClick}
handleShowTurnBar={handleShowTurnBar}
handleTurnBarTextChange={handleTurnBarTextChange}
changecptTour={changecptTour}
addToHistory={addToHistory}
solo={IsSolo}
isDaily={isDaily}
isEasy={isEasy}
setPlayerTouched={handleSetPlayerTouched}
playerTouched={playerTouched}
setNetwork={setNetworkData}
setNetworkEnigme={setNetworkEnigmeData}
showLast={showLast}
setPlayerIndex={setPlayerIndexData}/>
</div>
{IsSolo ? (
{IsSolo && !isDaily &&
<div className='nbLaps' style={{
backgroundColor: theme.colors.tertiary,
backgroundColor: theme.colors.primary,
borderColor: theme.colors.secondary
}}>
Tour : {cptTour}
</div>
) : (
<div className='playerlistDiv'>
<button className='button'
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary
}}
onClick={handleChangeP}>
Players
</button>
</div>
)
}
{(!isDaily || (isDaily && isEasy)) &&
<div className='historique' id="history-container">
{history.map((item, index) => (
<div key={index}>{item}</div>
))}
</div>
}
<div className='paramDiv'>
<button className='button'
@ -172,7 +308,33 @@ const InGame = ({locale, changeLocale}) => {
</button>
</div>
<div className='menuGame'>
<div className='resetDiv'>
<button className='button'
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary
}}
onClick={resetGraph}>
{
isLoading ? (
<Spinner animation="grow" />
)
: (
<img src={Reset} alt="paramètres" height='40'/>
)
}
</button>
</div>
{/* <Link to='/info#indice-possible' target='_blank'>
//? redirection impossible apparament (securité des navigateur
*/}
@ -189,6 +351,7 @@ const InGame = ({locale, changeLocale}) => {
<img src={Check} alt="check" height="40"/>
</button> */}
{!IsSolo &&
<Link to='/info' target='_blank'>
<button className='button'
style={{
@ -197,17 +360,37 @@ const InGame = ({locale, changeLocale}) => {
}}>
<img src={Check} alt="check" height="40"/>
</button>
</Link>
</Link>}
<button className='button' onClick={handleChange}
{!IsSolo && <button className='button' onClick={handleChange}
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary
}}>
<img src={MGlass} alt="indice" height="40"/>
</button>
</button>}
{!IsSolo && <button className='button' onClick={setShowLastData}
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary
}}>
<img src={ eye } alt="indice" height="40"/>
</button>}
{IsSolo &&
<button className='button' onClick={generateTEX}
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary
}}>
<img src={Download} alt="indice" height="40"/>
</button>
}
</div>
{/*
<Offcanvas show={showP}
onHide={handleCloseP}>
<Offcanvas.Header closeButton>
@ -215,15 +398,16 @@ const InGame = ({locale, changeLocale}) => {
<h3>Il y a {players.length} joueurs</h3>
</Offcanvas.Header>
<Offcanvas.Body>
{/* affichage d'une liste responsive dynamic */}
{/* <div className='playerCanvasBody'>
<PersonStatus state={Replay} name="Dummy"/>
<PersonStatus state={Replay} name="Boat"/>
<PersonStatus state={Replay} name="Bot-tom"/>
</div> */}
<PlayerList players={players} />
</Offcanvas.Body>
</Offcanvas>
*/}
{ !IsSolo &&
<div className='playerlistDiv'>
<PlayerList players={players} setPlayerTouched={handleSetPlayerTouched} playerTouched={playerTouched} playerIndex={playerIndex}/>
</div>
}
<Offcanvas show={show}
onHide={handleClose}
@ -271,9 +455,6 @@ const InGame = ({locale, changeLocale}) => {
</Offcanvas.Body>
</Offcanvas>
<div id="bottom-container">
{showChoiceBar && <ChoiceBar />}
</div>
{/*
<div id="endgamebutton" > {/* tmp
<ButtonImgNav dest="/endgame" img={Leave} text='endgame'/>

@ -0,0 +1,42 @@
.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;
}
.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,59 @@
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';
function Lobbies() {
const theme=useTheme();
const lobbyData = [
{ roomNum: '63194', headPlayer: new User('1', 'Emma', '', null, null), nbPlayer: 6 },
{ roomNum: '81194', headPlayer: new User('2', 'Ray', '', null, null), nbPlayer: 1 },
{ roomNum: '22194', headPlayer: new User('3', 'Norman', '', null, null), nbPlayer: 4 },
{ roomNum: 'null', headPlayer: new User('null', 'tnull', '', null, null), nbPlayer: 1 },
{ roomNum: '111111', headPlayer: new User('11', '1111111', '', null, null), nbPlayer: 1 },
{ roomNum: '741852963', headPlayer: new User('3', 'Guest_741852963', '', null, null), nbPlayer: 6 },
];
const [searchTerm, setSearchTerm] = useState('');
const filteredLobbies = lobbyData.filter((lobby) =>
lobby.roomNum.toLowerCase().includes(searchTerm.toLowerCase()) ||
lobby.headPlayer.pseudo.toLowerCase().includes(searchTerm.toLowerCase())
);
return(
<div style={{display:'flex', flexDirection:'column', alignItems:'center'}}>
<h1>Bienvenue dans le lobby des lobbies</h1>
<input
type="text"
className='searchLobby'
placeholder="Rechercher un lobby..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<div className="lobbyList">
{filteredLobbies.map((lobby, index) => (
<LobbyContainer
key={index}
roomNum={lobby.roomNum}
HeadPlayer={lobby.headPlayer}
nbPlayer={lobby.nbPlayer}
/>
))}
</div>
</div>
);
}
export default Lobbies;

@ -11,6 +11,7 @@
padding: 20px;
margin-right: 10px;
margin-left: 30px;
}
.lobby-vertical-divider{
@ -26,7 +27,7 @@
.player-board{
height: 100%;
width: max-content;
width: 800px;
background-color: rgb(243, 241, 235);
border-radius: 20px;
@ -53,4 +54,44 @@
color: gray;
font-size: 20px;
cursor: pointer;
}
.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;
}

@ -1,99 +1,189 @@
import React, { useEffect, useState } from 'react';
/* Style */
import './Lobby.css';
import "../Style/Global.css"
import { useTheme } from '../Style/ThemeContext';
/* res */
import PlayerItemList from '../Components/PlayerItemList'
import PersonImg from '../res/img/Person.png';
import Bot from '../res/img/bot.png';
import param from '../res/icon/param.png';
import cible from '../res/icon/cible.png';
import defaultImg from "../res/img/Person.png"
/* Component */
import ButtonImgNav from '../Components/ButtonImgNav';
import { io } from 'socket.io-client';
import { Link } from 'react-router-dom';
/* Context */
import { useGame } from '../Contexts/GameContext';
import { useAuth } from '../Contexts/AuthContext';
import { useNavigate } from 'react-router-dom';
/* Model */
import PersonNetwork from '../model/PersonsNetwork';
import Person from '../model/Person';
import GameCreator from '../model/GameCreator';
import { useGame } from '../Contexts/GameContext';
import JSONParser from '../JSONParser';
import Indice from '../model/Indices/Indice';
import { useNavigate } from 'react-router-dom';
import JSONParser from '../JSONParser';
import Player from '../model/Player';
import EasyBot from '../model/EasyBot';
import Bot from '../model/Bot';
import User from '../model/User';
import {loadImageAsync} from "../ImageHelper"
/* serv */
import { socket } from "../SocketConfig";
import { random } from 'lodash';
import Player from '../model/Player';
import SessionService from '../services/SessionService';
import { useRef } from 'react';
import Button from 'react-bootstrap/Button';
import Overlay from 'react-bootstrap/Overlay';
let gameStarted = false
function Lobby() {
const theme=useTheme();
const navigate = useNavigate();
const { indices, setIndicesData, indice, setIndiceData, person, setPersonData, personNetwork, setPersonNetworkData, players, setPlayersData, setActualPlayerIndexData, setTurnPlayerIndexData, setRoomData } = useGame();
const {user, setUserData, manager, login} = useAuth()
let first = true
const params = new URLSearchParams(window.location.search);
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())
}
//* nb Node
const [enteredNumber, setEnteredNumber] = useState(20);
//@ts-ignore
const handleNumberChange = (event) => {
const newNumber = Math.max(20, Math.min(60, parseInt(event.target.value, 10)));
setEnteredNumber(newNumber);
};
useEffect(() => {
if (first){
first = false
socket.emit("lobby joined", room, "test name" + Math.floor(Math.random() * 10))
first=false
if (user == null){
manager.userService.fetchUserInformation().then(([u, loggedIn]) => {
if (u!=null){
if (loggedIn){
login()
setUserData(u)
}
else{
loadImageAsync(defaultImg).then((blob) => {
u.profilePicture=blob
setUserData(u)
})
}
socket.emit("lobby joined", room, u.toJson())
}
})
}
else{
socket.emit("lobby joined", room, user.toJson())
}
return () => {
socket.off('game created');
};
}
}, []);
}, [socket.id]);
socket.on("game created", (jsonNetwork, jsonPersonString, jsonIndicesString, playerIndex)=> {
const jsonPerson = JSON.parse(jsonPersonString)
const network: PersonNetwork = JSONParser.JSONToNetwork(jsonNetwork)
const choosenOne: Person = network.getPersons().filter((i) => i.getId() == jsonPerson.id)[0]
const choosenIndices : Indice[] = JSONParser.JSONToIndices(jsonIndicesString)
let index = 0
for (let i=0; i<players.length; i++){
if(players[i].id == socket.id){
index=i
break
const player = players[i]
if(player.id == socket.id){
setActualPlayerIndexData(i)
setIndiceData(choosenIndices[i])
}
if (player instanceof Bot){
player.indice = choosenIndices[i]
}
}
if (room != null){
setRoomData(room)
}
setTurnPlayerIndexData(playerIndex)
setActualPlayerIndexData(index)
setIndiceData(choosenIndices[index])
setPersonData(choosenOne)
setPersonNetworkData(network)
setIndicesData(choosenIndices)
first = true
navigate('/game?solo=false');
gameStarted = true
socket.off("player left")
socket.off("new player")
navigate('/game?solo=false&daily=false');
});
socket.on("new player", (tab) =>{
const tmpTab: Player[] = []
for (const p of tab){
tmpTab.push(new Player(p.id, p.name))
tmpTab.push(JSONParser.JSONToPlayer(p))
}
setPlayersData(tab.map((p: any) => new Player(p.id, p.name)))
console.log(tmpTab)
setPlayersData(tmpTab)
})
socket.on("player left", (tab, i) => {
const tmpTab: Player[] = []
for (const p of tab){
tmpTab.push(JSONParser.JSONToPlayer(p))
}
setPlayersData(tmpTab)
})
const [codeShowed, setCodeShowed] = useState(true);
function StartGame(){
const [networkPerson, choosenPerson, choosenIndices] = GameCreator.CreateGame(players.length, 30)
const [networkPerson, choosenPerson, choosenIndices] = GameCreator.CreateGame(players.length, enteredNumber)
setPersonData(choosenPerson)
setPersonNetworkData(networkPerson)
setIndicesData(choosenIndices)
socket.emit('network created', JSON.stringify(networkPerson, null, 2), JSON.stringify(choosenPerson), JSON.stringify(choosenIndices), room);
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);
}
const copyGameLink = () => {
setShow(!show)
const gameLink = "http://localhost:3000/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 [show, setShow] = useState(false);
const target = useRef(null);
return (
<div className='lobby-container'>
<div className='left-part'>
@ -109,15 +199,24 @@ function Lobby() {
</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} pdp={PersonImg} name={player.name} id={player.id}/>
<PlayerItemList key={player.id} player={player} room={room}/>
))}
<div className='centerButton'>
<button className='button' onClick={addBot}
style={{
backgroundColor: theme.colors.primary,
borderColor: theme.colors.secondary}}>
+
</button>
</div>
</div>
</div>
<div className="lobby-vertical-divider" style={{backgroundColor: theme.colors.secondary}}></div>
<div className='right-part'>
<div className='title-param-div'>
{/* <div className='title-param-div'>
<img src={param} alt="param"/>
<h2>Paramètre de la partie</h2>
</div>
@ -126,19 +225,105 @@ function Lobby() {
<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*/}
<li><h4>Niveau des bots : Facile </h4></li>
<li><h4>Thèmes : basique </h4></li> */}
{
//? mettre un timer pour chaques personne ?
//? indice avancé ? ==> négation, voisin du 2e degré etc.
}
</ul>
<center >
{/* </ul> */}
{/* <center >
<button className='buttonNabImg' onClick={StartGame}>
<img src={cible} alt="Button Image" height="50" width="50"/>
<img src={cible} alt="Button Image" height="50" width="50" />
<p>{"la chasse !"}</p>
</button>
</center>
</center> */}
<div className='lobbyR'
style={{flexDirection:'column',
alignItems:'space-around'}}>
<h3>Bienvenue dans votre lobby !</h3>
<p>Attendez que tous vos amis rejoignent avant de lancer la partie.</p>
{/* Bouton pour copier le lien */}
<Button variant="primary" ref={target} onClick={copyGameLink}>
Inviter des amis
</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,
}}
>
Lien copié
</div>
)}
</Overlay>
<div className='nbNodeDiv'>
<label htmlFor="numberInput">Sélectionner le nombre de noeud (entre 20 et 60) :</label>
<div>
<button className='valuebutton' onClick={() => { if (enteredNumber>20) setEnteredNumber(enteredNumber-1)}}
style={{borderColor:theme.colors.secondary}}> - </button>
<input
// type="number"
id="numberInput"
disabled
value={ "Nombre de noeuds : " + enteredNumber}
onChange={handleNumberChange}
min={20}
max={60}/>
<button className='valuebutton' onClick={() => { if (enteredNumber<60) setEnteredNumber(enteredNumber+1)}}
style={{borderColor:theme.colors.secondary}}> + </button>
</div>
</div>
<button className='button' onClick={StartGame}
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary,
width: 'auto',
height: 'auto'
}}>
Démarrer la partie !
</button>
</div>
{/* <div className='centerDivH'>
<div>
<label htmlFor="numberInput">Séléctionner le nombre de noeud (entre 20 et 60) :</label>
<input
type="number"
id="numberInput"
value={enteredNumber}
onChange={handleNumberChange}
min={20}
max={60}/>
<p>La valeur saisie : {enteredNumber}</p>
</div>
<div className='centerDivH'>
<button className='button' onClick={StartGame}
style={{
backgroundColor: theme.colors.tertiary,
borderColor: theme.colors.secondary
}}>
<img src={cible} alt="cible" height="40"/>
</button>
</div> */}
</div>
</div>
);

@ -1,52 +1,114 @@
import React, { Component } from 'react';
import React, { useState, useEffect } from 'react';
import { AiOutlineSend } from 'react-icons/ai';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../Contexts/AuthContext';
import AuthService from '../services/AuthService';
import '../Style/Global.css';
export default class Login extends Component {
render() {
return (
<div className="form-container">
<form>
<br/>
<h3>Log In</h3>
<div className="mb-3">
<label>Pseudo</label>
<input
const SignIn = () => {
const navigate = useNavigate();
const { login } = useAuth();
const [error, setError] = useState<string | null>(null);
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
};
const validation = await AuthService.validateSignIn(data);
if (!validation.valid) {
setError(validation.error);
} else {
setError(null);
const result = await AuthService.signIn(data);
// console.log(result);
setShowConfirmation(true);
setTimeout(async () => {
await login();
navigate('/play'); // 3 secondes avant de rediriger vers la page de connexion
}, 3000);
}
} catch (error: any) {
setError(error.message);
}
};
useEffect(() => {
return () => {
setShowConfirmation(false);
};
}, []);
return (
<div className="form-container">
<form onSubmit={handleSubmit}>
<br/>
<h3>Log In</h3>
<div className="mb-3">
<label>Pseudo</label>
<input
type="pseudo"
name='pseudo'
className="form-control"
placeholder="Entrez votre pseudo ici"
/>
</div>
<div className="mb-3">
<label>Password</label>
<input
/>
</div>
<div className="mb-3">
<label>Password</label>
<input
type="password"
name='password'
className="form-control"
placeholder="Entrez votre mot de passe ici"
/>
</div>
<div className="mb-3">
<div className="custom-control custom-checkbox">
/>
</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">
</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é ?
</p>
</form>
</div>
)
}
}
</button>
</div>
<p className="forgot-password text-right">
<a href="#">Mot de passe</a> oublié ?
</p>
</form>
{error && (
<div className="alert alert-danger" role="alert">
{error}
</div>
)}
{showConfirmation && (
<div className="alert alert-success" role="alert">
Connexion réussie ! Vous serez redirigé vers votre profil dans 3 secondes.
</div>
)}
</div>
);
};
export default SignIn;

@ -35,25 +35,7 @@
width: 30%;
}
.LeaderBoardiv{
background-color: #D7D4C6;
opacity: 80%;
border-radius: 25px;
display: flex;
align-items: center;
flex-direction: column;
margin: 15px;
}
.textBoard{
display: flex;
flex-wrap: column;
justify-content: space-between;
width: 300px;
}
/* .textBoard div{
display: flex;

@ -1,34 +1,113 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
/* Context */
import { useAuth } from '../Contexts/AuthContext';
/* Style */
import './Play.css';
import { useTheme } from '../Style/ThemeContext';
/* Nav */
import { Link } from 'react-router-dom';
/* Component */
import ButtonImgNav from "../Components/ButtonImgNav"
import ButtonImgNav from "../Components/ButtonImgNav";
/* Img */
import Person from '../res/img/Person.png';
/* Icon */
import trophy from '../res/icon/trophy.png';
import param from '../res/icon/param.png';
import share from '../res/icon/share.png';
import { socket } from '../SocketConfig';
import { useNavigate } 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';
function Play() {
let first = true
const theme=useTheme()
const {isLoggedIn, login, user, setUserData, manager } = useAuth();
const {setDailyEnigmeData} = useGame()
const target = useRef(null);
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]) =>{
if (user!=null){
if (loggedIn){
login()
setUserData(user)
}
else{
loadImageAsync(defaultImg).then((blob) => {
user.profilePicture=blob
setUserData(user)
})
}
}
})
}
}, [isLoggedIn]);
const [room, setRoom] = useState(null);
const navigate = useNavigate();
@ -36,20 +115,32 @@ function Play() {
socket.emit("lobby created")
}
useEffect(() => {
console.log(user)
}, [user])
function launchMastermind(){
const [networkPerson, choosenPerson, choosenIndices] = GameCreator.CreateGame(5, 30)
const [networkPerson, choosenPerson, choosenIndices] = GameCreator.CreateGame(3, 30)
setPersonData(choosenPerson)
setPersonNetworkData(networkPerson)
setIndicesData(choosenIndices)
setIndicesData(choosenIndices)
navigate('/game?solo=true');
navigate('/game?solo=true&daily=false');
}
function launchEngimeJour(){
//* overlay
if (!showOverlay)setShowOverlay(true)
else setShowOverlay(true)
}
useEffect(() => {
const handleLobbyCreated = (newRoom: any) => {
setRoom(newRoom);
setRoom(newRoom);
};
// Ajouter l'event listener
@ -62,15 +153,56 @@ function Play() {
}, []); // Aucune dépendance ici
useEffect(() => {
useEffect(() => {
if (room !== null) {
const nouvelleURL = `/lobby?room=${room}`;
navigate(nouvelleURL);
const nouvelleURL = `/lobby?room=${room}`;
navigate(nouvelleURL);
}
}, [room, navigate]);
}, [room, navigate]);
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(3, 30)
setPersonData(choosenPerson)
setPersonNetworkData(networkPerson)
setIndicesData(choosenIndices)
setIndicesData(choosenIndices)
navigate('/game?solo=true&daily=true&easy=true');
setShowOverlay(false);
};
const handleStartHardGame = () => {
//* Mode difficile
//todo différencier les deux
const [networkPerson, choosenPerson, choosenIndices] = GameCreator.CreateGame(3, 30)
setPersonData(choosenPerson)
setPersonNetworkData(networkPerson)
setIndicesData(choosenIndices)
setIndicesData(choosenIndices)
if (first){
first = false
const map = EnigmeDuJourCreator.createEnigme(networkPerson, choosenIndices, choosenPerson, Stub.GenerateIndice())
setDailyEnigmeData(map)
}
navigate('/game?solo=true&daily=true&easy=false');
setShowOverlay(false);
};
return (
<div className="MainContainer">
@ -78,14 +210,14 @@ function Play() {
{/* <button className='ButtonNav'>
Param
</button> */}
<ButtonImgNav dest='/signup' img={Person} text="Gestion du compte"/>
{/* <ButtonImgNav dest='/signup' img={defaultImg} text="Gestion du compte"/> */}
</div>
<div className="MidContainer">
<div>
<h2>
Guest 177013
{user && user.pseudo}
</h2>
<img src={Person}
<img src={user?.profilePicture}
height='300'
width='300'
alt="Person"
@ -93,39 +225,36 @@ function Play() {
</div>
<div className='buttonGroupVertical'>
<button onClick={launchMastermind} className="ButtonNav" style={{backgroundColor: theme.colors.primary, borderColor: theme.colors.secondary}}> Jouer seul </button>
<button ref={target} onClick={launchEngimeJour} className="ButtonNav" style={{backgroundColor: theme.colors.primary, borderColor: theme.colors.secondary}}> Résoudre une énigme</button>
<Overlay show={showOverlay} target={target.current} placement="bottom" rootClose={true} rootCloseEvent='click'>
{({ 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}>Facile</Button>
<Button onClick={handleStartHardGame}>Difficile</Button>
</ButtonGroup>
</div>
)}
</Overlay>
<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("/join")} className="ButtonNav" style={{backgroundColor: theme.colors.primary, borderColor: theme.colors.secondary}}> Rejoindre </button>
</div>
</div>
<div className='rightContainer'>
<div className='LeaderBoardiv'>
<img src={trophy}
height='100'
width='100'
alt="Person2"
/>
<div className='textBoard'>
<div>
<h4>
Partie Jouées : <br/>
Partie gagnées : <br/>
Pions posés : <br/>
</h4>
</div>
<div>
<h4>
10 <br/>
2 <br/>
45 <br/>
</h4>
</div>
</div>
{/* <button className='ButtonNav'>
Share
</button> */}
<ButtonImgNav dest='/' img={share}/>
</div>
{user && (<ScoreBoard Player={user}/>)}
</div>
</div>
);

@ -0,0 +1,123 @@
.mainContainer{
display: flex;
/* flex-direction: column; */
justify-content: center;
align-items: center;
margin: 50px;
}
.mainPDPContainer{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 2px solid whitesmoke;
border-radius: 15px;
background-color: white;
margin: 10px;
min-height: 250px;
}
/*Lpart*/
.imgContainer{
border: 5px solid black;
border-radius: 50px;
margin: 15px;
}
/*Rpart*/
.Rpart{
min-width: 40%;
min-height: 250px;
margin: 20px;
padding: 20px;
background-color: white;
border: solid 1px whitesmoke;
border-radius: 15px;
}
.username-display{
display: flex;
}
.editbutton{
border-color: white;
background-color: white;
border: none;
margin-left: 15px;
height: 25px;
width: 25px;
}
.inputpseudo{
display: 'flex';
justify-content: 'flex-start';
align-items: 'center';
flex-direction: 'row';
width: 20vw;
padding: 5;
border:none;
border-bottom: solid 2px gray;
border-radius: 5;
font-size: 40px;
}
.bottom{
display: flex;
flex-direction: column;
justify-content: end;
align-items: end;
height: 100px;
}
/*File upload*/
.parent {
/* width: 250px; */
/* margin: auto; */
margin: 2rem;
background: #ffffff;
border-radius: 25px;
/* box-shadow: 7px 20px 20px rgb(210, 227, 244); */
}
.file-upload {
text-align: center;
border: 3px dashed rgb(210, 227, 244);
/* padding: 1.5rem; */
position: relative;
cursor: pointer;
width: 100px;
height: 50px;
}
.file-upload p {
font-size: 0.5rem;
/* margin-top: 10px; */
color: #bbcada;
}
.file-upload input {
display: block;
height: 100%;
width: 100%;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
opacity: 0;
cursor: pointer;
}

@ -0,0 +1,194 @@
import React, { useEffect, useState } 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';
/* Style */
import './Profile.css'
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';
/* Context */
import { useAuth } from '../Contexts/AuthContext';
/* Boostrap */
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';
//@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 [newUsername, setNewUsername] = useState(user?.pseudo);
//@ts-ignore
const onUsernameChange = (newUsername) => {
if(user?.pseudo != null){
SessionService.UpdatePseudo(user.pseudo, newUsername)
user.pseudo = newUsername;
}
}
const handleUsernameChange = () => {
// Maj du pseudo
onUsernameChange(newUsername);
// Désactiver le mode d'édition
setEditingUsername(false);
};
//* Gestion Modal de suppression :
const [showDeleteModal, setShowDeleteModal] = useState(false);
const handleShowDeleteModal = () => {
setShowDeleteModal(true);
};
const handleCloseDeleteModal = () => {
setShowDeleteModal(false);
};
//* Confirmation avec la phrase :
const [confirmationPhrase, setConfirmationPhrase] = useState('');
const [showWrong, setShowWrong] = useState(false);
//@ts-ignore
const handleConfirmationPhraseChange = (e) => {
setConfirmationPhrase(e.target.value);
};
const handleDeleteAccount = () => {
// 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();
}
else{
console.error("l'utilisateur ne peut pas être null")
}
handleCloseDeleteModal();
navigate("/play")
} else {
console.error('Phrase de confirmation incorrecte.');
setShowWrong(true);
setTimeout(async () => {
setShowWrong(false);
}, 3000);
}
};
return (
<>
<center><h1>Mon Compte</h1></center>
<div className='mainContainer'>
<div>
<ProfilePDP/>
</div>
<div className='Rpart'>
{editingUsername ? (
<div className='username-edit'>
<input
type='text'
className='inputpseudo'
value={newUsername}
onChange={(e) => setNewUsername(e.target.value)}
/>
<button className='editbutton' onClick={handleUsernameChange}>
<img src={Coche} alt='edit' width='25' height='25'/>
</button>
<button className='editbutton' onClick={() => setEditingUsername(false)}>
<img src={Cancel} alt='edit' width='25' height='25'/>
</button>
</div>
) : (
<div className='username-display'>
<h1>{user?.pseudo}</h1>
<button className='editbutton' onClick={() => setEditingUsername(true)}>
<img src={Edit} alt='edit' width='25' height='25'/>
</button>
</div>
)
}
<hr/>
{!editingUsername ? (
<Button variant="secondary">Modifier le mot de passe</Button>
) : (
<Alert key='info' variant='info' style={{width:'100%'}}>
Vous êtes en mode "édition".
</Alert>
)}
<div className='bottom'>
<>
<Button variant="danger" onClick={handleShowDeleteModal}>Supprimer</Button>
<Modal show={showDeleteModal} onHide={handleCloseDeleteModal}>
<Modal.Header closeButton>
<Modal.Title>Confirmation de suppression</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>
Pour confirmer la suppression de votre compte, veuillez
entrer la phrase : "supprimer mon compte".
</p>
<Form.Control
type='text'
placeholder='Entrez la phrase de confirmation'
value={confirmationPhrase}
onChange={handleConfirmationPhraseChange}
/>
{
showWrong &&
<Alert key='infomodel' variant='danger' style={{width:'100%'}}>
La phrase de confirmation est incorrecte.
</Alert>
}
</Modal.Body>
<Modal.Footer>
<Button variant='secondary' onClick={handleCloseDeleteModal}>
Annuler
</Button>
<Button variant='danger' onClick={handleDeleteAccount}>Supprimer mon compte</Button>
</Modal.Footer>
</Modal>
</>
</div>
</div>
</div>
</>
);
};
export default Profile;

@ -1,48 +1,111 @@
import React, { Component } from 'react';
import React, { useState, useEffect } from 'react';
import { AiOutlineSend } from 'react-icons/ai';
import { useNavigate } from 'react-router-dom';
import AuthService from '../services/AuthService';
import '../Style/Global.css';
export default class SignUp extends Component {
render() {
return (
<div className="form-container">
<form>
<br/>
<h3>Sign Up</h3>
<div className="mb-3">
<label>Pseudo</label>
<input
type="pseudo"
const SignUp = () => {
const navigate = useNavigate();
const [error, setError] = useState<string | null>(null);
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,
Cpassword: (event.target as any).Cpassword.value,
};
const validation = await AuthService.validateSignUp(data);
if (!validation.valid) {
setError(validation.error);
} else {
setError(null);
const result = await AuthService.signUp(data);
console.log(result);
setShowConfirmation(true);
setTimeout(() => {
navigate('/login'); // 3 secondes avant de rediriger vers la page de connexion
}, 3000);
}
} catch (error: any) {
setError(error.message);
}
};
useEffect(() => {
return () => {
setShowConfirmation(false);
};
}, []);
return (
<div className="form-container">
<form onSubmit={handleSubmit}>
<br/>
<h3>Sign Up</h3>
<div className="mb-3">
<label htmlFor="pseudo">Pseudo</label>
<input
type="text"
id="pseudo"
name="pseudo"
className="form-control"
placeholder="Entrez votre pseudo ici"
/>
</div>
<div className="mb-3">
<label>Password</label>
<input
/>
</div>
<div className="mb-3">
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
name="password"
className="form-control"
placeholder="Entrez votre mot de passe ici"
/>
</div>
<div className="mb-3">
<label>Confirm Password</label>
<input
type="Cpassword"
required
/>
</div>
<div className="mb-3">
<label htmlFor="Cpassword">Confirm Password</label>
<input
type="password"
id="Cpassword"
name="Cpassword"
className="form-control"
placeholder="Confirmez votre mot de passe ici"
/>
</div>
<div className="d-grid">
<button type="submit" className="btn btn-primary">
required
/>
</div>
<div className="d-grid">
<button type="submit" className="btn btn-primary">
Soumettre <AiOutlineSend/>
</button>
</div>
<p className="forgot-password text-right">
Vous avez déjà un <a href="/login">compte</a> ?
</p>
</form>
</div>
)
}
}
</button>
</div>
<p className="forgot-password text-right">
Vous avez déjà un <a href="/login">compte</a> ?
</p>
</form>
{error && (
<div className="alert alert-danger" role="alert">
{error}
</div>
)}
{showConfirmation && (
<div className="alert alert-success" role="alert">
Inscription réussie ! Vous serez redirigé vers la page de connexion dans 3 secondes.
</div>
)}
</div>
);
};
export default SignUp;

@ -0,0 +1,204 @@
import PersonNetwork from "../model/PersonsNetwork";
import Person from "../model/Person";
import GenerateNetwork from "../model/NetworkGenerator";
import NetworkGenerator from "../model/NetworkGenerator";
import fs from 'fs';
import Stub from "../model/Stub";
import GameCreator from "../model/GameCreator";
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"
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 / 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")} \\\\`
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`;
});
console.log(person.getFriends().length);
});
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"
choosenIndices.forEach((indice, index) => {
latexCode += `\\item Indice ${index + 1} : ${indice.ToString('fr')}.\n`
})
latexCode += "\\end{compactitem}\n"
//* Solution
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`;
});
console.log(person.getFriends().length);
});
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 += "\\end{document}\n"
return latexCode
}
export {generateLatexCode, generateLatexCodeEnigme}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@ -1,36 +0,0 @@
import {
Network,
Options,
Data,
Edge,
Node
} from "vis-network/standalone/esm/vis-network";
import React, { useState, useLayoutEffect, useRef } from "react";
export interface UseVisNetworkOptions {
options: Options;
nodes: Node[];
edges: Edge[];
}
export default (props: UseVisNetworkOptions) => {
const { edges, nodes, options } = props;
const [network, addNetwork] = useState<Network | null>(null);
const ref = useRef<HTMLDivElement>(null);
const data: Data = { nodes, edges };
useLayoutEffect(() => {
if (ref.current) {
const instance = new Network(ref.current, data, options);
addNetwork(instance);
}
return () => network?.destroy();
}, []);
return {
network,
ref
};
};

@ -1,6 +1,6 @@
import { io } from "socket.io-client";
import { ADRESSE_WEBSERVER } from "./AdressSetup";
const socket = io("http://127.20.10.4:3002");
const socket = io(ADRESSE_WEBSERVER);
export {socket}

@ -22,12 +22,32 @@
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background-color:#fff;
border-radius: 15px;
width: 500px;
margin: 10px 20px;
}
.item-horizontal-div div{
display: flex;
align-items: center;
}
.delButtonDiv{
display: flex;
justify-content: end;
}
.suprButton{
margin: auto 50px;
border: solid 1px darkred;
border-radius: 10px;
background-color: red;
}
.statusDiv{
/* background-color: #A7E2DC; */
border-radius: 15px;
@ -52,4 +72,31 @@ form{
.listContainer{
columns: 2 auto;
}
}
/* LeaderBoard */
.LeaderBoardiv{
border: solid;
border-width: 0px 1px 1px 1px;
border-color: lightgray;
border-radius: 0px 0px 20px 20px;
display: flex;
align-items: center;
flex-direction: column;
margin: 15px;
}
.textBoard{
display: flex;
flex-wrap: column;
justify-content: space-between;
width: 300px;
}
.leftRow{
display: flex;
justify-content: end;
}

@ -10,4 +10,4 @@ const theme = {
},
};
export default theme;
export default theme;

@ -21,6 +21,7 @@
"log_in":" Log in ",
"sign_up":" Sign up ",
"log_out":" Log out ",
"join" : "Join",
"create_room" : "Create room",
@ -42,11 +43,11 @@
"and": "and",
"or": "or",
"or_sport": "and/or",
"or_sport": "or",
"age_indice_start": "The suspect is between",
"age_indice_more_start": "The suspect is older than or",
"age_indice_more_start": "The suspect is at least",
"age_indice_end": "years old",
"color_edges_start": "The suspect has at least one friend with",
@ -55,11 +56,11 @@
"color_start": "The suspect has",
"color_end": "hair",
"nb_friends_indice_start": "The suspect has at least",
"nb_friends_indice_start": "The suspect has",
"nb_friends_indice_end": "friends",
"nb_sports_indice_start": "The suspect is playing",
"nb_sports_indice_end": "sport",
"nb_sports_indice_end": "sport(s)",
"sport_start": "The suspect plays at least",
"sport_end": "",
@ -104,7 +105,7 @@
"info.composant.hair_col.title":"Hair's colors",
"info.composant.hair_col":"The characters also have a hair colour, which can be found in the following colours :",
"hair.blanc":"Blanc",
"hair.blanc":"White",
"hair.blond":"Blond",
"hair.roux":"Red head",
"hair.chatain":"Brown",

@ -21,6 +21,7 @@
"log_in":" Se connecter ",
"sign_up":" S'inscrire ",
"log_out":" Se déconnecter ",
"join" : "Rejoindre",
@ -42,10 +43,10 @@
"and": "et",
"or": "ou",
"or_sport": "et/ou du",
"or_sport": "ou du",
"age_indice_start": "Le suspect a entre",
"age_indice_more_start": "Le suspect a ou a plus de",
"age_indice_more_start": "Le suspect a au moins",
"age_indice_end": "ans",
"color_edges_start": "Le suspect a au moins un ami avec les cheveux",
@ -54,7 +55,7 @@
"color_start": "Le suspect a les cheveux",
"color_end": "",
"nb_friends_indice_start": "Le suspect a au moins",
"nb_friends_indice_start": "Le suspect a",
"nb_friends_indice_end": "amis",
"nb_sports_indice_start": "Le suspect pratique",

@ -0,0 +1,32 @@
import { DataSet } from "vis-network";
import NodePerson from "./Graph/NodePerson";
import Indice from "./Indices/Indice";
import Pair from "./Pair";
import Person from "./Person";
import PersonNetwork from "./PersonsNetwork";
import Player from "./Player";
abstract class Bot extends Player{
public indice: Indice | undefined
public index : number
public actualNetwork: Map<Person, Pair<number, boolean>[]>
constructor( id: string, name: string, profilePicture: string){
super(id, name, profilePicture);
this.actualNetwork = new Map<Person, Pair<number, boolean>[]>()
this.index = -1
}
abstract playRound(personNetwork : PersonNetwork, players: Player[]): [number, number]
abstract placeSquare(personNetwork : PersonNetwork, players: Player[]): number
abstract newInformation(person: Person, playerIndex: number, works: boolean): void
abstract initiateMap(personNetwork: PersonNetwork): void
}
export default Bot

@ -0,0 +1,74 @@
import SessionService from "../../services/SessionService";
import { socket } from "../../SocketConfig";
import User from "../User";
import IUserService from "./IUserService";
class DbUserService implements IUserService{
async fetchUserInformation(): Promise<[User | null, boolean]> {
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,
})
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), '',
{
nbGames: 0,
bestScore: 0,
avgNbTry: 0,
},
{
nbGames: 0,
nbWins: 0,
ratio: 0,
})
return [guestPlayer, false]
}
} catch (error) {
console.error(error);
return [null, false]
}
}
async updateSoloStats(pseudo: string, nbGames: number, bestScore: number, avgNbTry: number): Promise<void> {
try {
const result = await SessionService.updateSoloStats(pseudo, nbGames, bestScore, avgNbTry);
if (result) {
console.log("Stats solo updated");
} else {
console.log("Stats solo not updated");
}
} catch (error) {
console.error(error);
}
}
async updateOnlineStats(pseudo: string, nbGames: number, bestScore: number, ratio: number): Promise<void> {
try {
const result = await SessionService.updateOnlineStats(pseudo, nbGames, bestScore, ratio);
if (result) {
console.log("Stats online updated");
} else {
console.log("Stats online not updated");
}
} catch (error) {
console.error(error);
}
}
}
export default DbUserService

@ -0,0 +1,10 @@
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>
}
export default IUserService

@ -0,0 +1,12 @@
import IUserService from "./IUserService";
class Manager{
public userService: IUserService
constructor(userService: IUserService){
this.userService = userService
}
}
export default Manager

@ -0,0 +1,115 @@
import { DataSet } from "vis-network";
import { colorToEmoji, positionToColor } from "../ColorHelper";
import Bot from "./Bot";
import IndiceTesterFactory from "./Factory/IndiceTesterFactory";
import NodePerson from "./Graph/NodePerson";
import Indice from "./Indices/Indice";
import Pair from "./Pair";
import Person from "./Person";
import PersonNetwork from "./PersonsNetwork";
import Player from "./Player";
import DefaultImg from "../res/img/bot.png"
class EasyBot extends Bot{
constructor(id: string, pseudo: string, profilePicture: string){
if (profilePicture === undefined || profilePicture === ""){
profilePicture=DefaultImg
}
super(id, pseudo, profilePicture)
}
toJson() {
return {
type: "EasyBot",
id: this.id,
pseudo: this.pseudo,
profilePicture: this.profilePicture
};
}
playRound(personNetwork: PersonNetwork, players: Player[]): [number, number] {
if (this.indice==undefined){
return [-1, -1]
}
let rand = Math.random()
const filterPlayers = players.filter((p) => p.id != this.id)
if (rand < 0.75){
const possibleCombinaison = new Map<number, number[]>()
filterPlayers.forEach((p, index) =>{
possibleCombinaison.set(index, [])
})
personNetwork.getPersons().forEach((p) =>{
players.forEach((player, index) =>{
if (index!=this.index && (!this.actualNetwork.get(p)?.includes(new Pair(index, true) || !this.actualNetwork.get(p)?.includes(new Pair(index, false))))){
const t = possibleCombinaison.get(index)
if( t==undefined){
possibleCombinaison.set(index, [p.getId()])
}
else{
t.push(p.getId())
}
}
})
})
let r = this.index
while(r==this.index){
r = Math.floor(Math.random() * players.length)
}
const tab = possibleCombinaison.get(r)
if (tab!= undefined){
const randIndex = Math.floor(Math.random() * tab.length)
const tester = IndiceTesterFactory.Create(this.indice)
const pers = personNetwork.getPersons().find((p) => p.getId() == tab[randIndex])
if (pers != undefined)
return [r, tab[randIndex]]
}
}
else{
const filterNodes = []
const possibleNodes: number[] = []
const indiceTester=IndiceTesterFactory.Create(this.indice)
personNetwork.getPersons().forEach((p) => {
let works = true
for(let i = 0; i<players.length; i++){
if (!indiceTester.Works(p) || this.actualNetwork.get(p)?.includes(new Pair(i, false))){
works = false
}
}
if (works){
possibleNodes.push(p.getId())
}
});
const index = possibleNodes[Math.floor(Math.random() * possibleNodes.length)]
return [players.length, index]
}
return [-1, -1]
}
placeSquare(personNetwork: PersonNetwork, players: Player[]): number {
const tabFilterPerson: Person[] = []
if (this.indice == undefined){
return -1
}
const indiceTester = IndiceTesterFactory.Create(this.indice)
personNetwork.getPersons().forEach((p) =>{
const tab = this.actualNetwork.get(p)
if (!indiceTester.Works(p) && (!tab?.includes(new Pair(this.index, true) || !tab?.includes(new Pair(this.index, false))))){
tabFilterPerson.push(p)
}
})
return tabFilterPerson[Math.floor(Math.random() * tabFilterPerson.length)].getId()
}
newInformation(person: Person, playerIndex: number, works: boolean): void {
this.actualNetwork.get(person)?.push(new Pair(playerIndex, works))
}
initiateMap(personNetwork: PersonNetwork): void {
personNetwork.getPersons().forEach((p) =>{
this.actualNetwork.set(p, [])
})
}
}
export default EasyBot

@ -11,9 +11,8 @@ class EdgesCreator{
CreateWorkingEdge(personNetwork: PersonNetwork, choosenPerson: Person, indice: Indice, indices: Indice[]){
let creator = IndiceEdgesFactory.Create(indice)
const nbMaxEdge = Math.floor(Math.random() * 5) + 1
creator.createWorkingEdges(personNetwork, choosenPerson, indices)
const nbMaxEdge = creator.createWorkingEdges(personNetwork, choosenPerson, indices)
if (choosenPerson.getFriends().length < nbMaxEdge){
for (const p of personNetwork.getPersons()){
@ -75,6 +74,7 @@ class EdgesCreator{
}
CreateAllEdges(personNetwork: PersonNetwork, choosenPerson: Person, indices: Indice[]){
const nbTryConnex = 10
const test = new Map<number, number>()
const tabEdgesSize: number[] = []
for (let i = 0; i<personNetwork.getPersons().length / 4; i++){
@ -101,6 +101,48 @@ class EdgesCreator{
this.CreateNotWorkingEdge(personNetwork, p, choosenPerson, indices, test)
}
});
let [isConnected, connectedNodes, unconnectedNodes] = personNetwork.getConnectivityDetails()
let i = 0
while(!isConnected){
if (i==nbTryConnex){
return
}
for(const pers of unconnectedNodes){
let canBreak = false
for(const newFriend of connectedNodes){
if (pers != newFriend && newFriend!=choosenPerson){
let testEdgeWork1 = 0
let testEdgeWork2 = 0
const p1 = cloneDeep(pers);
const personEdge = cloneDeep(newFriend);
p1.addFriend(personEdge)
personEdge.addFriend(p1)
indices.forEach((indice) => {
const tester = IndiceTesterFactory.Create(indice)
if (tester.Works(p1)){
testEdgeWork1 ++
}
if (tester.Works(personEdge)){
testEdgeWork2 ++
}
});
if (testEdgeWork1 < indices.length && testEdgeWork2 < indices.length){
pers.addFriend(newFriend)
newFriend.addFriend(pers)
canBreak = true
break
}
}
}
if (canBreak){
break
}
}
[isConnected, connectedNodes, unconnectedNodes] = personNetwork.getConnectivityDetails()
i++
}
}
}

@ -0,0 +1,72 @@
import IndiceTesterFactory from "./Factory/IndiceTesterFactory";
import Indice from "./Indices/Indice";
import Pair from "./Pair";
import Person from "./Person";
import PersonNetwork from "./PersonsNetwork";
class EnigmeDuJourCreator{
static createEnigme(personNetwork: PersonNetwork, choosenIndices: Indice[], choosenPerson: Person, allIndices: Indice[]): Map<number, Pair<Indice, boolean>[]>{
const map = new Map<number, Pair<Indice, boolean>[]>()
personNetwork.getPersons().forEach((p) =>{
map.set(p.getId(), [])
})
choosenIndices.forEach((choosenIndice) => {
const choosenIndiceTester = IndiceTesterFactory.Create(choosenIndice)
const modifiedPersons: Pair<Person, boolean>[] = []
let possibleIndices: Indice[] = [...allIndices]
let i = 0
while (possibleIndices.length != 1){
let tmpPossibleIndices: Indice[] = [...possibleIndices]
let choosenPair : Pair<Person, boolean> = new Pair(personNetwork.getPersons()[0], true)
for(const person of personNetwork.getPersons().filter((p) => p.getId() !== choosenPerson.getId())){
const veryTmpIndice = [...possibleIndices]
if (!choosenIndiceTester.Works(person)){
possibleIndices.forEach((possibleIndice, index) =>{
const tester = IndiceTesterFactory.Create(possibleIndice)
if (tester.Works(person)){
const t = veryTmpIndice.findIndex((tmpIndice) => tmpIndice.getId() == possibleIndice.getId())
if (t != -1){
veryTmpIndice.splice(t, 1)
}
}
})
if (veryTmpIndice.length<tmpPossibleIndices.length){
tmpPossibleIndices = veryTmpIndice
choosenPair = new Pair(person, false)
}
}
else{
possibleIndices.forEach((possibleIndice, index) =>{
const tester = IndiceTesterFactory.Create(possibleIndice)
if (!tester.Works(person)){
const t = veryTmpIndice.findIndex((tmpIndice) => tmpIndice.getId() == possibleIndice.getId())
if (t != -1){
veryTmpIndice.splice(t, 1)
}
}
})
if (veryTmpIndice.length<tmpPossibleIndices.length){
tmpPossibleIndices = veryTmpIndice
choosenPair = new Pair(person, true)
}
}
}
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))
})
})
return map
}
}
export default EnigmeDuJourCreator

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

@ -11,7 +11,7 @@ class GraphCreator{
const nodesPerson : NodePerson[] = []
const edges: Edge[] = []
network.getPersons().forEach((p) =>{
let label = p.getName() + "\n" + p.getAge() + "\n"
let label = p.getName() + "\n" + p.getAge() + "🎂" + "\n"
for (let i = 0; i<p.getSports().length; i++){
label += SportToIcon(p.getSports()[i])
}

@ -0,0 +1,11 @@
class NodeColor{
public background: string
public border: string
constructor(color: string, border: string){
this.background=color
this.border=border
}
}
export default NodeColor

@ -8,6 +8,8 @@ class NodePerson{
public color: string
public font: Font
public shape: string
public shadow: boolean = true
public opacity: number = 1
constructor(id: number, label: string, color: Color, font: Font, shape: string){
this.id=id

@ -40,7 +40,7 @@ class ColorIndiceEdgesCreator implements IndiceEdgesCreator{
if (testEdgeWork < indices.length){
p.addFriend(person)
person.addFriend(p)
return 1
return Math.floor(Math.random() * 4)
}
}
}

@ -18,7 +18,7 @@ class NbEdgesIndiceEdgesCreator implements IndiceEdgesCreator{
createWorkingEdges(personNetwork: PersonNetwork, person: Person, indices: Indice[]): number {
let indiceTest = new NbEdgesIndiceTester(this.indice)
const nbEdges = this.indice.getNbEdges() + Math.floor(Math.random() * 2)
const nbEdges = this.indice.getNbEdges()
personNetwork.getPersons().forEach(p => {
if (person.getFriends().length == nbEdges){
return

@ -12,7 +12,7 @@ class NbEdgesIndiceTester implements IndiceTester{
}
Works(person: Person): boolean {
return person.getFriends().length >= this.nbEdgesIndice.getNbEdges()
return person.getFriends().length == this.nbEdgesIndice.getNbEdges()
}
}

@ -13,7 +13,7 @@ class NbSportIndiceTester implements IndiceTester{
}
Works(person: Person): boolean {
return this.nbSportIndice.getNbSport() == person.getSports().length
return this.nbSportIndice.getNbSport().includes(person.getSports().length)
}
}

@ -2,21 +2,30 @@ import { GetJsonFile } from "../EnumExtender";
import Indice from "./Indice";
class NbSportIndice extends Indice {
private nbSport: number;
private nbSport: number[];
constructor(id: number, nbSport: number) {
constructor(id: number, nbSport: number[]) {
super(id);
this.nbSport = nbSport;
}
public getNbSport(): number{
public getNbSport(): number[]{
return this.nbSport
}
// Implémentation de la méthode abstraite
ToString(lang: string): string {
let json = GetJsonFile(lang)
return `${json.nb_sports_indice_start} ${this.nbSport} ${json.nb_sports_indice_end}`;
let string = `${json.nb_sports_indice_start} `;
this.nbSport.forEach((i, index) =>{
if (index == this.nbSport.length - 1){
string += i
}
else{
string += ` ${i} ${json.or} `
}
})
return string + ` ${json.nb_sports_indice_end}`
}
toJSON() {

@ -16,10 +16,10 @@ class SportIndice extends Indice {
let string = json.sport_start;
for (let i = 0; i<this.sports.length; i++){
if (i==this.sports.length - 1 || this.sports.length == 1){
string = `${string} ${SportToString(this.sports[i], lang)}`
string = `${string} ${SportToString(this.sports[i], lang)} `
}
else{
string = `${string} ${SportToString(this.sports[i], lang)} ${json.or_sport}`
string = `${string} ${SportToString(this.sports[i], lang)} ${json.or_sport} `
}
}
return `${string} ${json.sport_end}`

@ -7,36 +7,40 @@ class NetworkGenerator{
static GenerateNetwork(nbPerson: number): PersonNetwork{
let json = require("../res/names.json")
const tabSports: Sport[] = [0, 1, 2, 3, 4, 5, 5, 5, 5]
const tabSports: Sport[] = [0, 1, 2, 3, 4, 5, 5, 5]
const tabColor: Color[] = [0, 1, 2, 3, 4]
const tabJeune: number[] = []
const tabAdo: number[] = []
const tabAdulte: number[] = []
const tabVieux: number[] = []
const tabTresVieux: number[] = []
const tabPerson: Person[] = []
const tabNames = json.names
const tabNames = [...json.names]
/*
let id = 0
for(let i = 0; i < nbPerson/4; i++){
for(let i = 0; i < nbPerson/5; i++){
const nombreAleatoire = Math.floor(Math.random() * 14) + 1;
tabJeune.push(nombreAleatoire)
}
for(let i = 0; i < nbPerson/4; i++){
const nombreAleatoire = Math.floor(Math.random() * 5) + 15;
*/
for(let i = 0; i < nbPerson/3; i++){
const nombreAleatoire = Math.floor(Math.random() * 8) + 12;
tabAdo.push(nombreAleatoire)
}
for(let i = 0; i < nbPerson/4; i++){
for(let i = 0; i < nbPerson/3; i++){
const nombreAleatoire = Math.floor(Math.random() * 10) + 20;
tabAdulte.push(nombreAleatoire)
}
for(let i = 0; i < nbPerson/4; i++){
const nombreAleatoire = Math.floor(Math.random() * 31) + 30;
for(let i = 0; i < nbPerson/3; i++){
const nombreAleatoire = Math.floor(Math.random() * 40) + 30;
tabVieux.push(nombreAleatoire)
}
const tabAge: number[][] = [tabJeune, tabAdo, tabAdulte, tabVieux]
const tabAge: number[][] = [tabAdo, tabAdulte, tabVieux]
let tmpTabSport=[...tabSports]
let tmpTabColor = [...tabColor]
@ -49,7 +53,10 @@ class NetworkGenerator{
}
let sports = []
for (let j = 0; j < 3; j++){
if (tmpTabSport.length == 0) tmpTabSport = [...tabSports]
if (tmpTabSport.length == 0){
tmpTabSport = [...tabSports]
break
}
const rand = Math.floor(Math.random() * tmpTabSport.length)
if (tmpTabSport[rand] != Sport.AUCUN){
sports.push(tmpTabSport[rand])

@ -0,0 +1,15 @@
class Pair<T1, T2>{
public first: T1
public second: T2
constructor(first: T1, second: T2){
this.first=first
this.second=second
}
equals(other: Pair<T1, T2>): boolean {
return this.first === other.first && this.second === other.second;
}
}
export default Pair

@ -18,6 +18,41 @@ class PersonNetwork{
addPerson(person: Person){
this.persons.push(person)
}
private dfs(startNode: Person, visited: Set<number>) {
visited.add(startNode.getId());
for (const friend of startNode.getFriends()) {
if (!visited.has(friend.getId())) {
this.dfs(friend, visited);
}
}
}
getConnectivityDetails(): [boolean, Person[], Person[]] {
if (this.persons.length === 0) {
// Si la liste de personnes est vide, le graphe est considéré comme connexe.
return [true, [], []]
}
const visited: Set<number> = new Set();
const connectedNodes: Person[] = [];
// Commencez la recherche en profondeur à partir du premier nœud du réseau.
this.dfs(this.persons[0], visited);
// Obtenez les nœuds connectés.
for (const person of this.persons) {
if (visited.has(person.getId())) {
connectedNodes.push(person);
}
}
// Obtenez les nœuds non connectés.
const unconnectedNodes: Person[] = this.persons.filter(person => !visited.has(person.getId()));
return [unconnectedNodes.length === 0, connectedNodes, unconnectedNodes];
}
}
export default PersonNetwork

@ -1,12 +1,17 @@
class Player{
abstract class Player{
public id: string
public name: string;
public pseudo: string;
public profilePicture: string
constructor(id: string, name: string){
constructor(id: string, pseudo: string, profilePicture: string){
this.id=id
this.name=name
this.pseudo=pseudo
this.profilePicture=profilePicture
}
abstract toJson(): any
}
export default Player

@ -8,4 +8,3 @@ enum Sport {
}
export default Sport

@ -13,29 +13,23 @@ class Stub{
static GenerateIndice(): Indice[]{
let indice = new NbEdgesIndice(1, 2)
let indice1 = new NbEdgesIndice(2, 3)
let ageIndice = new AgeIndice(3, 0, 14)
let ageIndice1 = new AgeIndice(4, 15, 19)
let ageIndice2 = new AgeIndice(5, 20, 29)
let ageIndice3 = new AgeIndice(6, 30, 100000)
let indice2 = new NbEdgesIndice(3, 4)
let ageIndice = new AgeIndice(4, 12, 19)
let ageIndice1 = new AgeIndice(5, 20, 29)
let ageIndice2 = new AgeIndice(6, 30, 100000)
let indices: Indice[] = [indice, indice1, ageIndice, ageIndice1, ageIndice2, ageIndice3]
let indices: Indice[] = [indice, indice1, indice2, ageIndice, ageIndice1, ageIndice2]
let test = 7
for (let i: Color=0; i<5; i++){
for (let j: Color=0; j<5; j++){
if (j==i){
continue
}
for (let j: Color=i + 1; j<5; j++){
indices.push(new ColorIndice(test, [i, j]))
test++
}
}
for (let i: Sport=0; i<5; i++){
for (let j: Sport=0; j<5; j++){
if (j==i){
continue
}
for (let j: Sport=i + 1; j<5; j++){
indices.push(new SportIndice(test, [i, j]))
test++
}
@ -49,8 +43,9 @@ class Stub{
//* Nombre de sport
for (let i=1; i<3; i++){
indices.push(new NbSportIndice(test, i))
indices.push(new NbSportIndice(test, [i]))
test++
}
return indices
}

@ -0,0 +1,27 @@
import Player from "./Player";
import defaultImg from "../res/img/Person.png"
class User extends Player{
public soloStats: any
public onlineStats: any
constructor(id: string, pseudo: string, profilePicture: string, soloStats: any, onlineStats: any){
super(id, pseudo, profilePicture || defaultImg)
this.soloStats=soloStats
this.onlineStats=onlineStats
}
toJson() {
return {
type: "User",
id: this.id,
profilePicture: this.profilePicture,
pseudo: this.pseudo,
soloStats: this.soloStats,
onlineStats: this.onlineStats
};
}
}
export default User

Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -22,7 +22,6 @@
"Riley",
"Layla",
"Stella",
"Aurora",
"Natalie",
"Zoe",
"Lucy",
@ -48,7 +47,6 @@
"Bella",
"Sadie",
"Hailey",
"Aurora",
"Liam",
"Noah",
"Oliver",
@ -69,7 +67,6 @@
"Jack",
"Jayden",
"Owen",
"Noah",
"Ethan",
"Mason",
"Logan",
@ -93,4 +90,3 @@
"Julian"
]
}

@ -0,0 +1,137 @@
const UserService = require('../services/UserService');
const DatabaseService = require('../services/DatabaseService');
const bcrypt = require('bcrypt');
const sqlite3 = require('sqlite3');
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);
// Initialiser les stats de l'utilisateur
await databaseService.initSoloStats(user.idUser);
await databaseService.initOnlineStats(user.idUser);
const soloStats = await databaseService.getSoloStatsByUserId(user.idUser);
const onlineStats = await databaseService.getOnlineStatsByUserId(user.idUser);
await databaseService.updateUserIDStats(user.idUser, soloStats.idSoloStats, onlineStats.idOnlineStats);
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;
// 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 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.deleteSoloStat(user.idUser);
await db.deleteOnlineStat(user.idUser);
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();
}
}
}
module.exports = AuthController;

@ -0,0 +1,146 @@
const DatabaseService = require('../services/DatabaseService');
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;
}
req.session.user.soloStats = await db.getSoloStatsByUserId(req.session.user.idUser);
req.session.user.onlineStats = await db.getOnlineStatsByUserId(req.session.user.idUser);
console.log("[" + hour + ":" + minutes + "] " + req.session.user.pseudo + " have a session.");
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 updateSoloStats(req, res) {
const db = new DatabaseService();
const date = new Date();
const hour = date.getHours();
const minutes = date.getMinutes();
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;
}
const soloStats = await db.getSoloStatsByUserId(user.idUser);
if (!soloStats) {
res.status(200).json({ error: "true", message: 'Solo stats not found' });
return;
}
await db.updateSoloStats(user.idUser, req.body.nbGames, req.body.bestScore, req.body.avgNbTry);
const newSoloStats = await db.getSoloStatsByUserId(user.idUser);
req.session.user.soloStats = newSoloStats;
console.log("[" + hour + ":" + minutes + "] " + req.session.user.pseudo + "'s solot_stats are updated.");
res.status(200).json({ user: req.session.user });
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la mise à jour des statistiques solo.' });
}
finally{
await db.disconnect();
}
}
static async updateOnlineStats(req, res) {
const db = new DatabaseService();
const date = new Date();
const hour = date.getHours();
const minutes = date.getMinutes();
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;
}
const onlineStats = await db.getOnlineStatsByUserId(user.idUser);
if (!onlineStats) {
res.status(200).json({ error: "true", message: 'Online stats not found' });
return;
}
await db.updateOnlineStats(user.idUser, req.body.nbGames, req.body.nbWins, req.body.ratio);
const newOnlineStats = await db.getOnlineStatsByUserId(user.idUser);
req.session.user.onlineStats = newOnlineStats;
console.log("[" + hour + ":" + minutes + "] " + req.session.user.pseudo + "'s online_stats are updated.");
res.status(200).json({ user: req.session.user });
}
catch(error){
console.error(error);
res.status(500).json({ error: 'Erreur lors de la mise à jour des statistiques en ligne.' });
}
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();
}
}
}
module.exports = SessionController;

@ -0,0 +1,18 @@
const express = require('express');
const router = express.Router();
const AuthController = require('../controllers/AuthController');
const SessionController = require('../controllers/SessionController');
// 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)
// Routes pour les sessions
router.get('/session', SessionController.getUserInformation);
router.put('/session/updatePseudo', SessionController.UpdatePseudo);
router.put('/session/updateSoloStats', SessionController.updateSoloStats);
router.put('/session/updateOnlineStats', SessionController.updateOnlineStats);
module.exports = router;

@ -0,0 +1,59 @@
const express = require('express');
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 app = express();
const port = 3003;
// Middleware
app.use(cors(
{
origin: ["http://localhost:3000", "http://172.20.10.4:3000"],
credentials: true
}
)); // Autoriser les requêtes cross-origin
app.use(bodyParser.json()); // Parser le body des requêtes en JSON
// Session
const secret = crypto.randomBytes(32).toString('hex');
app.use(session({
secret: secret,
resave: false,
saveUninitialized: true,
cookie: {
secure: process.env.NODE_ENV === 'production' ? true : false,
maxAge: 60 * 60 * 1000
}
}));
// Middleware pour les routes
app.use('/', authRoutes);
// Middleware de gestion des erreurs
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Erreur interne du serveur!');
});
// Middleware pour la gestion des erreurs 404
app.use((req, res) => {
res.status(404).send('Page non trouvée');
});
// Connexion à la base de données sqlite3 au démarrage du serveur
const databaseService = new DatabaseService();
databaseService.connect()
.then(() => {
// Lancer le serveur
app.listen(port, () => {
console.log(`Serveur lancé sur le port ${port}`);
});
})
.catch((err) => {
console.error(err);
});

@ -0,0 +1,231 @@
const sqlite3 = require('sqlite3');
const path = require('path');
const { rejects } = require('assert');
class DatabaseService {
constructor(){
this.db_name = "socialgraph";
}
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();
}
});
});
}
// 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);
}
});
});
}
// 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 stats solo de l'utilisateur
async getSoloStatsByUserId(userId){
return new Promise((resolve, reject) => {
this.client.get('SELECT * FROM solo_stats WHERE idUser = ?', userId, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
// Récupère stats online de l'utilisateur
async getOnlineStatsByUserId(userId){
return new Promise((resolve, reject) => {
this.client.get('SELECT * FROM online_stats WHERE idUser = ?', userId, (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);
}
});
});
}
// Mettre à jour l'id de stats solo et online de l'utilisateur
async updateUserIDStats(userId, soloStatsId, onlineStatsId){
return new Promise((resolve, reject) => {
this.client.run('UPDATE users SET idSoloStats = ?, idOnlineStats = ? WHERE idUser = ?', [soloStatsId, onlineStatsId, userId], (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
// Mettre à jour les stats solo de l'utilisateur
async updateSoloStats(userId, nbGames, bestScore, avgNbTry){
return new Promise((resolve, reject) => {
this.client.run('UPDATE solo_stats SET nbGames = ?, bestScore = ?, avgNbTry = ? WHERE idUser = ?', [nbGames, bestScore, avgNbTry, userId], (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
// Mettre à jour les stats online de l'utilisateur
async updateOnlineStats(userId, nbGames, nbWins, ratio){
return new Promise((resolve, reject) => {
this.client.run('UPDATE online_stats SET nbGames = ?, nbWins = ?, ratio = ? WHERE idUser = ?', [nbGames, nbWins, ratio, userId], (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async initSoloStats(userId) {
return new Promise((resolve, reject) => {
this.client.run('INSERT INTO solo_stats (nbGames, bestScore, avgNbTry, idUser) VALUES (?, ?, ?, ?)', 0, 0, 0.0, userId, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async initOnlineStats(userId) {
return new Promise((resolve, reject) => {
this.client.run('INSERT INTO online_stats (nbGames, nbWins, ratio, idUser) VALUES (?, ?, ?, ?)', 0, 0, 0.0, userId, (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 deleteSoloStat(userId){
return new Promise((resolve, reject) => {
this.client.run('DELETE FROM solo_stats WHERE idUser=?', userId, (err, result) => {
if(err){
reject(err);
}
else{
resolve(result);
}
});
});
}
async deleteOnlineStat(userId){
return new Promise((resolve, reject) => {
this.client.run('DELETE FROM online_stats 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);
}
});
});
}
}
module.exports = DatabaseService;

@ -0,0 +1,17 @@
const bcrypt = require('bcrypt');
class UserService {
static async createUser(userData) {
// Hacher le mot de passe avant de le stocker dans la base de données
const hashedPassword = await bcrypt.hash(userData.password, 10);
// Retournez l'utilisateur créé
return { pseudo: userData.pseudo, password: hashedPassword };
}
static async initUserStats(idUser) {
return { nbGames: 0, nbWins: 0, ratio: 0.0, idUser: idUser };
}
}
module.exports = UserService;

@ -0,0 +1,113 @@
import VerificationService from './VerificationService';
import {ADRESSE_DBSERVER} from "../AdressSetup"
import User from '../model/User';
class AuthService{
// Méthode pour vérifier les données de connexion
static async validateSignUp(data: any): Promise<{valid: boolean, error: string}> {
return VerificationService.validateSignUpData(data);
}
static async validateSignIn(data: any): Promise<{valid: boolean, error: string}> {
return VerificationService.validateSignInData(data);
}
static async signUp(data: any) {
try {
const response = await fetch(ADRESSE_DBSERVER + '/auth/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
credentials: 'include',
});
if (response.ok) {
const result = await response.json();
return result;
} else {
const errorResponse = await response.json();
throw new Error(errorResponse.error);
}
} catch (error) {
console.error(error);
throw error;
}
}
static async signIn(data: any) {
try {
const response = await fetch(ADRESSE_DBSERVER + '/auth/signin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
credentials: 'include',
});
if (response.ok) {
const result = await response.json();
return result;
} else {
const errorResponse = await response.json();
throw new Error(errorResponse.error);
}
}
catch (error) {
console.error(error);
throw error;
}
}
static async logout() {
try {
const response = await fetch(ADRESSE_DBSERVER + '/auth/logout', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
});
if (response.ok) {
const result = await response.json();
return result;
} else {
const errorResponse = await response.json();
throw new Error(errorResponse.error);
}
} catch (error) {
console.error(error);
throw error;
}
}
static async delAccount(pseudo: string){
try {
const response = await fetch(ADRESSE_DBSERVER + '/auth/delAccount', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ pseudo }),
credentials: 'include',
});
if (response.ok) {
const result = await response.json();
return result;
} else {
const errorResponse = await response.json();
throw new Error(errorResponse.error);
}
} catch (error) {
console.error(error);
throw error;
}
}
}
export default AuthService;

@ -0,0 +1,116 @@
import {ADRESSE_DBSERVER} from "../AdressSetup"
class SessionService {
static async getSession() {
try {
const response = await fetch(ADRESSE_DBSERVER + '/session', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
});
if (response.ok) {
const result = await response.json();
return result;
} else {
const errorResponse = await response.json();
throw new Error(errorResponse.error);
}
} catch (error) {
console.error(error);
throw error;
}
}
static async updateSoloStats(pseudo: string, nbGames: number, bestScore: number, avgNbTry: number){
try {
const response = await fetch(ADRESSE_DBSERVER + '/session/updateSoloStats', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({
pseudo,
nbGames,
bestScore,
avgNbTry
}),
});
if (response.ok) {
const result = await response.json();
return result;
} else {
const errorResponse = await response.json();
throw new Error(errorResponse.error);
}
} catch (error) {
console.error(error);
throw error;
}
}
static async updateOnlineStats(pseudo: string, nbGames: number, nbWins: number, ratio: number){
try {
console.log("updateOnlineStats : ", pseudo, nbGames, nbWins, ratio);
const response = await fetch(ADRESSE_DBSERVER + '/session/updateOnlineStats', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({
pseudo,
nbGames,
nbWins,
ratio
}),
});
if (response.ok) {
const result = await response.json();
return result;
} else {
const errorResponse = await response.json();
throw new Error(errorResponse.error);
}
} catch (error) {
console.error(error);
throw error;
}
}
static async UpdatePseudo(pseudo : string, newPseudo : string) {
console.log("pseudo : " + pseudo + " newpseudo : " + newPseudo)
try {
const response = await fetch(ADRESSE_DBSERVER + '/session/updatePseudo', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pseudo,
newPseudo
}),
credentials: 'include',
});
if (response.ok) {
const result = await response.json();
return result;
} else {
const errorResponse = await response.json();
throw new Error(errorResponse.error);
}
} catch (error) {
console.error(error);
throw error;
}
}
}
export default SessionService;

@ -0,0 +1,31 @@
class ValidationService {
public static validateSignUpData(data: any): {valid: boolean, error: string} {
if(!data.pseudo || !data.password || !data.Cpassword) {
return {valid: false, error: 'Veuillez remplir tous les champs.'};
}
if(data.password.length < 8) {
return {valid: false, error: 'Le mot de passe doit contenir au moins 8 caractères.'};
}
if(data.password !== data.Cpassword) {
return {valid: false, error: 'Les mots de passe ne correspondent pas.'};
}
return {valid: true, error: ''};
}
public static validateSignInData(data: any): {valid: boolean, error: string} {
if(!data.pseudo || !data.password) {
return {valid: false, error: 'Veuillez remplir tous les champs.'};
}
if(data.password.length < 8) {
return {valid: false, error: 'Le mot de passe doit contenir au moins 8 caractères.'};
}
return {valid: true, error: ''};
}
}
export default ValidationService;

@ -0,0 +1,18 @@
export interface PlayerSoloStats {
nbGames: number;
bestScore: number;
avgNbTry: number;
};
export interface PlayerOnlineStats {
nbGames: number;
nbWins: number;
ratio: number;
};
export interface PlayerProps {
profilePicture: string;
pseudo: string;
soloStats: PlayerSoloStats;
onlineStats: PlayerOnlineStats;
};

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save