mirror of
https://git.rezel.net/LudoTech/traque.git
synced 2026-04-10 16:30:18 +02:00
Polygonal zone fix + zones redesing + cleaning
This commit is contained in:
@@ -2,7 +2,7 @@ compte rendu traque 14-12-2025
|
||||
|
||||
Contexte
|
||||
|
||||
Cette traque s'est déroulé avec le même site et la même application que la traque de septembre.
|
||||
Cette traque s'est déroulé avec le même serveur et la même application que la traque de septembre.
|
||||
Météo : 8°C, pas de pluie, brouillard (visibilité <400m), vent 10km/h
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@ automatique.
|
||||
[x] Il est pas évident de voir qu'elles équipes sont encore en jeu sur la page principale admin. Notamment, on voudrait que les
|
||||
voyants des équipes encore en jeu soit mis en avant.
|
||||
[x] La visibilité des éléments de la map en calque satellite est mauvaise. Il faut revoir les couleurs.
|
||||
[ ] Les zones polygonales s'affichent mal sur la version prod de l'app mobile. Les anciennes n'ont pas l'air de disparaitre.
|
||||
[x] Les zones polygonales s'affichent mal sur la version prod de l'app mobile. Les anciennes n'ont pas l'air de disparaitre.
|
||||
[x] La zone de jeu (en rouge) est mal comprise, les joueurs évitent de rester dedans alors qu'ils ont le droit.
|
||||
[ ] L'application n'est pas pleinement intuitive, par exemple le bouton pour actualiser sa position ou alors les icônes des stats.
|
||||
|
||||
|
||||
À faire
|
||||
|
||||
@@ -14,14 +14,14 @@ sans raisons apparentes
|
||||
[ ] Une équipe avait deux téléphones connectés sauf que le premier ne marchait pas bien. Par ailleurs, l'équipe disaient n'avoir
|
||||
qu'un seul téléphone connecté.
|
||||
[ ] La photo d'une des équipes ne parvenait pas jusqu'au serveur. Tout semblait normal pour l'équipe.
|
||||
[ ] Il y a apparement eu un problème de synchronisation de l'affichage entre admin concernant les zones de départs. Il s'est résolu
|
||||
[x] Il y a apparement eu un problème de synchronisation de l'affichage entre admin concernant les zones de départs. Il s'est résolu
|
||||
de lui même. Peut être un problème passager de connection.
|
||||
[x] Le focus sur une équipe dans la page principale des admins est à revoir. Par exemple, le zoom seul devrait désactiver le focus
|
||||
automatique.
|
||||
[x] Il est pas évident de voir qu'elles équipes sont encore en jeu sur la page principale admin. Notamment, on voudrait que les
|
||||
voyants des équipes encore en jeu soit mis en avant.
|
||||
[x] La visibilité des éléments de la map en calque satellite est mauvaise. Il faut revoir les couleurs.
|
||||
[ ] Les zones polygonales s'affichent mal sur la version prod de l'app mobile. Les anciennes n'ont pas l'air de disparaitre.
|
||||
[x] Les zones polygonales s'affichent mal sur la version prod de l'app mobile. Les anciennes n'ont pas l'air de disparaitre.
|
||||
|
||||
|
||||
À faire
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// React
|
||||
import { useState, useEffect, Fragment } from 'react';
|
||||
import { useState, useEffect, useMemo, Fragment } from 'react';
|
||||
import { View, Text, Image, Alert, StyleSheet, TouchableOpacity } from 'react-native';
|
||||
// Expo
|
||||
import { useRouter } from 'expo-router';
|
||||
@@ -15,18 +15,36 @@ import { useTimeDifference } from '../hook/useTimeDifference';
|
||||
// Util
|
||||
import { GameState } from '../util/gameState';
|
||||
import { TimerMMSS } from '../components/timer';
|
||||
import { secondsToMMSS } from '../util/format';
|
||||
import { secondsToMMSS } from '../util/functions';
|
||||
import { Colors } from '../util/colors';
|
||||
|
||||
const Interface = () => {
|
||||
const router = useRouter();
|
||||
const {messages, nextZoneDate, isShrinking, startLocationTracking, stopLocationTracking, gameState} = useTeamContext();
|
||||
const {loggedIn, logout, loading} = useTeamConnexion();
|
||||
const {name, ready, captured, locationSendDeadline, outOfZone, outOfZoneDeadline, hasHandicap, enemyHasHandicap} = useGame();
|
||||
const {name, ready, captured, locationSendDeadline, sendCurrentPosition, outOfZone, outOfZoneDeadline, hasHandicap, enemyHasHandicap} = useGame();
|
||||
const [timeLeftSendLocation] = useTimeDifference(locationSendDeadline, 1000);
|
||||
const [timeLeftNextZone] = useTimeDifference(nextZoneDate, 1000);
|
||||
const [timeLeftOutOfZone] = useTimeDifference(outOfZoneDeadline, 1000);
|
||||
const [bottomContainerHeight, setBottomContainerHeight] = useState(0);
|
||||
|
||||
const statusMessage = useMemo(() => {
|
||||
switch (gameState) {
|
||||
case GameState.SETUP:
|
||||
return messages?.waiting || "Préparation de la partie";
|
||||
case GameState.PLACEMENT:
|
||||
return "Phase de placement";
|
||||
case GameState.PLAYING:
|
||||
if (captured) return messages?.captured || "Vous avez été éliminé...";
|
||||
if (!outOfZone) return "La partie est en cours";
|
||||
if (!hasHandicap) return `Veuillez retourner dans la zone\nHandicap dans ${secondsToMMSS(-timeLeftOutOfZone)}`;
|
||||
else return `Veuillez retourner dans la zone\nVotre position est révélée en continue`;
|
||||
case GameState.FINISHED:
|
||||
return `Vous avez ${captured ? (messages?.loser || "perdu...") : (messages?.winner || "gagné !")}`;
|
||||
default:
|
||||
return "Inconnue";
|
||||
}
|
||||
}, [gameState, messages, outOfZone, hasHandicap, timeLeftOutOfZone, captured]);
|
||||
|
||||
// Router
|
||||
useEffect(() => {
|
||||
@@ -61,45 +79,35 @@ const Interface = () => {
|
||||
<Text style={{fontSize: 36, fontWeight: "bold", textAlign: "center"}}>{(name ?? "Indisponible")}</Text>
|
||||
</View>
|
||||
<View style={styles.logContainer}>
|
||||
<TouchableOpacity style={{width:"100%"}}>
|
||||
{ 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.PLAYING && !outOfZone && <Text style={styles.gameState}>La partie est en cours</Text>}
|
||||
{ gameState == GameState.PLAYING && outOfZone && !hasHandicap && <Text style={styles.gameStateOutOfZone}>{`Veuillez retourner dans la zone\nHandicap dans ${secondsToMMSS(-timeLeftOutOfZone)}`}</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>}
|
||||
<TouchableOpacity style={styles.gameState}>
|
||||
<Text style={{fontSize: 18}}>{statusMessage}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{ gameState == GameState.PLACEMENT &&
|
||||
<View style={styles.timersContainer}>
|
||||
<View style={styles.infoContainer}>
|
||||
{ gameState == GameState.PLACEMENT &&
|
||||
<View style={[styles.readyIndicator, {backgroundColor: ready ? "#3C3" : "#C33"}]}>
|
||||
<Text style={{color: '#fff', fontSize: 16}}>{ready ? "Placé" : "Non placé"}</Text>
|
||||
</View>
|
||||
</View>
|
||||
}
|
||||
{ gameState == GameState.PLAYING && !captured && <Fragment>
|
||||
<View style={styles.timersContainer}>
|
||||
}
|
||||
{ gameState == GameState.PLAYING && !captured && <Fragment>
|
||||
<TimerMMSS style={{width: "50%"}} title={isShrinking ? "Réduction de la zone" : "Durée de la zone"} seconds={-timeLeftNextZone} />
|
||||
<TimerMMSS style={{width: "50%"}} title={"Position envoyée dans"} seconds={!hasHandicap ? -timeLeftSendLocation: 0} />
|
||||
</View>
|
||||
{enemyHasHandicap &&
|
||||
<Text style={{fontSize: 18, marginTop: 6, fontWeight: "bold"}}>Position ennemie révélée en continue !</Text>
|
||||
}
|
||||
</Fragment>}
|
||||
{ gameState == GameState.PLAYING && captured &&
|
||||
<View style={[styles.timersContainer, {height: 61}]}>
|
||||
<Text style={{fontSize: 20}}>{messages?.captured || "Vous avez été éliminé..."}</Text>
|
||||
</View>
|
||||
}
|
||||
{ gameState == GameState.FINISHED &&
|
||||
<View style={[styles.timersContainer, {height: 61}]}>
|
||||
{captured && <Text style={{fontSize: 20}}>{captured ? (messages?.loser || "Vous avez perdu...") : (messages?.winner || "Vous avez gagné !")}</Text>}
|
||||
</View>
|
||||
</Fragment>}
|
||||
</View>
|
||||
{ enemyHasHandicap &&
|
||||
<Text style={{fontSize: 18, marginTop: 6, fontWeight: "bold"}}>Position ennemie révélée en continue !</Text>
|
||||
}
|
||||
</View>
|
||||
<View style={styles.bottomContainer} onLayout={(event) => setBottomContainerHeight(event.nativeEvent.layout.height)}>
|
||||
<CustomMap/>
|
||||
<Drawer height={bottomContainerHeight}/>
|
||||
{ gameState == GameState.PLAYING && !captured && !hasHandicap &&
|
||||
<TouchableOpacity style={styles.updatePosition} onPress={sendCurrentPosition}>
|
||||
<Image source={require("../assets/images/update_position.png")} style={{width: 40, height: 40}} resizeMode="contain"></Image>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
{ gameState == GameState.PLAYING && !captured &&
|
||||
<Drawer height={bottomContainerHeight}/>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@@ -138,19 +146,9 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 10,
|
||||
width: "100%",
|
||||
backgroundColor: 'white',
|
||||
fontSize: 18,
|
||||
padding: 10,
|
||||
},
|
||||
gameStateOutOfZone: {
|
||||
borderWidth: 2,
|
||||
borderRadius: 10,
|
||||
width: "100%",
|
||||
backgroundColor: 'white',
|
||||
fontSize: 18,
|
||||
padding: 10,
|
||||
borderColor: 'red'
|
||||
},
|
||||
timersContainer: {
|
||||
infoContainer: {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
@@ -168,5 +166,18 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
bottomContainer: {
|
||||
flex: 1,
|
||||
}
|
||||
},
|
||||
updatePosition: {
|
||||
position: 'absolute',
|
||||
right: 30,
|
||||
bottom: 80,
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderRadius: 30,
|
||||
backgroundColor: 'white',
|
||||
borderWidth: 4,
|
||||
borderColor: 'black',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// React
|
||||
import { useState, useEffect, Fragment } from 'react';
|
||||
import { useState, useEffect, useMemo, Fragment } from 'react';
|
||||
import { ScrollView, View, Text, Image, StyleSheet, TouchableOpacity, TouchableHighlight, Alert } from 'react-native';
|
||||
import Collapsible from 'react-native-collapsible';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
@@ -16,7 +16,7 @@ import { useGame } from '../hook/useGame';
|
||||
// Util
|
||||
import { GameState } from '../util/gameState';
|
||||
import { Colors } from '../util/colors';
|
||||
import { secondsToHHMMSS } from '../util/format';
|
||||
import { secondsToHHMMSS } from '../util/functions';
|
||||
|
||||
export const Drawer = ({ height }) => {
|
||||
const [collapsibleState, setCollapsibleState] = useState(true);
|
||||
@@ -25,10 +25,18 @@ export const Drawer = ({ height }) => {
|
||||
const {gameState, startDate} = useTeamContext();
|
||||
const {capture, enemyName, captureCode, name, teamId, distance, finishDate, nCaptures, nSentLocation, hasHandicap} = useGame();
|
||||
const [timeSinceStart] = useTimeDifference(startDate, 1000);
|
||||
const [avgSpeed, setAvgSpeed] = useState(0); // Speed in m/s
|
||||
const [enemyImageURI, setEnemyImageURI] = useState("../assets/images/missing_image.jpg");
|
||||
const [captureStatus, setCaptureStatus] = useState(0); // 0 : no capture | 1 : waiting for response from server | 2 : capture failed | 3 : capture succesful
|
||||
const captureStatusColor = {0: "#777", 1: "#FFA500", 2: "#FF6B6B", 3: "#81C784"};
|
||||
|
||||
const avgSpeed = useMemo(() => {
|
||||
const hours = (finishDate ? (finishDate - startDate) : timeSinceStart*1000) / 1000 / 3600;
|
||||
if (hours <= 0 || distance <= 0) return 0;
|
||||
const km = distance / 1000;
|
||||
const speed = km / hours;
|
||||
|
||||
return parseFloat(speed.toFixed(1));
|
||||
}, [finishDate, startDate, timeSinceStart, distance]);
|
||||
|
||||
// Capture state update
|
||||
useEffect(() => {
|
||||
@@ -44,17 +52,8 @@ export const Drawer = ({ height }) => {
|
||||
useEffect(() => {
|
||||
setEnemyImageURI(`${SERVER_URL}/photo/enemy?team=${teamId}&t=${new Date().getTime()}`);
|
||||
}, [SERVER_URL, enemyName, teamId]);
|
||||
|
||||
// Update the average speed
|
||||
useEffect(() => {
|
||||
const hours = (finishDate ? (finishDate - startDate) : timeSinceStart*1000) / 1000 / 3600;
|
||||
const km = distance / 1000;
|
||||
setAvgSpeed(Math.floor(km / hours * 10) / 10);
|
||||
}, [distance, finishDate, startDate, timeSinceStart]);
|
||||
|
||||
if (gameState != GameState.PLAYING) return;
|
||||
|
||||
function handleCapture() {
|
||||
const handleCapture = () => {
|
||||
if (captureStatus != 1) {
|
||||
setCaptureStatus(1);
|
||||
capture(enemyCaptureCode)
|
||||
@@ -71,7 +70,7 @@ export const Drawer = ({ height }) => {
|
||||
});
|
||||
setEnemyCaptureCode("");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.outerDrawerContainer}>
|
||||
|
||||
70
mobile/traque-app/components/layer.jsx
Normal file
70
mobile/traque-app/components/layer.jsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Fragment } from 'react';
|
||||
import { Polygon } from 'react-native-maps';
|
||||
import { circleToPolygon } from '../util/functions';
|
||||
|
||||
export const InvertedPolygon = ({id, coordinates, fillColor}) => {
|
||||
// We create 3 rectangles covering earth, with the first rectangle centered on the hole
|
||||
const shift = Math.floor(coordinates[0].longitude);
|
||||
const lat = 85;
|
||||
const lon = 60;
|
||||
const worldOuterBounds1 = [
|
||||
{ latitude: -lat, longitude: -lon + shift },
|
||||
{ latitude: -lat, longitude: lon + shift },
|
||||
{ latitude: lat, longitude: lon + shift },
|
||||
{ latitude: lat, longitude: -lon + shift },
|
||||
];
|
||||
const worldOuterBounds2 = [
|
||||
{ latitude: -lat, longitude: -lon + 120 + shift },
|
||||
{ latitude: -lat, longitude: lon + 120 + shift },
|
||||
{ latitude: lat, longitude: lon + 120 + shift },
|
||||
{ latitude: lat, longitude: -lon + 120 + shift },
|
||||
];
|
||||
const worldOuterBounds3 = [
|
||||
{ latitude: -lat, longitude: -lon + 240 + shift },
|
||||
{ latitude: -lat, longitude: lon + 240 + shift },
|
||||
{ latitude: lat, longitude: lon + 240 + shift },
|
||||
{ latitude: lat, longitude: -lon + 240 + shift },
|
||||
];
|
||||
|
||||
return <Fragment>
|
||||
<Polygon
|
||||
key={`${id}-mask-1`}
|
||||
geodesic={true}
|
||||
holes={[coordinates]}
|
||||
coordinates={worldOuterBounds1}
|
||||
fillColor={fillColor}
|
||||
strokeColor="rgba(0, 0, 0, 0)"
|
||||
/>
|
||||
<Polygon
|
||||
key={`${id}-mask-2`}
|
||||
geodesic={true}
|
||||
coordinates={worldOuterBounds2}
|
||||
fillColor={fillColor}
|
||||
strokeColor="rgba(0, 0, 0, 0)"
|
||||
/>
|
||||
<Polygon
|
||||
key={`${id}-mask-3`}
|
||||
geodesic={true}
|
||||
coordinates={worldOuterBounds3}
|
||||
fillColor={fillColor}
|
||||
strokeColor="rgba(0, 0, 0, 0)"
|
||||
/>
|
||||
</Fragment>;
|
||||
};
|
||||
|
||||
export const InvertedCircle = ({id, center, radius, fillColor}) => {
|
||||
return <InvertedPolygon id={id} coordinates={circleToPolygon({center: center, radius: radius})} fillColor={fillColor} />;
|
||||
};
|
||||
|
||||
export const DashedCircle = ({id, center, radius, fillColor, strokeColor, strokeWidth, lineDashPattern}) => {
|
||||
return (
|
||||
<Polygon
|
||||
key={id}
|
||||
coordinates={circleToPolygon({center: center, radius: radius})}
|
||||
fillColor={fillColor}
|
||||
strokeColor={strokeColor}
|
||||
strokeWidth={strokeWidth}
|
||||
lineDashPattern={lineDashPattern}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,10 @@
|
||||
// React
|
||||
import { useState, useEffect, useRef, Fragment } from 'react';
|
||||
import { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import { View, Image, Alert, StyleSheet, TouchableOpacity } from 'react-native';
|
||||
import MapView, { Marker, Circle, Polygon } from 'react-native-maps';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
// Components
|
||||
import { DashedCircle, InvertedCircle, InvertedPolygon } from './layer';
|
||||
// Contexts
|
||||
import { useTeamContext } from '../context/teamContext';
|
||||
// Hooks
|
||||
@@ -13,77 +15,143 @@ import { ZoneTypes, InitialRegions } from '../util/constants';
|
||||
|
||||
export const CustomMap = () => {
|
||||
const {zoneType, zoneExtremities, location, gameState} = useTeamContext();
|
||||
const {sendCurrentPosition, enemyLocation, startingArea, captured, lastSentLocation, hasHandicap} = useGame();
|
||||
const mapRef = useRef(null);
|
||||
const {enemyLocation, startingArea, lastSentLocation, hasHandicap} = useGame();
|
||||
const [centerMap, setCenterMap] = useState(true);
|
||||
const mapRef = useRef(null);
|
||||
|
||||
// Center the map on user position
|
||||
useEffect(() => {
|
||||
if (centerMap && mapRef.current && location) {
|
||||
mapRef.current.animateToRegion({latitude: location[0], longitude: location[1], latitudeDelta: 0, longitudeDelta: 0.02}, 1000);
|
||||
}
|
||||
}, [centerMap, mapRef, location]);
|
||||
|
||||
}, [centerMap, location]);
|
||||
|
||||
|
||||
// Map layers
|
||||
|
||||
const latToLatitude = (pos) => ({latitude: pos.lat, longitude: pos.lng});
|
||||
|
||||
const startZone = useMemo(() => {
|
||||
if (gameState != GameState.PLACEMENT || !startingArea) return null;
|
||||
|
||||
return (
|
||||
<Circle key="start-zone" center={{ latitude: startingArea.center.lat, longitude: startingArea.center.lng }} radius={startingArea.radius} strokeWidth={2} strokeColor={`rgba(0, 0, 255, 1)`} fillColor={`rgba(0, 0, 255, 0.2)`}/>
|
||||
);
|
||||
}, [gameState, startingArea]);
|
||||
|
||||
const gameZone = useMemo(() => {
|
||||
if (gameState !== GameState.PLAYING || !zoneExtremities) return null;
|
||||
|
||||
const items = [];
|
||||
|
||||
const nextZoneStrokeColor = "rgb(90, 90, 90)";
|
||||
const zoneColor = "rgba(25, 83, 169, 0.4)";
|
||||
const strokeWidth = 3;
|
||||
const lineDashPattern = [30, 10];
|
||||
|
||||
if (zoneType === ZoneTypes.circle) {
|
||||
if (zoneExtremities.begin) items.push(
|
||||
<InvertedCircle
|
||||
key="game-zone-begin-circle"
|
||||
id="game-zone-begin-circle"
|
||||
center={latToLatitude(zoneExtremities.begin.center)}
|
||||
radius={zoneExtremities.begin.radius}
|
||||
fillColor={zoneColor}
|
||||
/>
|
||||
);
|
||||
if (zoneExtremities.end) items.push(
|
||||
<DashedCircle
|
||||
key="game-zone-end-circle"
|
||||
id="game-zone-end-circle"
|
||||
center={latToLatitude(zoneExtremities.end.center)}
|
||||
radius={zoneExtremities.end.radius}
|
||||
strokeColor={nextZoneStrokeColor}
|
||||
strokeWidth={strokeWidth}
|
||||
lineDashPattern={lineDashPattern}
|
||||
/>
|
||||
);
|
||||
} else if (zoneType === ZoneTypes.polygon) {
|
||||
if (zoneExtremities.begin) items.push(
|
||||
<InvertedPolygon
|
||||
key="game-zone-begin-poly"
|
||||
id="game-zone-begin-poly"
|
||||
coordinates={zoneExtremities.begin.polygon.map(pos => latToLatitude(pos))}
|
||||
fillColor={zoneColor}
|
||||
/>
|
||||
);
|
||||
if (zoneExtremities.end) items.push(
|
||||
<Polygon
|
||||
key="game-zone-end-poly"
|
||||
coordinates={zoneExtremities.end.polygon.map(pos => latToLatitude(pos))}
|
||||
strokeColor={nextZoneStrokeColor}
|
||||
strokeWidth={strokeWidth}
|
||||
lineDashPattern={lineDashPattern}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return items.length ? items : null;
|
||||
}, [gameState, zoneType, zoneExtremities]);
|
||||
|
||||
const currentPositionMarker = useMemo(() => {
|
||||
if (!location) return null;
|
||||
|
||||
return (
|
||||
<Marker key={"current-position-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={styles.markerImage} resizeMode="contain"/>
|
||||
</Marker>
|
||||
);
|
||||
}, [location]);
|
||||
|
||||
const lastPositionMarker = useMemo(() => {
|
||||
if (gameState != GameState.PLAYING || !lastSentLocation || hasHandicap) return null;
|
||||
|
||||
return (
|
||||
<Marker key={"last-position-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={styles.markerImage} resizeMode="contain"/>
|
||||
</Marker>
|
||||
);
|
||||
}, [gameState, hasHandicap, lastSentLocation]);
|
||||
|
||||
const enemyPositionMarker = useMemo(() => {
|
||||
if (gameState != GameState.PLAYING || !enemyLocation || hasHandicap) return null;
|
||||
|
||||
return (
|
||||
<Marker key={"enemy-position-marker"} coordinate={{ latitude: enemyLocation[0], longitude: enemyLocation[1] }} anchor={{ x: 0.33, y: 0.33 }} onPress={() => Alert.alert("Position ennemie", "Ceci est la dernière position de vos ennemis connue")}>
|
||||
<Image source={require("../assets/images/marker/red.png")} style={styles.markerImage} resizeMode="contain"/>
|
||||
</Marker>
|
||||
);
|
||||
}, [gameState, hasHandicap, enemyLocation]);
|
||||
|
||||
|
||||
return (
|
||||
<View style={styles.mapContainer}>
|
||||
<View style={styles.container}>
|
||||
<MapView ref={mapRef} style={{flex: 1}} initialRegion={InitialRegions.paris} mapType="standard" onTouchMove={() => setCenterMap(false)} toolbarEnabled={false}>
|
||||
{ gameState == GameState.PLACEMENT && startingArea &&
|
||||
<Circle center={{ latitude: startingArea.center.lat, longitude: startingArea.center.lng }} radius={startingArea.radius} strokeWidth={2} strokeColor={`rgba(0, 0, 255, 1)`} fillColor={`rgba(0, 0, 255, 0.2)`}/>
|
||||
}
|
||||
{ gameState == GameState.PLAYING && zoneExtremities &&
|
||||
<Fragment>
|
||||
{ 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} />}
|
||||
{ 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>
|
||||
}
|
||||
{ location &&
|
||||
<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"/>
|
||||
</Marker>
|
||||
}
|
||||
{ gameState == GameState.PLAYING && lastSentLocation && !hasHandicap &&
|
||||
<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"/>
|
||||
</Marker>
|
||||
}
|
||||
{ gameState == GameState.PLAYING && enemyLocation && !hasHandicap &&
|
||||
<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" onPress={() => Alert.alert("Position ennemie", "Ceci est la dernière position de vos ennemis connue")}/>
|
||||
</Marker>
|
||||
}
|
||||
{startZone}
|
||||
{gameZone}
|
||||
{currentPositionMarker}
|
||||
{lastPositionMarker}
|
||||
{enemyPositionMarker}
|
||||
</MapView>
|
||||
<LinearGradient colors={['rgba(0,0,0,0.3)', 'rgba(0,0,0,0)']} style={{height: 40, width: "100%", position: "absolute"}}/>
|
||||
{ !centerMap &&
|
||||
<TouchableOpacity style={styles.centerMapContainer} onPress={() => setCenterMap(true)}>
|
||||
<TouchableOpacity style={styles.centerMap} onPress={() => setCenterMap(true)}>
|
||||
<Image source={require("../assets/images/centerMap.png")} style={{width: 30, height: 30}} resizeMode="contain"></Image>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
{ gameState == GameState.PLAYING && !captured &&
|
||||
<View style={styles.toolBarRight}>
|
||||
{ !hasHandicap &&
|
||||
<TouchableOpacity style={styles.updatePositionContainer} onPress={sendCurrentPosition}>
|
||||
<Image source={require("../assets/images/update_position.png")} style={{width: 40, height: 40}} resizeMode="contain"></Image>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
</View>
|
||||
}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
mapContainer: {
|
||||
container: {
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
borderTopLeftRadius: 30,
|
||||
borderTopRightRadius: 30,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
centerMapContainer: {
|
||||
centerMap: {
|
||||
position: 'absolute',
|
||||
right: 20,
|
||||
top: 20,
|
||||
@@ -96,19 +164,8 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
toolBarRight: {
|
||||
position: 'absolute',
|
||||
right: 30,
|
||||
bottom: 80
|
||||
},
|
||||
updatePositionContainer: {
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderRadius: 30,
|
||||
backgroundColor: 'white',
|
||||
borderWidth: 4,
|
||||
borderColor: 'black',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
markerImage: {
|
||||
width: 24,
|
||||
height: 24
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// React
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
// Util
|
||||
import { secondsToMMSS } from '../util/format';
|
||||
import { secondsToMMSS } from '../util/functions';
|
||||
|
||||
export const TimerMMSS = ({ title, seconds, style }) => {
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
export const circleToPolygon = (circle) => {
|
||||
// circle : {center: {latitude: ..., longitude: ...}, radius: ...}
|
||||
// polygon : [{latitude: ..., longitude: ...}, ...]
|
||||
const polygon = [];
|
||||
const center = circle.center;
|
||||
const radiusInDegrees = circle.radius / 111320; // Approximation m -> deg
|
||||
|
||||
for (let i = 0; i < 360; i += 5) {
|
||||
const rad = (i * Math.PI) / 180;
|
||||
polygon.push({
|
||||
latitude: center.latitude + radiusInDegrees * Math.sin(rad),
|
||||
longitude: center.longitude + radiusInDegrees * Math.cos(rad) / Math.cos(center.latitude * Math.PI / 180),
|
||||
});
|
||||
}
|
||||
|
||||
return polygon;
|
||||
};
|
||||
|
||||
export const secondsToMMSS = (seconds) => {
|
||||
if (!Number.isInteger(seconds)) return "Inconnue";
|
||||
if (seconds < 0) seconds = 0;
|
||||
Reference in New Issue
Block a user