Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/tutosrive/avl_tree_car/llms.txt

Use this file to discover all available pages before exploring further.

The Obstacle class is the core value stored in every AVL tree node. It represents a road hazard placed at a precise (x, y) position on the simulation road. Every structural operation the tree performs — insertion, search, deletion, traversal — ultimately works with Obstacle instances, making a solid understanding of this model essential before exploring the tree itself.

Obstacle Class

An Obstacle is constructed with three arguments: the x and y coordinates (both float) that fix its position on the road, and an integer type_id that maps to one of the ten predefined hazard categories. The id field is auto-generated at construction time using RandomsUtils.random_id.
# Should have a RECT collider for collide with the CAR
# Every Node have a collider

from src.utils.randoms_utils import RandomsUtils


class Obstacle:
    def __init__(self, x: float, y: float, type_id: int) -> None:
        self.id = RandomsUtils.random_id(8, 2)
        self.x = x
        self.y = y
        self.type_id = type_id

    def to_dict(self) -> dict:
        return {
            'id': self.id,
            'x': self.x,
            'y': self.y,
            'type_id': self.type_id,
        }
to_dict() returns a flat dictionary suitable for JSON serialisation over Socket.IO or REST:
{
  "id": "0A2B4C6D",
  "x": 100.0,
  "y": 20.0,
  "type_id": 2
}

ID Generation

Every obstacle receives an 8-character string ID generated by RandomsUtils.random_id(8, 2). The second argument (int_every=2) controls the pattern: even-indexed positions (0, 2, 4, 6) receive a random digit (0–9) and odd-indexed positions (1, 3, 5, 7) receive a random capital letter (A–Z). This alternating scheme produces IDs such as 0A2B4C6D or 1X3Y5Z7W.
import random as rd

class RandomsUtils:
    @staticmethod
    def random_id(length: int, int_every: int | None = None, capital: bool = True) -> str:
        key, rd_letter, rd_int = '', '', -1

        for i in range(length):
            # Add a random capital letter
            rd_letter = RandomsUtils.random_letter(capital)

            if int_every:
                # It's in the position "int_every" => int_every = 2; par positions, every 2 positions
                if i % int_every == 0:
                    # Generate and add random int
                    rd_int = RandomsUtils.random_int(0, 9)
                    key += str(rd_int)
                else:
                    key += rd_letter
            else:
                key += rd_letter
        return key

    @staticmethod
    def random_letter(capital: bool = True) -> str:
        letter, __randint = '', -1

        range_ascii_capital = [65, 90]
        range_ascii = [97, 122]

        __default = range_ascii_capital

        if not capital:
            __default = range_ascii

        __randint = rd.randint(__default[0], __default[1])
        letter = chr(__randint)

        return letter

    @staticmethod
    def random_int(start: int, end: int) -> int:
        return rd.randint(start, end)
Example IDs that follow the digit-letter-digit-letter-… pattern:
0A2B4C6D
1X3Y5Z7W
4M8P2K9R
7Q1N5W3T

Type IDs

Each obstacle carries a type_id integer that the frontend maps to a specific sprite, collision hitbox, and damage value. The ten available types are defined in obstacles_types.json:
IDTypeDamage
1Cone0.5
2Rock1
3Tree10
4Tire5
5Nail4
6Trunk6
7Person3
8Car15
9Bicycle7
10Chair6
The raw JSON file at src/data/obstacles_types.json:
[
  { "id": 1, "type": "Cone", "damage": 0.5 },
  { "id": 2, "type": "Rock", "damage": 1 },
  { "id": 3, "type": "Tree", "damage": 10 },
  { "id": 4, "type": "Tire", "damage": 5 },
  { "id": 5, "type": "Nail", "damage": 4 },
  { "id": 6, "type": "Trunk", "damage": 6 },
  { "id": 7, "type": "Person", "damage": 3 },
  { "id": 8, "type": "Car", "damage": 15 },
  { "id": 9, "type": "Bicycle", "damage": 7 },
  { "id": 10, "type": "Chair", "damage": 6 }
]

Node Wrapper

Obstacle objects are never stored directly in the tree — they are wrapped in a Node. Each Node holds the obstacle as value, plus left/right child pointers, a parent pointer, and the height integer used for balance-factor calculations. Newly created nodes start with height = 1.
from src.models.obstacle import Obstacle

class Node:
    def __init__(self, value: Obstacle, parent: 'Node' = None):
        self.left: 'Node' = None
        self.right: 'Node' = None
        self.height: int = 1
        self.value = value
        self.parent = parent

    def to_dict(self) -> dict:
        return {
            'value': self.value.to_dict(),
            'left': self.__get_node_dict(),
            'right': self.__get_node_dict(True),
            'height': self.height
        }

    def __get_node_dict(self, is_right: bool = False) -> dict | None:
        __return = None

        if not is_right and self.left:
                __return = self.left.to_dict()
        elif self.right:
                __return = self.right.to_dict()

        return __return
Node.to_dict() serializes the full subtree rooted at that node, embedding the obstacle’s to_dict() output under the value key alongside the recursively serialized left and right children and the node’s height.

Uniqueness Constraint

Two obstacles are considered duplicates when they share the same (x, y) coordinate pair. The AVLTree.insert method calls search before inserting and rejects the new obstacle if a node with matching x and y already exists. The x-only BST ordering means two obstacles can share the same x value at different y positions and coexist in the tree; only an exact (x, y) match is refused.

Build docs developers (and LLMs) love