using System; using System.Collections.Generic; using System.IO; using UnityEngine; namespace Code.Lab6 { public class GraphGrid : Graph { public GameObject obstaclePrefab; public string mapName = "arena.map"; public bool get8Vicinity = false; public float cellSize = 1f; [Range(0, Mathf.Infinity)] public float defaultCost = 1f; private const string MapsDir = "Resources/Maps"; private int _numCols; private int _numRows; private GameObject[] _vertexObj; private bool[,] _mapVertices; public bool useWeightedMap = true; public Texture2D weightMapTexture; public float minWeight = 0.5f; public float maxWeight = 5f; private Dictionary _gridIdToVertexIndex; private int GridToId(int x, int y) { return _numCols * y + x; } private Vector2Int IdToGrid(int id) { return new Vector2Int(id % _numCols, id / _numCols); } private void LoadMapWithWeights(string filename) { var path = Application.dataPath + "/" + MapsDir + "/" + filename; try { var strmRdr = new StreamReader(path); using (strmRdr) { strmRdr.ReadLine(); var line = strmRdr.ReadLine(); if (line != null && line.StartsWith("height")) _numRows = int.Parse(line.Split(' ')[1]); line = strmRdr.ReadLine(); if (line != null && line.StartsWith("width")) _numCols = int.Parse(line.Split(' ')[1]); strmRdr.ReadLine(); Vertices = new List(); Neighbours = new List>(); Costs = new List>(); _vertexObj = new GameObject[_numRows * _numCols]; _mapVertices = new bool[_numRows, _numCols]; _gridIdToVertexIndex = new Dictionary(); Debug.Log($"Loading weighted map {_numRows}x{_numCols}"); for (var row = 0; row < _numRows; row++) { line = strmRdr.ReadLine(); if (string.IsNullOrEmpty(line)) continue; for (var col = 0; col < _numCols && col < line.Length; col++) { var cellChar = line[col]; var isGround = cellChar != '#'; _mapVertices[row, col] = isGround; var position = new Vector3(col * cellSize, 0f, row * cellSize); var gridId = GridToId(col, row); GameObject obj; if (isGround && vertexPrefab != null) { obj = Instantiate(vertexPrefab, position, Quaternion.identity); } else if (!isGround && obstaclePrefab != null) { obj = Instantiate(obstaclePrefab, position, Quaternion.identity); } else { obj = GameObject.CreatePrimitive(PrimitiveType.Cube); obj.transform.position = position; } if (obj == null) continue; obj.name = $"{cellChar}_{gridId}"; obj.transform.parent = transform; var scale = obj.transform.localScale; scale.x = cellSize; scale.z = cellSize; scale.y = isGround ? 0.1f : 1f; obj.transform.localScale = scale; _vertexObj[gridId] = obj; if (!isGround) continue; var v = obj.GetComponent(); if (v == null) v = obj.AddComponent(); v.id = gridId; v.neighbours = new List(); v.weight = GetWeightFromChar(cellChar); var renderer1 = obj.GetComponent(); if (renderer1 != null) { renderer1.material.color = cellChar switch { 'r' => new Color(0.8f, 0.2f, 0.2f), 'm' => new Color(0.6f, 0.4f, 0.2f), 'h' => new Color(0.2f, 0.8f, 0.2f), _ => new Color(0.8f, 0.8f, 0.8f) }; } var vertexIndex = Vertices.Count; Vertices.Add(v); Neighbours.Add(new List()); Costs.Add(new List()); _gridIdToVertexIndex[gridId] = vertexIndex; } } foreach (var kvp in _gridIdToVertexIndex) { var gridId = kvp.Key; var vertexIndex = kvp.Value; var gridPos = IdToGrid(gridId); SetNeighboursWithWeights(gridPos.x, gridPos.y, vertexIndex); } Debug.Log($"Weighted map loaded. {Vertices.Count} walkable vertices"); } } catch (Exception e) { Debug.LogError($"Failed to load weighted map: {e.Message}"); } } private float GetWeightFromChar(char c) { return c switch { 'h' => 0.5f, '.' => 1f, 'm' => 3f, 'r' => 5f, '#' => float.MaxValue, _ => 1f }; } private float CalculateVertexWeight(int row, int col, Texture2D weightTexture) { if (weightTexture != null) { var texX = Mathf.FloorToInt((float)col / _numCols * weightTexture.width); var texY = Mathf.FloorToInt((float)row / _numRows * weightTexture.height); var pixel = weightTexture.GetPixel(texX, texY); var gray = pixel.grayscale; return Mathf.Lerp(minWeight, maxWeight, gray); } var centerX = _numCols / 2f; var centerY = _numRows / 2f; var distFromCenter = Vector2.Distance(new Vector2(col, row), new Vector2(centerX, centerY)); var maxDist = Mathf.Max(_numCols, _numRows) / 2f; var centerWeight = Mathf.Lerp(minWeight, maxWeight, distFromCenter / maxDist); var noise = Mathf.PerlinNoise(col * 0.1f, row * 0.1f); var noiseWeight = Mathf.Lerp(minWeight, maxWeight, noise); var riverX = _numCols * 0.3f + Mathf.Sin(row * 0.3f) * 5f; var distToRiver = Mathf.Abs(col - riverX); var riverWeight = distToRiver < 3f ? maxWeight : minWeight; var finalWeight = (centerWeight * 0.3f + noiseWeight * 0.3f + riverWeight * 0.4f); return Mathf.Clamp(finalWeight, minWeight, maxWeight); } protected void SetNeighboursWithWeights(int x, int y, int vertexIndex) { var currentVertex = Vertices[vertexIndex]; Neighbours[vertexIndex].Clear(); Costs[vertexIndex].Clear(); currentVertex.neighbours.Clear(); Vector2Int[] directions; if (get8Vicinity) { directions = new[] { new Vector2Int(x, y - 1), new Vector2Int(x + 1, y - 1), new Vector2Int(x + 1, y), new Vector2Int(x + 1, y + 1), new Vector2Int(x, y + 1), new Vector2Int(x - 1, y + 1), new Vector2Int(x - 1, y), new Vector2Int(x - 1, y - 1) }; } else { directions = new[] { new Vector2Int(x, y - 1), new Vector2Int(x + 1, y), new Vector2Int(x, y + 1), new Vector2Int(x - 1, y) }; } const float diagonalMultiplier = 1.4f; foreach (var dir in directions) { if (dir.x < 0 || dir.x >= _numCols || dir.y < 0 || dir.y >= _numRows) continue; if (!_mapVertices[dir.y, dir.x]) continue; var neighbourGridId = GridToId(dir.x, dir.y); if (!_gridIdToVertexIndex.TryGetValue(neighbourGridId, out var neighbourIndex)) continue; var neighbourVertex = Vertices[neighbourIndex]; var baseCost = (currentVertex.weight + neighbourVertex.weight) / 2f; if (dir.x != x && dir.y != y) { baseCost *= diagonalMultiplier; } Neighbours[vertexIndex].Add(neighbourVertex); Costs[vertexIndex].Add(baseCost); currentVertex.neighbours.Add(new Edge(neighbourVertex, baseCost)); } } public void GenerateLargeWeightedMap(int width, int height) { _numCols = width; _numRows = height; Vertices = new List(); Neighbours = new List>(); Costs = new List>(); _vertexObj = new GameObject[width * height]; _mapVertices = new bool[height, width]; _gridIdToVertexIndex = new Dictionary(); for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { _mapVertices[y, x] = true; var position = new Vector3(x * cellSize, 0f, y * cellSize); var gridId = GridToId(x, y); var obj = Instantiate(vertexPrefab, position, Quaternion.identity); obj.name = $"Vertex_{gridId}"; obj.transform.parent = transform; var v = obj.GetComponent(); if (v == null) v = obj.AddComponent(); v.id = gridId; v.neighbours = new List(); v.weight = CalculateVertexWeight(y, x, null); var renderer1 = obj.GetComponent(); if (renderer1 != null) { var t = Mathf.InverseLerp(minWeight, maxWeight, v.weight); renderer1.material.color = Color.Lerp(Color.white, Color.black, t); } var vertexIndex = Vertices.Count; Vertices.Add(v); Neighbours.Add(new List()); Costs.Add(new List()); _gridIdToVertexIndex[gridId] = vertexIndex; } } for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { var gridId = GridToId(x, y); if (_gridIdToVertexIndex.TryGetValue(gridId, out var vertexIndex)) { SetNeighboursWithWeights(x, y, vertexIndex); } } } Debug.Log($"Generated large weighted map: {width}x{height} ({Vertices.Count} vertices)"); } public override void Load() { if (useWeightedMap) { LoadMapWithWeights(mapName); } else { LoadMap(mapName); } } private void LoadMap(string filename) { var path = Application.dataPath + "/" + MapsDir + "/" + filename; try { var strmRdr = new StreamReader(path); using (strmRdr) { strmRdr.ReadLine(); var line = strmRdr.ReadLine(); if (line != null && line.StartsWith("height")) { _numRows = int.Parse(line.Split(' ')[1]); } else { Debug.LogError("Invalid map format: height line not found"); return; } line = strmRdr.ReadLine(); if (line != null && line.StartsWith("width")) { _numCols = int.Parse(line.Split(' ')[1]); } else { Debug.LogError("Invalid map format: width line not found"); return; } strmRdr.ReadLine(); var totalCells = _numRows * _numCols; Vertices = new List(); Neighbours = new List>(); Costs = new List>(); _vertexObj = new GameObject[totalCells]; _mapVertices = new bool[_numRows, _numCols]; _gridIdToVertexIndex = new Dictionary(); Debug.Log($"Loading map {_numRows}x{_numCols} (total cells: {totalCells})"); for (var row = 0; row < _numRows; row++) { line = strmRdr.ReadLine(); if (string.IsNullOrEmpty(line) || line.Length < _numCols) { Debug.LogError($"Map line {row} is too short or missing"); continue; } for (var col = 0; col < _numCols; col++) { var cellChar = line[col]; var isGround = (cellChar == '.' || cellChar == 'G' || cellChar == ' '); _mapVertices[row, col] = isGround; var position = new Vector3(col * cellSize, 0f, row * cellSize); var gridId = GridToId(col, row); GameObject obj; if (isGround && vertexPrefab != null) { obj = Instantiate(vertexPrefab, position, Quaternion.identity); } else if (!isGround && obstaclePrefab != null) { obj = Instantiate(obstaclePrefab, position, Quaternion.identity); } else { obj = GameObject.CreatePrimitive(PrimitiveType.Cube); obj.transform.position = position; var renderer1 = obj.GetComponent(); if (renderer1 != null) { renderer1.material.color = isGround ? Color.green : Color.red; } } if (obj == null) continue; obj.name = $"{(isGround ? "Ground" : "Obstacle")}_{gridId}"; obj.transform.parent = transform; var scale = obj.transform.localScale; scale.x = cellSize; scale.z = cellSize; scale.y = isGround ? 0.1f : 1f; obj.transform.localScale = scale; _vertexObj[gridId] = obj; if (!isGround) continue; var v = obj.GetComponent(); if (v == null) v = obj.AddComponent(); v.id = gridId; v.neighbours = new List(); var vertexIndex = Vertices.Count; Vertices.Add(v); Neighbours.Add(new List()); Costs.Add(new List()); _gridIdToVertexIndex[gridId] = vertexIndex; } } Debug.Log($"Created {Vertices.Count} walkable vertices"); foreach (var kvp in _gridIdToVertexIndex) { var gridId = kvp.Key; var vertexIndex = kvp.Value; var gridPos = IdToGrid(gridId); SetNeighbours(gridPos.x, gridPos.y, vertexIndex); } Debug.Log($"Map loaded successfully. {Vertices.Count} walkable vertices"); } } catch (Exception e) { Debug.LogError($"Failed to load map: {e.Message}"); Debug.LogException(e); } } protected void SetNeighbours(int x, int y, int vertexIndex) { var currentVertex = Vertices[vertexIndex]; var gridId = currentVertex.id; Neighbours[vertexIndex].Clear(); Costs[vertexIndex].Clear(); currentVertex.neighbours.Clear(); Vector2Int[] directions; if (get8Vicinity) { directions = new[] { new Vector2Int(x, y - 1), new Vector2Int(x + 1, y - 1), new Vector2Int(x + 1, y), new Vector2Int(x + 1, y + 1), new Vector2Int(x, y + 1), new Vector2Int(x - 1, y + 1), new Vector2Int(x - 1, y), new Vector2Int(x - 1, y - 1) }; } else { directions = new[] { new Vector2Int(x, y - 1), new Vector2Int(x + 1, y), new Vector2Int(x, y + 1), new Vector2Int(x - 1, y) }; } var diagonalCost = defaultCost * 1.4f; foreach (var dir in directions) { if (dir.x < 0 || dir.x >= _numCols || dir.y < 0 || dir.y >= _numRows) continue; if (!_mapVertices[dir.y, dir.x]) continue; var neighbourGridId = GridToId(dir.x, dir.y); if (!_gridIdToVertexIndex.TryGetValue(neighbourGridId, out var neighbourIndex)) continue; var neighbourVertex = Vertices[neighbourIndex]; var cost = defaultCost; if (dir.x != x && dir.y != y) { cost = diagonalCost; } Neighbours[vertexIndex].Add(neighbourVertex); Costs[vertexIndex].Add(cost); currentVertex.neighbours.Add(new Edge(neighbourVertex, cost)); } } public override Vertex GetNearestVertex(Vector3 position) { var col = Mathf.RoundToInt(position.x / cellSize); var row = Mathf.RoundToInt(position.z / cellSize); col = Mathf.Clamp(col, 0, _numCols - 1); row = Mathf.Clamp(row, 0, _numRows - 1); if (!_mapVertices[row, col]) { return FindNearestWalkable(col, row); } var gridId = GridToId(col, row); return _gridIdToVertexIndex.TryGetValue(gridId, out var vertexIndex) ? Vertices[vertexIndex] : null; } private Vertex FindNearestWalkable(int startCol, int startRow) { var queue = new Queue(); var explored = new HashSet(); queue.Enqueue(new Vector2Int(startCol, startRow)); explored.Add(new Vector2Int(startCol, startRow)); const int maxSearchRadius = 20; var steps = 0; while (queue.Count > 0 && steps < maxSearchRadius * maxSearchRadius) { var pos = queue.Dequeue(); if (_mapVertices[pos.y, pos.x]) { var gridId = GridToId(pos.x, pos.y); if (_gridIdToVertexIndex.TryGetValue(gridId, out var vertexIndex)) { return Vertices[vertexIndex]; } } var neighbours = new[] { new Vector2Int(pos.x, pos.y - 1), new Vector2Int(pos.x + 1, pos.y), new Vector2Int(pos.x, pos.y + 1), new Vector2Int(pos.x - 1, pos.y) }; foreach (var n in neighbours) { if (n.x < 0 || n.x >= _numCols || n.y < 0 || n.y >= _numRows || !explored.Add(n)) continue; queue.Enqueue(n); } steps++; } Debug.LogWarning($"No walkable vertex found near ({startCol}, {startRow})"); return Vertices.Count > 0 ? Vertices[0] : null; } public override Vertex[] GetNeighbours(Vertex v) { if (!v || Vertices == null || Neighbours == null || !_gridIdToVertexIndex.TryGetValue(v.id, out var vertexIndex) || vertexIndex < 0 || vertexIndex >= Neighbours.Count || Neighbours[vertexIndex] == null) return Array.Empty(); return Neighbours[vertexIndex].ToArray(); } public override Edge[] GetEdges(Vertex v) { if (!v || v.neighbours == null) return Array.Empty(); return v.neighbours.ToArray(); } public override Vertex GetVertexObj(int id) { if (!_gridIdToVertexIndex.TryGetValue(id, out var vertexIndex)) return null; if (vertexIndex >= 0 && vertexIndex < Vertices.Count) return Vertices[vertexIndex]; return null; } } }