using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// GAME 1303 Tier 5 — The dachshund. A chain of segments that follows the head's path,
/// grows when you eat, and ends the game if the head runs into the body.
/// Weenie(dog) Maze's core mechanic.
///
/// HOW IT WORKS
/// The head records the cell it leaves at the end of every step. Segment 1 moves to
/// where the head was, segment 2 to where segment 1 was, and so on — the classic snake
/// trick, done as a queue of positions rather than per-segment logic.
///
/// SETUP
/// 1. Head object: Sprite + Collider2D (trigger) + GridMover (Mode = Continuous) +
/// this script. Tag "Player".
/// 2. Body Segment Prefab: a sprite with a Collider2D set to Is Trigger, on a layer
/// called "SnakeBody". No script needed on it.
/// 3. Tail Prefab (optional): a different sprite for the last segment.
/// 4. Starting Length: how many segments to spawn at the start (3 is traditional).
/// 5. Grace Segments: how many segments right behind the head can't kill you. 1 or 2
/// stops unfair deaths on tight turns. Jax's "without running into yourself" is the
/// fail condition — wire OnSelfCollision to GameManager.GameOver.
/// 6. Growth: call Grow() from the treat's Collectible.OnCollected event, or wire
/// CollectionTracker.OnCountChanged if you prefer.
///
/// The wiener-dog joke works nicely here: start at length 3 and the "growing" is the dog
/// getting longer, so make the body sprite a stretched middle section.
/// </summary>
public class SnakeBody : MonoBehaviour
{
[Header("Prefabs")]
public GameObject bodySegmentPrefab;
public GameObject tailPrefab;
[Header("Length")]
public int startingLength = 3;
public int segmentsPerGrow = 1;
[Tooltip("Segments right behind the head that can't kill you (fairness on tight turns).")]
public int graceSegments = 1;
[Tooltip("Max length; 0 = unlimited.")]
public int maxLength = 0;
[Header("Collision")]
public bool selfCollisionKills = true;
[Tooltip("How close the head must be to a segment to count as a hit (fraction of a cell).")]
[Range(0.1f, 0.9f)] public float hitTolerance = 0.5f;
[Header("Events")]
public UnityEvent<int> OnLengthChanged;
public UnityEvent OnGrew;
public UnityEvent OnSelfCollision;
public int Length => segments.Count;
private readonly List<Transform> segments = new List<Transform>();
private readonly List<Vector3> path = new List<Vector3>(); // cells the head has left, newest first
private GridMover head;
private int pendingGrowth;
private bool dead;
void Awake()
{
head = GetComponent<GridMover>();
if (head == null) { Debug.LogError("SnakeBody needs a GridMover on the same object."); enabled = false; }
}
void OnEnable() { if (head != null) head.OnStepCompleted.AddListener(OnHeadStepped); }
void OnDisable() { if (head != null) head.OnStepCompleted.RemoveListener(OnHeadStepped); }
void Start()
{
// Seed the path behind the head so the starting segments have somewhere to sit
Vector3 back = new Vector3(-head.Direction.x, -head.Direction.y, 0f) * head.cellSize;
for (int i = 0; i < startingLength + 2; i++)
path.Add(transform.position + back * (i + 1));
for (int i = 0; i < startingLength; i++) AddSegment();
OnLengthChanged?.Invoke(Length);
}
void OnHeadStepped(Vector3 arrivedAt)
{
if (dead) return;
// The head just left PreviousCell — that's where segment 1 goes.
path.Insert(0, head.PreviousCell);
// Grow by leaving the tail where it is for a step
if (pendingGrowth > 0)
{
AddSegment();
pendingGrowth--;
OnGrew?.Invoke();
OnLengthChanged?.Invoke(Length);
}
// Move every segment one position along the path
for (int i = 0; i < segments.Count; i++)
if (i < path.Count) segments[i].position = path[i];
// Trim history we no longer need
int keep = segments.Count + 2;
while (path.Count > keep) path.RemoveAt(path.Count - 1);
CheckSelfCollision(arrivedAt);
UpdateTailSprite();
}
void CheckSelfCollision(Vector3 headPos)
{
if (!selfCollisionKills || dead) return;
float threshold = head.cellSize * hitTolerance;
for (int i = graceSegments; i < segments.Count; i++)
{
if (segments[i] == null) continue;
if (Vector3.Distance(headPos, segments[i].position) < threshold)
{
dead = true;
head.Halt();
OnSelfCollision?.Invoke();
return;
}
}
}
void AddSegment()
{
if (bodySegmentPrefab == null) return;
if (maxLength > 0 && segments.Count >= maxLength) return;
int index = segments.Count;
Vector3 spawnAt = index < path.Count ? path[index] : transform.position;
var seg = Instantiate(bodySegmentPrefab, spawnAt, Quaternion.identity, transform.parent);
seg.name = $"Segment_{index + 1}";
segments.Add(seg.transform);
}
void UpdateTailSprite()
{
if (tailPrefab == null || segments.Count == 0) return;
// Simple approach: the last segment keeps a distinct sprite, applied once.
var last = segments[segments.Count - 1];
if (last != null && last.name.StartsWith("Segment_"))
{
var srPrefab = tailPrefab.GetComponent<SpriteRenderer>();
var srLast = last.GetComponent<SpriteRenderer>();
if (srPrefab != null && srLast != null && srLast.sprite != srPrefab.sprite)
{
// restore the previous one to a body sprite
if (segments.Count > 1)
{
var prev = segments[segments.Count - 2].GetComponent<SpriteRenderer>();
var body = bodySegmentPrefab.GetComponent<SpriteRenderer>();
if (prev != null && body != null) prev.sprite = body.sprite;
}
srLast.sprite = srPrefab.sprite;
}
}
}
/// <summary>Wire the treat's Collectible.OnCollected here.</summary>
public void Grow() => pendingGrowth += segmentsPerGrow;
public void Grow(int amount) => pendingGrowth += amount;
/// <summary>Back to starting length (for a retry without reloading the scene).</summary>
public void ResetSnake()
{
foreach (var s in segments) if (s != null) Destroy(s.gameObject);
segments.Clear();
path.Clear();
pendingGrowth = 0;
dead = false;
Vector3 back = new Vector3(-head.Direction.x, -head.Direction.y, 0f) * head.cellSize;
for (int i = 0; i < startingLength + 2; i++)
path.Add(transform.position + back * (i + 1));
for (int i = 0; i < startingLength; i++) AddSegment();
head.ResetSpeed();
OnLengthChanged?.Invoke(Length);
}
}