Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • wilsonba/ia04projet2c
1 result
Show changes
Commits on Source (4)
Showing with 18607 additions and 7 deletions
......@@ -151,7 +151,7 @@ func (spy *SpyAgent) Deliberate() {
for _, coords := range canInteractCop { // share suspects to cop
// si on ne lui a pas parlé récemment, on peut lui partager notre liste de suspects
ag, _ := spy.world.Load(coords)
if _, exists := spy.cooldownCop[ag.(*CopAgent)]; !exists {
if _, exists := spy.cooldownCop[ag.(*CopAgent)]; exists {
spy.decision = ShareSuspects
spy.goalCoords = coords
}
......
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# BABEL REVOLUTION - Simulation IA04
### Présentation
Ce projet comprend la partie visualisation front-end de la simulation BABEL REVOLUTION - IA04. Pour rappel, il s'agit de la simulation d'un scenario dans lequel le Centre de la Monoculture décrète progressivement l'interdiction de certains mots de la langue française. Le scénario est dynamique et implique des rebelles, des policiers et des espions dans un environnement basé sur une grille. La simulation suit l'activité de discussion entre les "personnages" et le nombre de prisonniers au fil du temps, affichant les résultats à travers des graphiques et une visualisation de la grille. Le projet utilise ReactJs ainsi que des composants chartJs afin de gérer les graphiques.
### Fonctionnalités
- **Configuration de la Simulation :**
La simulation est paramétrable :
- Hauteur de la grille
- Largeur de la grille
- Nombre d'agents rebelles
- Nombre d'agents policiers
- Nombre d'agents espions
Après validation, une requête est envoyée au serveur avec tous les paramètres saisis afin de démarrer la simulation.
- **Mises à Jour en Temps Réel :**
- L'application communique avec un serveur backend pour effectuer des mises à jour en temps réel sur l'état de la simulation. Toutes les 750ms (pourrait être adaptée), une requête est envoyée à une API REST afin d'obtenir l'état actuel complet de la simulation.
- **Liste des mots :**
- La liste des mots interdits évoluant continuellement, elle fait également partie de l'état de la simulation et la liste est visible et actualisée à l'écran.
- **Visualisation de la Grille :**
- La grille visualise l'état actuel de la simulation avec la position des rebelles, policiers et espions.
- **Représentation Graphique :**
- Deux graphiques représentent visuellement l'évolution de l'activité de communication entre les personnages et du nombre de prisonniers au fil du temps.
### Composants
- **SimulationGrid :**
- Rend la visualisation de la grille de la simulation, affichant les agents sous forme d'emojis en fonction de leur type (Rebelles, Policiers, Espions).
- **SimulationWords :**
- Affiche un tableau de mots interdits utilisés dans la simulation.
- **App :**
- Le composant principal de l'application, il gère la configuration/lancement de la simulation, les mises à jour en temps réel et la visualisation.
### Démarrage du projet
- NodeJS doit être installé sur le système.
- `npm install` installe correctement les dépendances du projet
- `npm start` permet de démarrer le projet sur le port par défaut.
### Contributeurs
- Cette application a été développée par : Balthazar JARRY-WILSON, Edouard BLANC, Gabrielle VAN DE VIJVER, Benoit CHEVILLON
\ No newline at end of file
This diff is collapsed.
{
"name": "ia04_front",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
front/public/favicon.ico

3.78 KiB

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
front/public/logo192.png

5.22 KiB

front/public/logo512.png

9.44 KiB

{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
.App {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
height: 100vh;
margin: 0;
background-color: #e9dada;
font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
}
h1 {
margin-top: 0;
text-align: center;
}
.charts {
display: flex;
flex-direction: row;
width: 100%;
align-items: center;
justify-content: space-around;
}
.charts canvas {
background-color: white;
border-radius: 5px;
flex: 1;
max-width: 48%;
height: 200px;
}
.simulationGrid {
margin: 0; /* Ajouter une marge autour de la grille (à ajuster selon vos besoins) */
display: inline-block; /* Pour que la largeur de la grille s'ajuste à son contenu */
border: 2px solid black;
border-radius: 5px;
}
table {
width: 100%;
border-collapse: collapse; /* Fusionner les bordures des cellules */
}
td {
border: 2px solid #aca6a6; /* Ajouter une bordure de 1 pixel solide avec une couleur grise */
padding: 0; /* Supprimer l'espacement interne pour que la bordure définisse la taille totale */
width: 28px; /* Largeur de chaque cellule (à ajuster selon vos besoins) */
height: 28px; /* Hauteur de chaque cellule (à ajuster selon vos besoins) */
text-align: center; /* Centrer le texte dans les cellules */
font-size: 1.26em;
}
td:empty:not(.cop, .speak) {
background-color: #f0f0f0; /* Ajouter une couleur de fond pour les cellules vides */
}
.cop {
background-color: rgb(188, 33, 33);
}
.speak {
background-color: rgb(255, 152, 34);
}
.simulation {
display: flex;
flex-direction: row;
justify-content: space-around;
}
.words table {
margin: 0;
display: inline-block;
border: 2px solid rgb(85, 85, 85);
height: 350px;
width: max-content;
overflow: scroll;
padding: 5px;
}
.words tr {
padding: 0;
text-align: center;
font-size: 1em;
}
.words thead tr {
background-color: #aca6a6;
border: 1px solid gray;
}
.words {
width: 500px;
display: flex;
align-items: center;
justify-content: center;
}
/* Ajout de styles pour le formulaire */
.config-form {
width: 300px;
padding: 20px;
border: 2px solid #aca6a6;
border-radius: 5px;
background-color: white;
margin-top: 20px;
}
.config-form label {
display: block;
margin-bottom: 10px;
}
.config-form input {
width: 100%;
padding: 10px;
margin-bottom: 15px;
box-sizing: border-box;
}
.config-form input[type="submit"] {
background-color: #4caf50;
color: white;
cursor: pointer;
}
.config-form {
width: 300px;
padding: 20px;
border: 2px solid #aca6a6;
border-radius: 5px;
background-color: white;
margin-top: 20px;
margin-left: auto;
margin-right: auto;
}
import './App.css';
import { useEffect, useState } from 'react';
import { Line } from 'react-chartjs-2';
import SimulationGrid from './components/SimulationGrid';
import SimulationWords from './components/SimulationWords';
import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend } from 'chart.js';
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);
function App() {
let simulationInterval = null;
const [running, setRunning] = useState(false);
// Paramètres par défaut
const [formData, setFormData] = useState({
height: 15,
width: 15,
nbRebels: 50,
nbCops: 8,
nbSpies: 5,
maxStep: -1,
maxDuration: 600,
});
// On initialise l'instant 0 de la simulation avec 0 mots prononcés et 0 prisonniers
const [nbWords, setNbWords] = useState({id: 0, nb: 0});
const [nbPrisoners, setNbPrisoners] = useState({id: 0, nb: 0});
// Par défaut, les graphiques sont vides
const [dataLanguage, setDataLanguage] = useState({
labels: [],
datasets: [
{
label: 'Nombre de mots en usage',
data: [],
fill: false,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1,
},
],
});
const [dataPrisoners, setDataPrisoners] = useState({
labels: [],
datasets: [
{
label: 'Nombre de prisonniers',
data: [],
fill: false,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1,
},
],
});
const [dataGrid, setDataGrid] = useState([]);
const [dataWords, setDataWords] = useState([]);
// Options des graphiques
const optionsLanguage = {
maintainAspectRatio: false,
responsive: true,
plugins: {
legend: {
position: 'bottom',
},
title: {
display: true,
text: 'Communication',
},
},
};
const optionsPrisoners = {
maintainAspectRatio: false,
responsive: true,
plugins: {
legend: {
position: 'bottom',
},
title: {
display: true,
text: 'Prison',
},
},
};
const handleInputChange = (e) => { // Gestion des saisis dans le formulaire
const numericValue = parseFloat(e.target.value);
setFormData({ ...formData, [e.target.name]: numericValue });
};
const handleSubmit = (e) => { // Validation du formulaire
e.preventDefault();
fetch('http://localhost:8080/new_simu', { // Envoi des paramètres de la simulation
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
})
.then((response) => {
if (response.status === 200) {
console.log('Simulation lancée');
setRunning(true);
}
})
.catch((error) => {
console.error('Erreur :', error);
});
};
// Si une simulation est déjà en cours d'execution, on la démarre dans une grille 30x30
useEffect(() => {
fetch('http://localhost:8080/update')
.then((response) => {
if (response.status === 200) {
setFormData({...formData, height: 30, width: 30});
setRunning(true);
} else {
console.log('Aucune simulation en cours');
}
});
}, []);
// Actualisation toutes les 750ms, le délai pourrait être adapté
useEffect(() => {
if (running) {
simulationInterval = setInterval(() => {
fetch('http://localhost:8080/update')
.then((response) => {
if (response.status === 200) {
response = response.json()
.then((response) => {
setDataWords(response.forbiddenWords);
setDataGrid({
world: response.world,
width: formData.width,
height: formData.height
});
setNbPrisoners({id: nbPrisoners.id++, nb: response.prison.length});
setNbWords({id: nbWords.id++, nb: response.nb_mot_second});
});
} else {
clearInterval(simulationInterval);
setRunning(false);
console.log('Aucune simulation en cours');
}
})
}, 750);
}
else
clearInterval(simulationInterval);
}, [running]);
// Actualisation des graphiques lorsque les données changent
useEffect(() => {
const newLanguage = {
labels: [...dataLanguage.labels, nbWords.id],
datasets: [
{
label: 'Nombre de mots prononcés',
data: [...dataLanguage.datasets[0].data, nbWords.nb],
fill: false,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1,
},
],
};
setDataLanguage(newLanguage);
}, [nbWords]);
useEffect(() => {
const newLanguage = {
labels: [...dataPrisoners.labels, nbPrisoners.id],
datasets: [
{
label: 'Nombre de prisonniers',
data: [...dataPrisoners.datasets[0].data, nbPrisoners.nb],
fill: false,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1,
},
],
};
setDataPrisoners(newLanguage);
}, [nbPrisoners]);
if (!running) {
return (
<div className="launch">
<h1>Configuration de la Simulation</h1>
<form className="config-form" onSubmit={handleSubmit}>
<label htmlFor="height">Height:</label>
<input type="number" id="height" name="height" value={formData.height} onChange={handleInputChange} required min="0" /><br />
<label htmlFor="width">Width:</label>
<input type="number" id="width" name="width" value={formData.width} onChange={handleInputChange} required min="0" /><br />
<label htmlFor="nbRebels">Number of Rebels:</label>
<input type="number" id="nbRebels" name="nbRebels" value={formData.nbRebels} onChange={handleInputChange} required min="0" /><br />
<label htmlFor="nbCops">Number of Cops:</label>
<input type="number" id="nbCops" name="nbCops" value={formData.nbCops} onChange={handleInputChange} required min="0" /><br />
<label htmlFor="nbSpies">Number of Spies:</label>
<input type="number" id="nbSpies" name="nbSpies" value={formData.nbSpies} onChange={handleInputChange} required min="0" /><br />
<label htmlFor="maxStep">Max Step:</label>
<input type="number" id="maxStep" name="maxStep" value={formData.maxStep} onChange={handleInputChange} required min="-1" /><br />
<label htmlFor="maxDuration">Max Duration:</label>
<input type="number" id="maxDuration" name="maxDuration" value={formData.maxDuration} onChange={handleInputChange} required min="0" /><br />
<input type="submit" value="Start Simulation" />
</form>
</div>
);
} else {
return (
<div className="App">
<h1>Simulation BABEL REVOLUTION - IA04</h1>
<div className="simulation">
<div className="words">
<SimulationWords data={dataWords} />
</div>
<div>
<SimulationGrid data={dataGrid} />
</div>
<div style={{ width: '500px' }}></div>
</div>
<div className="charts">
<Line options={optionsLanguage} data={dataLanguage} />
<Line options={optionsPrisoners} data={dataPrisoners} />
</div>
</div>
);
}
}
export default App;
\ No newline at end of file
import React from 'react';
function SimulationGrid(props) {
const generateGrid = () => {
if (!props.data || !props.data.world) return null; // Si aucune donnée, aucun affichage
const world = props.data.world;
const rows = [];
// Ajout des agents dans la grille
for (let i = 0; i < props.data.height; i++) {
const cells = [];
for (let j = 0; j < props.data.width; j++) {
const coordinates = `${i},${j}`;
let cellContent = world[coordinates] || '';
let classCell = "";
if (cellContent.startsWith('RbAgt')) {
cellContent = '👨‍🎤';
} else if (cellContent.startsWith('cop')) {
cellContent = '👮';
classCell = "cop" // Classe spécifique pour couleur spécifique
} else if (cellContent.startsWith('SpyAgt')) {
cellContent = '🕵️';
}
cells.push(<td key={coordinates} className={classCell}>{cellContent}</td>);
}
rows.push(<tr key={i}>{cells}</tr>);
}
return (
<div className="simulationGrid">
<table>
<tbody>{rows}</tbody>
</table>
</div>
);
};
return generateGrid();
}
export default SimulationGrid;
import { useEffect, useState } from 'react';
function SimulationWords(props) {
const [wordsTable, setWordsTable] = useState([]);
useEffect(() => {
if (props.data === undefined) return; // Si aucune donnée, aucun affichage
const table = props.data.map((word, index) => ( // Pour chaque mot, on ajoute une ligne
<tr key={index}>
<td>{word}</td>
</tr>
));
setWordsTable(table);
}, [props.data]);
return (
<table>
<thead>
<tr>
<td style={{ fontSize: "1.2em", width: "max-content" }}>Mots interdits</td>
</tr>
</thead>
<tbody>{wordsTable}</tbody>
</table>
);
}
export default SimulationWords;
body {
margin: 0;
}
\ No newline at end of file
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
\ No newline at end of file
......@@ -227,12 +227,22 @@ func (simu *Simulation) Run() {
for _, agent := range simu.Agents {
go func(agent agt.Agent) {
step := 0
for step < simu.maxStep {
step++
c, _ := simu.syncChans.Load(agent.ID())
c.(chan int) <- step
time.Sleep(500 * time.Millisecond)
<-c.(chan int)
if simu.maxStep < 0 {
for {
step++
c, _ := simu.syncChans.Load(agent.ID())
c.(chan int) <- step
time.Sleep(500 * time.Millisecond)
<-c.(chan int)
}
} else {
for step < simu.maxStep {
step++
c, _ := simu.syncChans.Load(agent.ID())
c.(chan int) <- step
time.Sleep(500 * time.Millisecond)
<-c.(chan int)
}
}
}(agent)
}
......