# Space Traveller

**Catégorie:** Divers - **Difficulté:** Facile

{% file src="/files/yDJfkxjBZxkwpFQGiKKH" %}

**Description:**

<figure><img src="/files/MK7z0QtgaQIYuK7sQ1ab" alt=""><figcaption></figcaption></figure>

Solution:

Voici le script afin de terminer le jeu (infernal) :&#x20;

```python



import json
import time
import random

sio = socketio.Client(logger=False, engineio_logger=False)
score = 0
flag = None
game_started_successfully = False
target_score = 90

waves_pending_completion = [] 

PLAYER_X_POS = 50
PLAYER_WIDTH = 49
PLAYER_HEIGHT = 40 
ASTEROID_WIDTH = 55
ASTEROID_HEIGHT = 48
ASTEROID_START_X = 400
TARGET_ASTEROID_X_FOR_COMPLETION = -2.0 
GAME_TICK_INTERVAL_SECONDS = 0.030 
MAX_PLAYER_Y = 600 # Hauteur du canvas du jeu
EFFECTIVE_MAX_PLAYER_Y = MAX_PLAYER_Y - PLAYER_HEIGHT # Max Y pour le coin supérieur gauche du joueur

@sio.event
def connect():
    print("Connecté au serveur !")
    print("Envoi de game_start...")
    sio.emit("message", "game_start")
    global game_started_successfully
    game_started_successfully = True

@sio.event
def connect_error(data):
    print(f"Erreur de connexion : {data}")

def is_player_safe(player_y, spawns_for_wave):
    player_top = player_y
    player_bottom = player_y + PLAYER_HEIGHT
    for asteroid in spawns_for_wave:
        asteroid_top = asteroid['y']
        asteroid_bottom = asteroid['y'] + ASTEROID_HEIGHT
        # Condition de collision (chevauchement vertical)
        # Le joueur est touché si le bas du joueur est sous le haut de l'astéroïde ET
        # le haut du joueur est au-dessus du bas de l'astéroïde.
        # C'est-à-dire : player_bottom > asteroid_top AND player_top < asteroid_bottom
        if player_bottom > asteroid_top and player_top < asteroid_bottom:
            return False # Collision
    return True # Pas de collision

@sio.on("message")
def on_server_message(type_interne, data_interne=None):
    global score, flag, waves_pending_completion

    if type_interne == "message":
        if data_interne is None:
            print("Erreur: type_interne est 'message' mais data_interne est None.")
            return
        try:
            event_data = json.loads(data_interne)
            actual_event_type = event_data.get("event")

            if actual_event_type == "score_up":
                score += 1
                print(f"Score augmenté ! Score actuel : {score}")
                if score >= target_score and not flag:
                    print(f"Score de {target_score} atteint ou dépassé ! En attente du flag.")
            
            elif actual_event_type == "reward":
                flag = event_data.get("flag")
                print(f"Récompense reçue ! Flag : {flag}")
                if flag: 
                    sio.disconnect()

            elif actual_event_type == "new_wave":
                wave_index = event_data.get("i")
                spawns = event_data.get("spawns", [])
                
                chosen_player_y_for_wave = EFFECTIVE_MAX_PLAYER_Y // 2 # Default
                if spawns:
                    speed = spawns[0].get("speed", 5) 
                    distance_to_travel = ASTEROID_START_X - TARGET_ASTEROID_X_FOR_COMPLETION
                    ticks_needed = distance_to_travel / speed
                    base_delay_seconds = ticks_needed * GAME_TICK_INTERVAL_SECONDS
                    
                    found_safe_y = False
                    # Essayer de trouver une position Y sûre par incréments
                    for y_candidate in range(0, EFFECTIVE_MAX_PLAYER_Y + 1, 5):
                        if is_player_safe(y_candidate, spawns):
                            chosen_player_y_for_wave = y_candidate
                            found_safe_y = True
                            break
                    if not found_safe_y:
                        # Si aucune position sûre n'est trouvée par incréments, essayer des positions aléatoires
                        for _ in range(10): # 10 tentatives aléatoires
                            y_candidate = random.randint(0, EFFECTIVE_MAX_PLAYER_Y)
                            if is_player_safe(y_candidate, spawns):
                                chosen_player_y_for_wave = y_candidate
                                found_safe_y = True
                                break
                    if not found_safe_y:
                         print(f"AVERTISSEMENT: Aucune position Y sûre trouvée pour la vague {wave_index}. Utilisation de {chosen_player_y_for_wave}.")

                    actual_completion_delay = base_delay_seconds + random.uniform(0.05, 0.15) # Petit délai aléatoire
                    completion_time = time.time() + actual_completion_delay
                    
                    waves_pending_completion.append({
                        "index": wave_index, 
                        "completion_time": completion_time, 
                        "playerY_at_completion": chosen_player_y_for_wave
                    })
                    print(f"Nouvelle vague {wave_index} (speed {speed:.2f}). Player_Y choisi: {chosen_player_y_for_wave}. Complétion en ~{actual_completion_delay:.2f}s.")
                else: # Pas de spawns, vague vide ?
                    print(f"Nouvelle vague {wave_index} sans données de spawn. Utilisation d'un délai par défaut et Y par défaut.")
                    completion_time = time.time() + random.uniform(1.0, 1.5)
                    waves_pending_completion.append({"index": wave_index, "completion_time": completion_time, "playerY_at_completion": chosen_player_y_for_wave})

            elif actual_event_type == "game_over":
                print("Game Over reçu du serveur.")
                if not flag:
                    sio.disconnect()

        except json.JSONDecodeError:
            print(f"Erreur de décodage JSON pour les données : {data_interne}")
        except Exception as e:
            print(f"Erreur lors du traitement du message (type interne 'message') du serveur : {e}, données: {data_interne}")
    
    elif type_interne == "ping":
        sio.emit("message", "pong") 

@sio.event
def disconnect():
    print("Déconnecté du serveur.")

def process_pending_waves():
    global waves_pending_completion
    if not sio.connected or not game_started_successfully or flag:
        return

    current_time = time.time()
    waves_pending_completion.sort(key=lambda w: w["completion_time"]) 
    
    processed_indices_this_tick = set()
    temp_pending_waves = []

    for wave_info in waves_pending_completion:
        if current_time >= wave_info["completion_time"] and wave_info["index"] not in processed_indices_this_tick:
            payload = {"playerY": wave_info["playerY_at_completion"], "waveIndex": wave_info["index"]}
            print(f"Envoi de wave_completed pour l'index: {wave_info['index']} avec playerY: {wave_info['playerY_at_completion']}")
            sio.emit("message", ("wave_completed", json.dumps(payload)))
            processed_indices_this_tick.add(wave_info["index"])
        else:
            temp_pending_waves.append(wave_info)
    waves_pending_completion = temp_pending_waves

# Programme principal
if __name__ == '__main__':
    try:
        print("Tentative de connexion à wss://space-traveler.404ctf.fr/")
        sio.connect('wss://space-traveler.404ctf.fr/', transports=['websocket'], socketio_path='socket.io')
        
        start_time = time.time()
        timeout_seconds = 300 # 5 minutes

        while not game_started_successfully and (time.time() - start_time) < 5:
            time.sleep(0.1)
        
        if not game_started_successfully:
            print("Échec du démarrage du jeu.")
        else:
            print("Jeu démarré. Boucle principale.")
            while sio.connected and not flag and (time.time() - start_time) < timeout_seconds:
                process_pending_waves()
                time.sleep(GAME_TICK_INTERVAL_SECONDS / 2) 
                
                if score >= target_score and not flag:
                    print(f"Score de {score} atteint. Attente du flag...")

        if not flag and (time.time() - start_time) >= timeout_seconds:
            print(f"Timeout: Flag non obtenu après {timeout_seconds} secondes.")
        elif not sio.connected and not flag and not ((time.time() - start_time) >= timeout_seconds) :
             print(f"Déconnecté avant d'obtenir le flag (et avant timeout). Score: {score}")
        elif not flag:
            print(f"Flag non obtenu après la boucle principale (score: {score}).")

    except socketio.exceptions.ConnectionError as e:
        print(f"Erreur de connexion Socket.IO : {e}")
    except Exception as e:
        print(f"Une erreur inattendue est survenue : {e}")
    finally:
        if sio.connected:
            sio.disconnect()
        print(f"Script terminé. Score final: {score}, Flag: {flag if flag else 'Non obtenu'}")
```

Ce script a été créé en étudiant toutes les fonctions JS qui tournent en arrière plan de l'application web.\
Lorsque nous lançons ce script, voici ce que nous obtenons (étape par étape) :&#x20;

<figure><img src="/files/YQZ6Yd4ce086RsNnCeXn" alt=""><figcaption></figcaption></figure>

\[...]<br>

<figure><img src="/files/i2urRAbjDqR4RnShaLhX" alt=""><figcaption></figcaption></figure>

<details>

<summary>🚩FLAG</summary>

`404CTF{TR1CH3R_C'4ST_B13N_1_B0N_G4M3_D3S1GN_C'4ST_M13UX}`

</details>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://writeups.ayweth20.com/2025/404ctf-2025/divers/space-traveller.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
