Corrections + admin full screen

This commit is contained in:
Sebastien Riviere
2025-09-18 01:27:44 +02:00
parent 0f64fc59f9
commit a2c4b5c540
24 changed files with 201 additions and 135 deletions

View File

@@ -38,7 +38,7 @@
- [x] Refaire les flèches de chasse sur la map - [x] Refaire les flèches de chasse sur la map
- [x] Pouvoir définir la zone de départ de chaque équipe - [x] Pouvoir définir la zone de départ de chaque équipe
- [x] Nommer les polygons par des lettres de l'alphabet - [x] Nommer les polygons par des lettres de l'alphabet
- [ ] Plein écran - [x] Plein écran
- [ ] Pouvoir faire pause dans la partie - [ ] Pouvoir faire pause dans la partie
- [ ] Mettre en évidence le menu paramètre - [ ] Mettre en évidence le menu paramètre
- [ ] Afficher un feedback quand un paramètre est sauvegardé - [ ] Afficher un feedback quand un paramètre est sauvegardé

View File

@@ -58,7 +58,7 @@ export default function Display() {
// Activating geolocation tracking // Activating geolocation tracking
useEffect(() => { useEffect(() => {
if (loggedIn && (gameState == GameState.SETUP || gameState == GameState.PLAYING || gameState == GameState.PLACEMENT) && !captured) { if (loggedIn) {
startLocationTracking(); startLocationTracking();
} else { } else {
stopLocationTracking(); stopLocationTracking();
@@ -89,8 +89,9 @@ export default function Display() {
// Update the average speed // Update the average speed
useEffect(() => { useEffect(() => {
const time = finishDate ? (finishDate - startDate) : timeSinceStart; const hours = (finishDate ? (finishDate - startDate) : timeSinceStart*1000) / 1000 / 3600;
setAvgSpeed(distance/time); const km = distance / 1000;
setAvgSpeed(Math.floor(km / hours * 10) / 10);
}, [distance, finishDate, timeSinceStart]); }, [distance, finishDate, timeSinceStart]);
function toggleCollapsible() { function toggleCollapsible() {
@@ -172,11 +173,11 @@ export default function Display() {
const GameLog = () => { const GameLog = () => {
return ( return (
<TouchableOpacity style={{width:"100%"}}> <TouchableOpacity style={{width:"100%"}}>
{ gameState == GameState.SETUP && <Text style={styles.gameState}>Préparation de la partie</Text>} { gameState == GameState.SETUP && <Text style={styles.gameState}>{messages?.waiting || "Préparation de la partie"}</Text>}
{ gameState == GameState.PLACEMENT && <Text style={styles.gameState}>Phase de placement</Text>} { gameState == GameState.PLACEMENT && <Text style={styles.gameState}>Phase de placement</Text>}
{ gameState == GameState.PLAYING && !outOfZone && <Text style={styles.gameState}>La partie est en cours</Text>} { gameState == GameState.PLAYING && !outOfZone && <Text style={styles.gameState}>La partie est en cours</Text>}
{ gameState == GameState.PLAYING && outOfZone && !hasHandicap && <Text style={styles.gameStateOutOfZone}>Hors zone (handicap dans {formatTimeMinutes(-timeLeftOutOfZone)})</Text>} { gameState == GameState.PLAYING && outOfZone && !hasHandicap && <Text style={styles.gameStateOutOfZone}>{`Veuillez retourner dans la zone\nHandicap dans ${formatTimeMinutes(-timeLeftOutOfZone)}`}</Text>}
{ gameState == GameState.PLAYING && hasHandicap && <Text style={styles.gameStateOutOfZone}>Hors zone (position révélée en continue)</Text>} { gameState == GameState.PLAYING && hasHandicap && <Text style={styles.gameStateOutOfZone}>{`Veuillez retourner dans la zone\nVotre position est révélée en continue`}</Text>}
{ gameState == GameState.FINISHED && <Text style={styles.gameState}>La partie est terminée</Text>} { gameState == GameState.FINISHED && <Text style={styles.gameState}>La partie est terminée</Text>}
</TouchableOpacity> </TouchableOpacity>
); );
@@ -243,44 +244,34 @@ export default function Display() {
const Zones = () => { const Zones = () => {
const latToLatitude = (pos) => ({latitude: pos.lat, longitude: pos.lng}); const latToLatitude = (pos) => ({latitude: pos.lat, longitude: pos.lng});
switch (zoneType) {
case zoneTypes.circle:
return ( return (
<Fragment> <Fragment>
{ zoneExtremities.begin && <Circle center={latToLatitude(zoneExtremities.begin.center)} radius={zoneExtremities.begin.radius} strokeColor="red" fillColor="rgba(255,0,0,0.1)" strokeWidth={2} />} { zoneType == zoneTypes.circle && zoneExtremities.begin && <Circle center={latToLatitude(zoneExtremities.begin.center)} radius={zoneExtremities.begin.radius} strokeColor="red" fillColor="rgba(255,0,0,0.1)" strokeWidth={2} />}
{ zoneExtremities.end && <Circle center={latToLatitude(zoneExtremities.end.center)} radius={zoneExtremities.end.radius} strokeColor="green" fillColor="rgba(0,255,0,0.1)" strokeWidth={2} />} { zoneType == zoneTypes.circle && zoneExtremities.end && <Circle center={latToLatitude(zoneExtremities.end.center)} radius={zoneExtremities.end.radius} strokeColor="green" fillColor="rgba(0,255,0,0.1)" strokeWidth={2} />}
{ zoneType == zoneTypes.polygon && zoneExtremities.begin && <Polygon coordinates={zoneExtremities.begin.polygon.map(pos => latToLatitude(pos))} strokeColor="red" fillColor="rgba(255,0,0,0.1)" strokeWidth={2} /> }
{ zoneType == zoneTypes.polygon && zoneExtremities.end && <Polygon coordinates={zoneExtremities.end.polygon.map(pos => latToLatitude(pos))} strokeColor="green" fillColor="rgba(0,255,0,0.1)" strokeWidth={2} /> }
</Fragment> </Fragment>
); );
case zoneTypes.polygon:
return (
<Fragment>
{ zoneExtremities.begin && <Polygon coordinates={zoneExtremities.begin.polygon.map(pos => latToLatitude(pos))} strokeColor="red" fillColor="rgba(255,0,0,0.1)" strokeWidth={2} /> }
{ zoneExtremities.end && <Polygon coordinates={zoneExtremities.end.polygon.map(pos => latToLatitude(pos))} strokeColor="green" fillColor="rgba(0,255,0,0.1)" strokeWidth={2} /> }
</Fragment>
);
default:
return null;
}
} }
const Map = () => { const Map = () => {
return ( return (
<MapView ref={mapRef} style={{flex: 1}} initialRegion={initialRegion} mapType="standard" onTouchMove={() => setCenterMap(false)}> <MapView ref={mapRef} style={{flex: 1}} initialRegion={initialRegion} mapType="standard" onTouchMove={() => setCenterMap(false)} toolbarEnabled={false}>
{ gameState == GameState.PLACEMENT && startingArea && circle("0, 0, 255", startingArea)} { gameState == GameState.PLACEMENT && startingArea && circle("0, 0, 255", startingArea)}
{ gameState == GameState.PLAYING && zoneExtremities && <Zones/>} { gameState == GameState.PLAYING && zoneExtremities && <Zones/>}
{ location && { location &&
<Marker coordinate={{ latitude: location[0], longitude: location[1] }} anchor={{ x: 0.33, y: 0.33 }}> <Marker coordinate={{ latitude: location[0], longitude: location[1] }} anchor={{ x: 0.33, y: 0.33 }} onPress={() => Alert.alert("Position actuelle", "Ceci est votre position")}>
<Image source={require("../assets/images/marker/blue.png")} style={{width: 24, height: 24}} resizeMode="contain"/> <Image source={require("../assets/images/marker/blue.png")} style={{width: 24, height: 24}} resizeMode="contain"/>
</Marker> </Marker>
} }
{ gameState == GameState.PLAYING && lastSentLocation && !hasHandicap && { gameState == GameState.PLAYING && lastSentLocation && !hasHandicap &&
<Marker coordinate={{ latitude: lastSentLocation[0], longitude: lastSentLocation[1] }} anchor={{ x: 0.33, y: 0.33 }}> <Marker coordinate={{ latitude: lastSentLocation[0], longitude: lastSentLocation[1] }} anchor={{ x: 0.33, y: 0.33 }} onPress={() => Alert.alert("Position envoyée", "Ceci est votre dernière position connue par le serveur")}>
<Image source={require("../assets/images/marker/grey.png")} style={{width: 24, height: 24}} resizeMode="contain"/> <Image source={require("../assets/images/marker/grey.png")} style={{width: 24, height: 24}} resizeMode="contain"/>
</Marker> </Marker>
} }
{ gameState == GameState.PLAYING && enemyLocation && !hasHandicap && { gameState == GameState.PLAYING && enemyLocation && !hasHandicap &&
<Marker coordinate={{ latitude: enemyLocation[0], longitude: enemyLocation[1] }} anchor={{ x: 0.33, y: 0.33 }}> <Marker coordinate={{ latitude: enemyLocation[0], longitude: enemyLocation[1] }} anchor={{ x: 0.33, y: 0.33 }}>
<Image source={require("../assets/images/marker/red.png")} style={{width: 24, height: 24}} resizeMode="contain"/> <Image source={require("../assets/images/marker/red.png")} style={{width: 24, height: 24}} resizeMode="contain" onPress={() => Alert.alert("Position ennemie", "Ceci est la dernière position de vos ennemis connue")}/>
</Marker> </Marker>
} }
</MapView> </MapView>
@@ -329,7 +320,6 @@ export default function Display() {
return ( return (
<View style={styles.imageContainer}> <View style={styles.imageContainer}>
{<Text style={{fontSize: 15, margin: 5}}>{"Cible (" + (enemyName ?? "Indisponible") + ")"}</Text>} {<Text style={{fontSize: 15, margin: 5}}>{"Cible (" + (enemyName ?? "Indisponible") + ")"}</Text>}
{enemyHasHandicap && <Text style={{fontSize: 15, margin: 5}}>Position ennemie révélée en continue</Text>}
{<CustomImage source={{ uri : enemyImageURI }} canZoom/>} {<CustomImage source={{ uri : enemyImageURI }} canZoom/>}
</View> </View>
); );
@@ -357,9 +347,9 @@ export default function Display() {
return ( return (
<View style={{gap: 15, width: "100%", marginVertical: 15}}> <View style={{gap: 15, width: "100%", marginVertical: 15}}>
<View style={{flexDirection: "row", justifyContent: "space-around"}}> <View style={{flexDirection: "row", justifyContent: "space-around"}}>
<Stat source={require('../assets/images/distance.png')} description={"Distance parcourue"}>{(distance / 1000).toFixed(1)}km</Stat> <Stat source={require('../assets/images/distance.png')} description={"Distance parcourue"}>{Math.floor(distance / 100) / 10}km</Stat>
<Stat source={require('../assets/images/time.png')} description={"Temps écoulé au format HH:MM:SS"}>{formatTimeHours((finishDate ? Math.floor((finishDate - startDate) / 1000) : timeSinceStart))}</Stat> <Stat source={require('../assets/images/time.png')} description={"Temps écoulé au format HH:MM:SS"}>{formatTimeHours((finishDate ? Math.floor((finishDate - startDate) / 1000) : timeSinceStart))}</Stat>
<Stat source={require('../assets/images/running.png')} description={"Vitesse moyenne"}>{(avgSpeed*3.6).toFixed(1)}km/h</Stat> <Stat source={require('../assets/images/running.png')} description={"Vitesse moyenne"}>{avgSpeed}km/h</Stat>
</View> </View>
<View style={{flexDirection: "row", justifyContent: "space-around"}}> <View style={{flexDirection: "row", justifyContent: "space-around"}}>
<Stat source={require('../assets/images/target/black.png')} description={"Nombre total de captures par votre équipe"}>{nCaptures}</Stat> <Stat source={require('../assets/images/target/black.png')} description={"Nombre total de captures par votre équipe"}>{nCaptures}</Stat>
@@ -394,6 +384,7 @@ export default function Display() {
{ gameState == GameState.FINISHED && { gameState == GameState.FINISHED &&
EndGameMessage() EndGameMessage()
} }
{enemyHasHandicap && <Text style={{fontSize: 18, marginTop: 6, fontWeight: "bold"}}>Position ennemie révélée en continue !</Text>}
</View> </View>
<View style={styles.bottomContainer} onLayout={(event) => {setBottomContainerHeight(event.nativeEvent.layout.height)}}> <View style={styles.bottomContainer} onLayout={(event) => {setBottomContainerHeight(event.nativeEvent.layout.height)}}>
<View style={styles.mapContainer}> <View style={styles.mapContainer}>
@@ -411,15 +402,15 @@ export default function Display() {
</View> </View>
} }
</View> </View>
{ gameState == GameState.PLAYING && !captured && { (gameState == GameState.PLAYING || gameState == GameState.FINISHED) &&
<View style={styles.outerDrawerContainer}> <View style={styles.outerDrawerContainer}>
<LinearGradient colors={['rgba(0,0,0,0)', 'rgba(0,0,0,0.5)']} style={{height: 70, width: "100%", position: "absolute", top: -30}}/> <LinearGradient colors={['rgba(0,0,0,0)', 'rgba(0,0,0,0.5)']} style={{height: 70, width: "100%", position: "absolute", top: -30}}/>
<View style={styles.innerDrawerContainer}> <View style={styles.innerDrawerContainer}>
{ CollapsibleButton() } { CollapsibleButton() }
<Collapsible style={[styles.collapsibleWindow, {height: bottomContainerHeight - 44}]} title="Collapse" collapsed={collapsibleState}> <Collapsible style={[styles.collapsibleWindow, {height: bottomContainerHeight - 44}]} title="Collapse" collapsed={collapsibleState}>
<ScrollView contentContainerStyle={styles.collapsibleContent}> <ScrollView contentContainerStyle={styles.collapsibleContent}>
{ TeamCaptureCode() } { gameState == GameState.PLAYING && TeamCaptureCode() }
{ !hasHandicap && <Fragment> { gameState == GameState.PLAYING && !hasHandicap && <Fragment>
{ ChasedTeamImage() } { ChasedTeamImage() }
<View style={styles.actionsContainer}> <View style={styles.actionsContainer}>
{ CaptureCode() } { CaptureCode() }
@@ -468,7 +459,7 @@ const styles = StyleSheet.create({
borderRadius: 10, borderRadius: 10,
width: "100%", width: "100%",
backgroundColor: 'white', backgroundColor: 'white',
fontSize: 20, fontSize: 18,
padding: 10, padding: 10,
}, },
gameStateOutOfZone: { gameStateOutOfZone: {
@@ -476,7 +467,7 @@ const styles = StyleSheet.create({
borderRadius: 10, borderRadius: 10,
width: "100%", width: "100%",
backgroundColor: 'white', backgroundColor: 'white',
fontSize: 20, fontSize: 18,
padding: 10, padding: 10,
borderColor: 'red' borderColor: 'red'
}, },

View File

@@ -1,7 +1,7 @@
import { createContext, useContext, useMemo } from "react"; import { createContext, useContext, useMemo } from "react";
import { io } from "socket.io-client"; import { io } from "socket.io-client";
const HOST = '192.168.15.129'; // IP of the machine hosting the server const HOST = 'traque.rezel.net'; // IP of the machine hosting the server
const SOCKET_URL = (HOST == "traque.rezel.net" ? "wss://" : "ws://") + HOST + "/player"; const SOCKET_URL = (HOST == "traque.rezel.net" ? "wss://" : "ws://") + HOST + "/player";
const SERVER_URL = (HOST == "traque.rezel.net" ? "https://" : "http://") + HOST + "/back"; const SERVER_URL = (HOST == "traque.rezel.net" ? "https://" : "http://") + HOST + "/back";

View File

@@ -8,11 +8,6 @@ import { useTeamConnexion } from "./teamConnexionContext";
const teamContext = createContext(); const teamContext = createContext();
const zoneTypes = {
circle: "circle",
polygon: "polygon"
}
function TeamProvider({children}) { function TeamProvider({children}) {
const {teamSocket} = useSocket(); const {teamSocket} = useSocket();
const [location, getLocationAuthorization, startLocationTracking, stopLocationTracking] = useLocation(5000, 10); const [location, getLocationAuthorization, startLocationTracking, stopLocationTracking] = useLocation(5000, 10);

View File

@@ -50,14 +50,15 @@ export function initAdminSocketHandler() {
socket.emit("teams", game.teams); socket.emit("teams", game.teams);
socket.emit("game_state", { socket.emit("game_state", {
state: game.state, state: game.state,
date: game.stateDate date: game.startDate
}); });
socket.emit("current_zone", { socket.emit("current_zone", {
begin: zoneManager.getCurrentZone(), begin: zoneManager.getCurrentZone(),
end: zoneManager.getNextZone(), end: zoneManager.getNextZone(),
endDate: zoneManager.currentZone.endDate, endDate: zoneManager.currentZone?.endDate,
}); });
socket.emit("settings", game.getAdminSettings()); socket.emit("settings", game.getAdminSettings());
socket.emit("login_response", true);
}); });
socket.on("add_team", (teamName) => { socket.on("add_team", (teamName) => {
@@ -77,7 +78,7 @@ export function initAdminSocketHandler() {
socket.on("capture_team", (teamId) => { socket.on("capture_team", (teamId) => {
if (!loggedIn) return; if (!loggedIn) return;
game.captureTeam(teamId); game.switchCapturedTeam(teamId);
}); });
socket.on("placement_team", (teamId, placementZone) => { socket.on("placement_team", (teamId, placementZone) => {

View File

@@ -40,7 +40,7 @@ export default {
// Current state of the game // Current state of the game
state: GameState.SETUP, state: GameState.SETUP,
// Date since the state changed // Date since the state changed
stateDate: Date.now(), startDate: null,
// Messages // Messages
messages: { messages: {
waiting: "", waiting: "",
@@ -84,7 +84,7 @@ export default {
} }
// Update of enemyLocation now we have the lastSentLocation of the enemy // Update of enemyLocation now we have the lastSentLocation of the enemy
for (const team of this.teams) { for (const team of this.teams) {
team.enemyLocation = this.getTeam(team.chasing).lastSentLocation; team.enemyLocation = this.getTeam(team.chasing)?.lastSentLocation;
sendUpdatedTeamInformations(team.id); sendUpdatedTeamInformations(team.id);
} }
// Broadcast new infos // Broadcast new infos
@@ -153,6 +153,7 @@ export default {
setState(newState) { setState(newState) {
const dateNow = Date.now(); const dateNow = Date.now();
if (newState == this.state) return true;
switch (newState) { switch (newState) {
case GameState.SETUP: case GameState.SETUP:
trajectory.stop(); trajectory.stop();
@@ -160,29 +161,32 @@ export default {
sendPositionTimeouts.clearAll(); sendPositionTimeouts.clearAll();
outOfZoneTimeouts.clearAll(); outOfZoneTimeouts.clearAll();
this.resetTeamsInfos(); this.resetTeamsInfos();
this.startDate = null;
break; break;
case GameState.PLACEMENT: case GameState.PLACEMENT:
if (this.teams.length < 3) { if (this.state == GameState.FINISHED || this.teams.length < 3) {
secureAdminBroadcast("game_state", {state: this.state, date: this.stateDate}); secureAdminBroadcast("game_state", {state: this.state, date: this.startDate});
return false; return false;
} }
trajectory.stop(); trajectory.stop();
zoneManager.stop(); zoneManager.stop();
sendPositionTimeouts.clearAll(); sendPositionTimeouts.clearAll();
outOfZoneTimeouts.clearAll(); outOfZoneTimeouts.clearAll();
this.startDate = null;
break; break;
case GameState.PLAYING: case GameState.PLAYING:
if (this.teams.length < 3) { if (this.state == GameState.FINISHED || this.teams.length < 3) {
secureAdminBroadcast("game_state", {state: this.state, date: this.stateDate}); secureAdminBroadcast("game_state", {state: this.state, date: this.startDate});
return false; return false;
} }
trajectory.start(); trajectory.start();
zoneManager.start(); zoneManager.start();
this.initLastSentLocations(); this.initLastSentLocations();
this.startDate = dateNow;
break; break;
case GameState.FINISHED: case GameState.FINISHED:
if (this.state != GameState.PLAYING) { if (this.state != GameState.PLAYING) {
secureAdminBroadcast("game_state", {state: this.state, date: this.stateDate}); secureAdminBroadcast("game_state", {state: this.state, date: this.startDate});
return false; return false;
} }
trajectory.stop(); trajectory.stop();
@@ -195,10 +199,9 @@ export default {
} }
// Update the state // Update the state
this.state = newState; this.state = newState;
this.stateDate = dateNow;
// Broadcast new infos // Broadcast new infos
secureAdminBroadcast("game_state", {state: newState, date: this.stateDate}); secureAdminBroadcast("game_state", {state: newState, date: this.startDate});
playersBroadcast("game_state", {state: newState, date: this.stateDate}); playersBroadcast("game_state", {state: newState, date: this.startDate});
return true; return true;
}, },
@@ -257,7 +260,7 @@ export default {
// Identification // Identification
sockets: [], sockets: [],
name: teamName, name: teamName,
id: this.getNewTeamId(this.teams), id: this.getNewTeamId(),
captureCode: randint(10_000), captureCode: randint(10_000),
// Chasing // Chasing
captured: false, captured: false,
@@ -319,19 +322,28 @@ export default {
return true; return true;
}, },
captureTeam(teamId) { switchCapturedTeam(teamId) {
// Test of parameters // Test of parameters
if (!this.hasTeam(teamId)) return false; if (!this.hasTeam(teamId)) return false;
// Variables // Variables
const team = this.getTeam(teamId); const team = this.getTeam(teamId);
const dateNow = Date.now(); const dateNow = Date.now();
// Make the capture // Switch team.captured
if (this.state != GameState.PLAYING) return false;
if (team.captured) {
team.captured = false;
team.finishDate = null;
team.lastSentLocation = team.currentLocation;
team.locationSendDeadline = dateNow + sendPositionTimeouts.delay * 60 * 1000;
sendPositionTimeouts.set(team.id);
} else {
team.captured = true; team.captured = true;
team.finishDate = dateNow; team.finishDate = dateNow;
team.chasing = null; team.chasing = null;
team.chased = null; team.chased = null;
sendPositionTimeouts.clear(team.id); sendPositionTimeouts.clear(team.id);
outOfZoneTimeouts.clear(team.id); outOfZoneTimeouts.clear(team.id);
}
this.updateChasingChain(); this.updateChasingChain();
this.checkEndGame(); this.checkEndGame();
// Broadcast new infos // Broadcast new infos
@@ -384,21 +396,24 @@ export default {
updateLocation(teamId, location) { updateLocation(teamId, location) {
// Test of parameters // Test of parameters
if (!this.hasTeam(teamId)) return false; if (!this.hasTeam(teamId)) return false;
if (!this.hasTeam(this.getTeam(teamId).chasing)) return false;
if (!location) return false; if (!location) return false;
// Variables // Variables
const team = this.getTeam(teamId); const team = this.getTeam(teamId);
const enemyTeam = this.getTeam(team.chasing); const enemyTeam = this.getTeam(team.chasing);
const dateNow = Date.now(); const dateNow = Date.now();
// Update distance // Update distance
if (team.currentLocation) team.distance += Math.floor(getDistanceFromLatLon({lat: location[0], lng: location[1]}, {lat: team.currentLocation[0], lng: team.currentLocation[1]})); if (this.state == GameState.PLAYING && team.currentLocation) {
team.distance += Math.floor(getDistanceFromLatLon({lat: location[0], lng: location[1]}, {lat: team.currentLocation[0], lng: team.currentLocation[1]}));
}
// Update of currentLocation // Update of currentLocation
team.currentLocation = location; team.currentLocation = location;
team.lastCurrentLocationDate = dateNow; team.lastCurrentLocationDate = dateNow;
if (team.hasHandicap) { if (this.state == GameState.PLAYING && team.hasHandicap) {
team.lastSentLocation = team.currentLocation; team.lastSentLocation = team.currentLocation;
} }
// Update of enemyLocation // Update of enemyLocation
if (enemyTeam.hasHandicap) { if (this.state == GameState.PLAYING && enemyTeam.hasHandicap) {
team.enemyLocation = enemyTeam.currentLocation; team.enemyLocation = enemyTeam.currentLocation;
} }
// Update of ready // Update of ready
@@ -406,6 +421,7 @@ export default {
team.ready = isInCircle({ lat: location[0], lng: location[1] }, team.startingArea.center, team.startingArea.radius); team.ready = isInCircle({ lat: location[0], lng: location[1] }, team.startingArea.center, team.startingArea.radius);
} }
// Update out of zone // Update out of zone
if (this.state == GameState.PLAYING) {
const teamCurrentlyOutOfZone = !zoneManager.isInZone({ lat: location[0], lng: location[1] }) const teamCurrentlyOutOfZone = !zoneManager.isInZone({ lat: location[0], lng: location[1] })
if (teamCurrentlyOutOfZone && !team.outOfZone) { if (teamCurrentlyOutOfZone && !team.outOfZone) {
team.outOfZone = true; team.outOfZone = true;
@@ -421,6 +437,7 @@ export default {
} }
outOfZoneTimeouts.clear(teamId); outOfZoneTimeouts.clear(teamId);
} }
}
// Broadcast new infos // Broadcast new infos
secureAdminBroadcast("teams", this.teams); secureAdminBroadcast("teams", this.teams);
sendUpdatedTeamInformations(team.id); sendUpdatedTeamInformations(team.id);
@@ -430,8 +447,11 @@ export default {
}, },
sendLocation(teamId) { sendLocation(teamId) {
// Conditions
if (this.state != GameState.PLAYING) return false;
// Test of parameters // Test of parameters
if (!this.hasTeam(teamId)) return false; if (!this.hasTeam(teamId)) return false;
if (!this.hasTeam(this.getTeam(teamId).chasing)) return false;
// Variables // Variables
const team = this.getTeam(teamId); const team = this.getTeam(teamId);
const enemyTeam = this.getTeam(team.chasing); const enemyTeam = this.getTeam(team.chasing);
@@ -454,8 +474,11 @@ export default {
}, },
tryCapture(teamId, captureCode) { tryCapture(teamId, captureCode) {
// Conditions
if (this.state != GameState.PLAYING) return false;
// Test of parameters // Test of parameters
if (!this.hasTeam(teamId)) return false; if (!this.hasTeam(teamId)) return false;
if (!this.hasTeam(this.getTeam(teamId).chasing)) return false;
// Variables // Variables
const team = this.getTeam(teamId); const team = this.getTeam(teamId);
const enemyTeam = this.getTeam(team.chasing); const enemyTeam = this.getTeam(team.chasing);

View File

@@ -59,7 +59,6 @@ export function sendUpdatedTeamInformations(teamId) {
distance: team.distance, distance: team.distance,
nCaptures: team.nCaptures, nCaptures: team.nCaptures,
nSentLocation: team.nSentLocation, nSentLocation: team.nSentLocation,
stateDate: game.stateDate,
finishDate: team.finishDate, finishDate: team.finishDate,
}); });
} }
@@ -99,12 +98,12 @@ export function initTeamSocket() {
sendUpdatedTeamInformations(loginTeamId); sendUpdatedTeamInformations(loginTeamId);
socket.emit("game_state", { socket.emit("game_state", {
state: game.state, state: game.state,
date: game.stateDate date: game.startDate
}); });
socket.emit("current_zone", { socket.emit("current_zone", {
begin: zoneManager.getCurrentZone(), begin: zoneManager.getCurrentZone(),
end: zoneManager.getNextZone(), end: zoneManager.getNextZone(),
endDate: zoneManager.currentZone.endDate, endDate: zoneManager.currentZone?.endDate,
}); });
socket.emit("settings", game.getPlayerSettings()); socket.emit("settings", game.getPlayerSettings());
callback({ isLoggedIn : true, message: "Logged in"}); callback({ isLoggedIn : true, message: "Logged in"});

View File

@@ -0,0 +1,21 @@
1758151340243,position,48.7119647,2.202376
1758151348183,position,48.7119684,2.2023519
1758151356935,position,48.7119613,2.2023486
1758151364187,position,48.7119464,2.2023686
1758151372354,position,48.7119567,2.2023589
1758151380901,position,48.7119705,2.2023828
1758151388892,position,48.7119695,2.2023512
1758151397527,position,48.7119742,2.2023338
1758151405867,position,48.7119592,2.2023596
1758151414004,position,48.7119691,2.2023728
1758151424855,position,48.7119702,2.2023816
1758151430282,position,48.7119739,2.2023721
1758151438203,position,48.7119633,2.2023841
1758151446373,position,48.7119647,2.2023806
1758151454699,position,48.7119593,2.2023871
1758151462553,position,48.7119616,2.2023903
1758151470342,position,48.7119594,2.2023859
1758151478473,position,48.7119605,2.2023748
1758151486112,position,48.7119559,2.2023561
1758151494133,position,48.7119617,2.2023674
1758151502319,position,48.7119549,2.2023335

View File

@@ -173,21 +173,25 @@ function polygonSettingsToZones(settings) {
export default { export default {
isRunning: false, isRunning: false,
zones: [], // A zone has to be connected space that doesn't contain an earth pole zones: [], // A zone has to be connected space that doesn't contain an earth pole
currentZone: { id: 0, timeoutId: null, endDate: null }, currentZone: null,
settings: defaultPolygonSettings, settings: defaultPolygonSettings,
start() { start() {
if (this.isRunning) return;
this.isRunning = true; this.isRunning = true;
this.currentZone.id = -1; this.currentZone = { id: -1, timeoutId: null, endDate: null };
this.goNextZone(); this.goNextZone();
}, },
stop() { stop() {
this.isRunning = false; if (!this.isRunning) return;
clearTimeout(this.currentZone.timeoutId); clearTimeout(this.currentZone.timeoutId);
this.isRunning = false;
this.currentZone = null;
}, },
goNextZone() { goNextZone() {
if (!this.isRunning) return;
this.currentZone.id++; this.currentZone.id++;
if (this.currentZone.id >= this.zones.length - 1) { if (this.currentZone.id >= this.zones.length - 1) {
this.currentZone.endDate = Date.now(); this.currentZone.endDate = Date.now();
@@ -199,10 +203,12 @@ export default {
}, },
getCurrentZone() { getCurrentZone() {
if (!this.isRunning) return null;
return this.zones[this.currentZone.id]; return this.zones[this.currentZone.id];
}, },
getNextZone() { getNextZone() {
if (!this.isRunning) return null;
if (this.currentZone.id + 1 < this.zones.length) { if (this.currentZone.id + 1 < this.zones.length) {
return this.zones[this.currentZone.id + 1]; return this.zones[this.currentZone.id + 1];
} else { } else {
@@ -211,6 +217,7 @@ export default {
}, },
isInZone(location) { isInZone(location) {
if (!this.isRunning) return false;
if (this.zones.length == 0) { if (this.zones.length == 0) {
return true; return true;
} else { } else {
@@ -227,13 +234,17 @@ export default {
this.zones = polygonSettingsToZones(settings); this.zones = polygonSettingsToZones(settings);
break; break;
default: default:
return; this.zones = [];
break;
} }
this.settings = settings; this.settings = settings;
this.stop();
this.start();
this.zoneBroadcast(); this.zoneBroadcast();
}, },
zoneBroadcast() { zoneBroadcast() {
if (!this.isRunning) return;
const zone = { const zone = {
begin: this.getCurrentZone(), begin: this.getCurrentZone(),
end: this.getNextZone(), end: this.getNextZone(),

View File

@@ -8,6 +8,8 @@ import { mapZooms } from "@/util/configurations";
export default function LiveMap({ selectedTeamId, onSelected, isFocusing, setIsFocusing, mapStyle, showZones, showNames, showArrows }) { export default function LiveMap({ selectedTeamId, onSelected, isFocusing, setIsFocusing, mapStyle, showZones, showNames, showArrows }) {
const { zoneType, zoneExtremities, teams, nextZoneDate, getTeam, gameState } = useAdmin(); const { zoneType, zoneExtremities, teams, nextZoneDate, getTeam, gameState } = useAdmin();
const [timeLeftNextZone, setTimeLeftNextZone] = useState(null); const [timeLeftNextZone, setTimeLeftNextZone] = useState(null);
const [isFullScreen, setIsFullScreen] = useState(false);
useEffect(() => { useEffect(() => {
if (nextZoneDate) { if (nextZoneDate) {
@@ -50,8 +52,7 @@ export default function LiveMap({ selectedTeamId, onSelected, isFocusing, setIsF
} }
return ( return (
<div className='h-full w-full flex flex-col'> <div className={`${isFullScreen ? "fixed inset-0 z-[9999]" : "relative h-full w-full"}`}>
{gameState == GameState.PLAYING && <p>{`Next zone in : ${formatTime(timeLeftNextZone)}`}</p>}
<CustomMapContainer mapStyle={mapStyle}> <CustomMapContainer mapStyle={mapStyle}>
{isFocusing && <MapPan center={getTeam(selectedTeamId)?.currentLocation} zoom={mapZooms.high} animate />} {isFocusing && <MapPan center={getTeam(selectedTeamId)?.currentLocation} zoom={mapZooms.high} animate />}
<MapEventListener onDragStart={() => setIsFocusing(false)}/> <MapEventListener onDragStart={() => setIsFocusing(false)}/>
@@ -60,12 +61,21 @@ export default function LiveMap({ selectedTeamId, onSelected, isFocusing, setIsF
<CircleZone circle={team.startingArea} color="blue" display={gameState == GameState.PLACEMENT && showZones}> <CircleZone circle={team.startingArea} color="blue" display={gameState == GameState.PLACEMENT && showZones}>
<Tag text={team.name} display={showNames} /> <Tag text={team.name} display={showNames} />
</CircleZone> </CircleZone>
<Arrow pos1={team.currentLocation} pos2={getTeam(team.chased)?.currentLocation} display={showArrows}/> <Arrow pos1={team.currentLocation} pos2={getTeam(team.chasing)?.currentLocation} display={showArrows}/>
<Position position={team.currentLocation} color={"blue"} onClick={() => onSelected(team.id)} display={!team.captured}> <Position position={team.currentLocation} color={"blue"} onClick={() => onSelected(team.id)} display={!team.captured}>
<Tag text={team.name} display={showNames} /> <Tag text={team.name} display={showNames} />
</Position> </Position>
</Fragment>)} </Fragment>)}
</CustomMapContainer> </CustomMapContainer>
{ gameState == GameState.PLAYING &&
<div className="absolute top-4 left-1/2 -translate-x-1/2 z-[1000] pointer-events-none flex flex-col items-center bg-white p-2 rounded-lg shadow-lg drop-shadow">
<p className="text-sm">Durée zone</p>
<p className="text-2xl font-bold">{formatTime(timeLeftNextZone)}</p>
</div>
}
<button className="absolute top-4 right-4 z-[1000] cursor-pointer bg-white p-3 rounded-full shadow-lg drop-shadow" onClick={() => setIsFullScreen(!isFullScreen)}>
<img src={`/icons/fullscreen.png`} className="w-8 h-8" />
</button>
</div> </div>
) )
} }

View File

@@ -2,8 +2,7 @@ import { env } from 'next-runtime-env';
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import useAdmin from "@/hook/useAdmin"; import useAdmin from "@/hook/useAdmin";
import { getStatus } from '@/util/functions'; import { getStatus } from '@/util/functions';
import { Colors } from '@/util/types'; import { Colors, GameState } from '@/util/types';
import { teamStatus } from '@/util/configurations';
function DotLine({ label, value }) { function DotLine({ label, value }) {
return ( return (
@@ -32,6 +31,7 @@ function IconValue({ color, icon, value }) {
export default function TeamSidePanel({ selectedTeamId, onClose }) { export default function TeamSidePanel({ selectedTeamId, onClose }) {
const { getTeam, startDate, gameState } = useAdmin(); const { getTeam, startDate, gameState } = useAdmin();
const [imgSrc, setImgSrc] = useState(""); const [imgSrc, setImgSrc] = useState("");
const [_, setRefreshKey] = useState(0);
const team = getTeam(selectedTeamId); const team = getTeam(selectedTeamId);
const NO_VALUE = "XX"; const NO_VALUE = "XX";
const NEXT_PUBLIC_SOCKET_HOST = env("NEXT_PUBLIC_SOCKET_HOST"); const NEXT_PUBLIC_SOCKET_HOST = env("NEXT_PUBLIC_SOCKET_HOST");
@@ -43,6 +43,14 @@ export default function TeamSidePanel({ selectedTeamId, onClose }) {
if (!team) return null; if (!team) return null;
useEffect(() => {
const interval = setInterval(() => {
setRefreshKey(prev => prev + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
function formatTime(startDate, endDate) { function formatTime(startDate, endDate) {
// startDate in milliseconds // startDate in milliseconds
if (endDate == null || startDate == null || startDate < 0) return NO_VALUE + ":" + NO_VALUE; if (endDate == null || startDate == null || startDate < 0) return NO_VALUE + ":" + NO_VALUE;
@@ -92,18 +100,22 @@ export default function TeamSidePanel({ selectedTeamId, onClose }) {
<DotLine label="ID d'équipe" value={String(selectedTeamId).padStart(6, '0').replace(/(\d{3})(\d{3})/, "$1 $2")} /> <DotLine label="ID d'équipe" value={String(selectedTeamId).padStart(6, '0').replace(/(\d{3})(\d{3})/, "$1 $2")} />
<DotLine label="ID de capture" value={team.captureCode ? String(team.captureCode).padStart(4, '0') : NO_VALUE} /> <DotLine label="ID de capture" value={team.captureCode ? String(team.captureCode).padStart(4, '0') : NO_VALUE} />
</div> </div>
{ gameState != GameState.FINISHED &&
<div> <div>
<DotLine label="Chasse" value={getTeam(team.chasing)?.name ?? NO_VALUE} /> <DotLine label="Chasse" value={getTeam(team.chasing)?.name ?? NO_VALUE} />
<DotLine label="Chassé par" value={getTeam(team.chased)?.name ?? NO_VALUE} /> <DotLine label="Chassé par" value={getTeam(team.chased)?.name ?? NO_VALUE} />
</div> </div>
}
{ (gameState == GameState.PLAYING || gameState == GameState.FINISHED) &&
<div> <div>
<DotLine label="Distance" value={formatDistance(team.distance)} /> <DotLine label="Distance" value={formatDistance(team.distance)} />
<DotLine label="Temps de survie" value={formatTime(startDate, team.captured ? team.finishDate : Date.now())} /> <DotLine label="Temps de survie" value={formatTime(startDate, team.finishDate || Date.now())} />
<DotLine label="Vitesse moyenne" value={formatSpeed(team.distance, startDate, team.captured ? team.finishDate : Date.now())} /> <DotLine label="Vitesse moyenne" value={formatSpeed(team.distance, startDate, team.finishDate || Date.now())} />
<DotLine label="Captures" value={team.nCaptures ?? NO_VALUE} /> <DotLine label="Captures" value={team.nCaptures ?? NO_VALUE} />
<DotLine label="Observations" value={team.nSentLocation ?? NO_VALUE} /> <DotLine label="Observations" value={team.nSentLocation ?? NO_VALUE} />
<DotLine label="Observé" value={team.nObserved ?? NO_VALUE} /> <DotLine label="Observé" value={team.nObserved ?? NO_VALUE} />
</div> </div>
}
<div> <div>
<DotLine label="Modèle" value={team.phoneModel ?? NO_VALUE} /> <DotLine label="Modèle" value={team.phoneModel ?? NO_VALUE} />
<DotLine label="Nom" value={team.phoneName ?? NO_VALUE} /> <DotLine label="Nom" value={team.phoneName ?? NO_VALUE} />

View File

@@ -17,9 +17,9 @@ export default function AdminLoginPage() {
return ( return (
<div className="w-full h-full flex items-center justify-center"> <div className="w-full h-full flex items-center justify-center">
<form className="flex flex-col items-center gap-3 bg-white p-8 rounded-lg ring-1 ring-black" onSubmit={handleSubmit}> <form className="flex flex-col items-center gap-3 bg-white p-8 rounded-lg ring-1 ring-black" onSubmit={handleSubmit}>
<h1 className="text-2xl font-bold text-center text-gray-700">Admin login</h1> <h1 className="text-2xl font-bold text-center text-gray-700">Connexion admin</h1>
<input name="team-id" className="w-60 h-12 text-center rounded ring-1 ring-inset ring-black placeholder:text-gray-400" placeholder="Admin password" value={value} onChange={(e) => setValue(e.target.value)}/> <input name="team-id" className="w-60 h-12 text-center rounded ring-1 ring-inset ring-black placeholder:text-gray-400" placeholder="Mot de passe admin" value={value} onChange={(e) => setValue(e.target.value)}/>
<button className=" w-40 h-12 bg-blue-600 hover:bg-blue-500 text-l text-white rounded ease-out duration-200" type="submit">Login</button> <button className=" w-40 h-12 bg-blue-600 hover:bg-blue-500 text-l text-white rounded ease-out duration-200" type="submit">Se connecter</button>
</form> </form>
</div> </div>
); );

View File

@@ -45,7 +45,7 @@ export default function AdminPage() {
const [mapStyle, setMapStyle] = useState(mapStyles.default); const [mapStyle, setMapStyle] = useState(mapStyles.default);
const [showZones, setShowZones] = useState(true); const [showZones, setShowZones] = useState(true);
const [showNames, setShowNames] = useState(true); const [showNames, setShowNames] = useState(true);
const [showArrows, setShowArrows] = useState(false); const [showArrows, setShowArrows] = useState(true);
const [isFocusing, setIsFocusing] = useState(true); const [isFocusing, setIsFocusing] = useState(true);
useProtect(); useProtect();

View File

@@ -89,21 +89,21 @@ export default function CircleZoneSelector({ display }) {
</CustomMapContainer> </CustomMapContainer>
</div> </div>
<div className="h-full w-1/6 flex flex-col gap-3"> <div className="h-full w-1/6 flex flex-col gap-3">
{editMode == EditMode.MIN && <button className="w-full h-16 text-lg text-white rounded bg-blue-600 hover:bg-blue-500" onClick={() => setEditMode(EditMode.MAX)}>Click to edit first zone</button>} {editMode == EditMode.MAX && <button className="w-full h-16 text-lg text-white rounded bg-blue-600 hover:bg-blue-500" onClick={() => setEditMode(EditMode.MIN)}>Édition première zone</button>}
{editMode == EditMode.MAX && <button className="w-full h-16 text-lg text-white rounded bg-red-600 hover:bg-red-500" onClick={() => setEditMode(EditMode.MIN)}>Click to edit last zone</button>} {editMode == EditMode.MIN && <button className="w-full h-16 text-lg text-white rounded bg-red-600 hover:bg-red-500" onClick={() => setEditMode(EditMode.MAX)}>Édition dernière zone</button>}
<div className="w-full flex flex-row gap-2 items-center justify-between"> <div className="w-full flex flex-row gap-2 items-center justify-between">
<p>Reduction number</p> <p>Nombre de rétrécissements</p>
<NumberInput id="reduction-number" value={localZoneSettings.reductionCount ?? ""} onChange={updateReductionCount} /> <NumberInput id="reduction-number" value={localZoneSettings.reductionCount ?? ""} onChange={updateReductionCount} />
</div> </div>
<div className="w-full flex flex-row gap-2 items-center justify-between"> <div className="w-full flex flex-row gap-2 items-center justify-between">
<p>Zone duration</p> <p>Durée d'une zone</p>
<NumberInput id="duration" value={localZoneSettings.duration ?? ""} onChange={updateDuration} /> <NumberInput id="duration" value={localZoneSettings.duration ?? ""} onChange={updateDuration} />
</div> </div>
<div className="w-full flex flex-row gap-2 items-center justify-between"> <div className="w-full flex flex-row gap-2 items-center justify-between">
<p>Timeout</p> <p>Temps permis hors zone</p>
<NumberInput id="timeout-circle-selector" value={localOutOfZoneDelay ?? ""} onChange={setLocalOutOfZoneDelay} /> <NumberInput id="timeout-circle-selector" value={localOutOfZoneDelay ?? ""} onChange={setLocalOutOfZoneDelay} />
</div> </div>
<button className="w-full h-16 text-lg text-white rounded bg-green-600 hover:bg-green-500" onClick={handleSubmit}>Apply</button> <button className="w-full h-16 text-lg text-white rounded bg-green-600 hover:bg-green-500" onClick={handleSubmit}>Appliquer</button>
</div> </div>
</>} </>}
</div> </div>

View File

@@ -20,7 +20,7 @@ export default function Messages() {
}; };
return ( return (
<Section title="Message" innerClassName="w-full h-full flex flex-col gap-3 items-center"> <Section title="Messages" innerClassName="w-full h-full flex flex-col gap-3 items-center">
<MessageInput id="waiting" title="Attente  :" value={localGameSettings?.waiting ?? ""} onChange={(e) => modifyLocalZoneSettings("waiting", e.target.value)} onBlur={applyLocalGameSettings}/> <MessageInput id="waiting" title="Attente  :" value={localGameSettings?.waiting ?? ""} onChange={(e) => modifyLocalZoneSettings("waiting", e.target.value)} onBlur={applyLocalGameSettings}/>
<MessageInput id="captured" title="Capture :" value={localGameSettings?.captured ?? ""} onChange={(e) => modifyLocalZoneSettings("captured", e.target.value)} onBlur={applyLocalGameSettings}/> <MessageInput id="captured" title="Capture :" value={localGameSettings?.captured ?? ""} onChange={(e) => modifyLocalZoneSettings("captured", e.target.value)} onBlur={applyLocalGameSettings}/>
<MessageInput id="winner" title="Victoire :" value={localGameSettings?.winner ?? ""} onChange={(e) => modifyLocalZoneSettings("winner", e.target.value)} onBlur={applyLocalGameSettings}/> <MessageInput id="winner" title="Victoire :" value={localGameSettings?.winner ?? ""} onChange={(e) => modifyLocalZoneSettings("winner", e.target.value)} onBlur={applyLocalGameSettings}/>

View File

@@ -54,7 +54,7 @@ export default function PlacementZoneSelector({ display }) {
</div> </div>
<div className="h-full w-1/6 flex flex-col gap-3"> <div className="h-full w-1/6 flex flex-col gap-3">
<div className="w-full text-center"> <div className="w-full text-center">
<h2 className="text-xl">Teams</h2> <h2 className="text-xl">Équipes</h2>
</div> </div>
<List array={teams} selectedId={selectedTeamId} onSelected={(id) => setSelectedTeamId(selectedTeamId != id ? id : null)}> <List array={teams} selectedId={selectedTeamId} onSelected={(id) => setSelectedTeamId(selectedTeamId != id ? id : null)}>
{ (team) => { (team) =>

View File

@@ -1,6 +1,7 @@
import { useState } from "react";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { ZoneTypes } from "@/util/types"; import { ZoneTypes } from "@/util/types";
import useLocalVariable from "@/hook/useLocalVariable";
import useAdmin from "@/hook/useAdmin";
// Imported at runtime and not at compile time // Imported at runtime and not at compile time
const CircleZoneSelector = dynamic(() => import('./circleZoneSelector'), { ssr: false }); const CircleZoneSelector = dynamic(() => import('./circleZoneSelector'), { ssr: false });
@@ -18,18 +19,19 @@ function ZoneTypeButton({title, onClick, isSelected}) {
} }
export default function PlayingZoneSelector({ display }) { export default function PlayingZoneSelector({ display }) {
const [zoneType, setZoneType] = useState(ZoneTypes.POLYGON); const { zoneType } = useAdmin();
const [localZoneType, setLocalZoneType] = useLocalVariable(zoneType, () => {});
return ( return (
<div className={display ? 'w-full h-full gap-3 flex flex-col' : "hidden"}> <div className={display ? 'w-full h-full gap-3 flex flex-col' : "hidden"}>
<div className="w-full flex flex-row gap-3 items-center"> <div className="w-full flex flex-row gap-3 items-center">
<p className="text-l">Type de zone :</p> <p className="text-l">Type de zone :</p>
<ZoneTypeButton title="Cercles" onClick={() => setZoneType(ZoneTypes.CIRCLE)} isSelected={zoneType == ZoneTypes.CIRCLE} /> <ZoneTypeButton title="Cercles" onClick={() => setLocalZoneType(ZoneTypes.CIRCLE)} isSelected={localZoneType == ZoneTypes.CIRCLE} />
<ZoneTypeButton title="Polygones" onClick={() => setZoneType(ZoneTypes.POLYGON)} isSelected={zoneType == ZoneTypes.POLYGON} /> <ZoneTypeButton title="Polygones" onClick={() => setLocalZoneType(ZoneTypes.POLYGON)} isSelected={localZoneType == ZoneTypes.POLYGON} />
</div> </div>
<div className="w-full flex-1"> <div className="w-full flex-1">
<CircleZoneSelector display={zoneType == ZoneTypes.CIRCLE} /> <CircleZoneSelector display={localZoneType == ZoneTypes.CIRCLE} />
<PolygonZoneSelector display={zoneType == ZoneTypes.POLYGON} /> <PolygonZoneSelector display={localZoneType == ZoneTypes.POLYGON} />
</div> </div>
</div> </div>
); );

View File

@@ -106,7 +106,7 @@ export default function PolygonZoneSelector({ display }) {
</div> </div>
<div className="h-full w-1/6 flex flex-col gap-3"> <div className="h-full w-1/6 flex flex-col gap-3">
<div className="w-full text-center"> <div className="w-full text-center">
<h2 className="text-xl">Reduction order</h2> <h2 className="text-xl">Ordre de réduction</h2>
</div> </div>
<ReorderList droppableId="zones-order" array={localZoneSettings.polygons} setArray={setLocalZoneSettingsPolygons}> <ReorderList droppableId="zones-order" array={localZoneSettings.polygons} setArray={setLocalZoneSettingsPolygons}>
{ (zone) => { (zone) =>
@@ -117,10 +117,10 @@ export default function PolygonZoneSelector({ display }) {
} }
</ReorderList> </ReorderList>
<div className="w-full flex flex-row gap-2 items-center justify-between"> <div className="w-full flex flex-row gap-2 items-center justify-between">
<p>Timeout</p> <p>Temps permis hors zone</p>
<NumberInput id="timeout-polygon-selector" value={localOutOfZoneDelay ?? ""} onChange={setLocalOutOfZoneDelay}/> <NumberInput id="timeout-polygon-selector" value={localOutOfZoneDelay ?? ""} onChange={setLocalOutOfZoneDelay}/>
</div> </div>
<button className="w-full h-16 text-lg text-white rounded bg-green-600 hover:bg-green-500" onClick={handleSubmit}>Apply</button> <button className="w-full h-16 text-lg text-white rounded bg-green-600 hover:bg-green-500" onClick={handleSubmit}>Appliquer</button>
</div> </div>
</>} </>}
</div> </div>

View File

@@ -34,7 +34,7 @@ export default function TeamManager() {
} }
return ( return (
<Section title="Équipe" outerClassName="flex-1 min-h-0" innerClassName="flex flex-col items-center gap-3"> <Section title="Équipes" outerClassName="flex-1 min-h-0" innerClassName="flex flex-col items-center gap-3">
<form className='w-full flex flex-row gap-3' onSubmit={handleTeamSubmit}> <form className='w-full flex flex-row gap-3' onSubmit={handleTeamSubmit}>
<div className='w-full'> <div className='w-full'>
<input name="teamName" label='Team name' value={teamName} onChange={(e) => setTeamName(e.target.value)} type="text" className="w-full h-full p-4 ring-1 ring-inset ring-gray-300" /> <input name="teamName" label='Team name' value={teamName} onChange={(e) => setTeamName(e.target.value)} type="text" className="w-full h-full p-4 ring-1 ring-inset ring-gray-300" />
@@ -51,7 +51,7 @@ export default function TeamManager() {
</ReorderList> </ReorderList>
</div> </div>
<div className="w-full flex flex-row gap-2 items-center justify-between"> <div className="w-full flex flex-row gap-2 items-center justify-between">
<p>Interval between position updates</p> <p>Intervalle entre les envois de position</p>
<NumberInput id="position-update" value={localSendPositionDelay ?? ""} onChange={setLocalSendPositionDelay} onBlur={applyLocalSendPositionDelay} /> <NumberInput id="position-update" value={localSendPositionDelay ?? ""} onChange={setLocalSendPositionDelay} onBlur={applyLocalSendPositionDelay} />
</div> </div>
</Section> </Section>

View File

@@ -158,7 +158,7 @@ export function Arrow({ pos1, pos2, color = 'black', weight = 5, arrowSize = 20,
map.removeLayer(polyline); map.removeLayer(polyline);
map.removeLayer(decorator); map.removeLayer(decorator);
}; };
}, [insetPositions]) }, [display, insetPositions])
return null; return null;
} }

View File

@@ -10,7 +10,7 @@ export default function usePasswordProtect(loginPath, redirectPath, loading, log
redirect(loginPath); redirect(loginPath);
} }
if(loggedIn && !loading && path === loginPath) { if(loggedIn && !loading && path === loginPath) {
redirect(redirectPath) redirect(redirectPath);
} }
}, [loggedIn, loading, path]); }, [loggedIn, loading, path]);
} }

View File

@@ -14,15 +14,13 @@ export default function useSocketAuth(socket, passwordName) {
const [savedPassword, setSavedPassword, savedPasswordLoading] = useLocalStorage(passwordName, null); const [savedPassword, setSavedPassword, savedPasswordLoading] = useLocalStorage(passwordName, null);
useEffect(() => { useEffect(() => {
console.log("Checking saved password", savedPassword, loggedIn);
if (savedPassword && !loggedIn) { if (savedPassword && !loggedIn) {
console.log("Logging in with saved password", savedPassword); console.log("Try to log with :", savedPassword);
socket.emit(LOGIN_MESSAGE, savedPassword); socket.emit(LOGIN_MESSAGE, savedPassword);
} }
}, [savedPassword]); }, [savedPassword]);
function login(password) { function login(password) {
console.log("Logging", password);
setSavedPassword(password) setSavedPassword(password)
} }
@@ -32,9 +30,10 @@ export default function useSocketAuth(socket, passwordName) {
socket.emit(LOGOUT_MESSAGE) socket.emit(LOGOUT_MESSAGE)
} }
useSocketListener(socket, LOGIN_RESPONSE_MESSAGE,(loginResponse) => { useSocketListener(socket, LOGIN_RESPONSE_MESSAGE, (loginResponse) => {
setWaitingForResponse(false); setWaitingForResponse(false);
setLoggedIn(loginResponse); setLoggedIn(loginResponse);
console.log(loginResponse ? "Logged in" : "Not logged in");
}); });
useEffect(() => { useEffect(() => {

View File

@@ -33,4 +33,6 @@ export const teamStatus = {
ready: { label: "Placée", color: Colors.green }, ready: { label: "Placée", color: Colors.green },
notready: { label: "Non placée", color: Colors.red }, notready: { label: "Non placée", color: Colors.red },
waiting: { label: "En attente", color: Colors.grey }, waiting: { label: "En attente", color: Colors.grey },
victory: { label: "Victoire", color: Colors.green },
defeat: { label: "Défaite", color: Colors.red },
} }

View File

@@ -11,7 +11,7 @@ export function getStatus(team, gamestate) {
case GameState.PLAYING: case GameState.PLAYING:
return team.captured ? teamStatus.captured : team.outOfZone ? teamStatus.outofzone : teamStatus.playing; return team.captured ? teamStatus.captured : team.outOfZone ? teamStatus.outofzone : teamStatus.playing;
case GameState.FINISHED: case GameState.FINISHED:
return team.captured ? teamStatus.captured : teamStatus.playing; return team.captured ? teamStatus.defeat : teamStatus.victory;
default: default:
return teamStatus.default; return teamStatus.default;
} }