func (dijk *DijkstraGrid) handleFrontOfQueue() (queueIsEmpty bool) {
	row, col := dijk.queue[0][0], dijk.queue[0][1]
	cell := dijk.grid[row][col]
	cell.seen = true
	if ascii := int(cell.value[0]) - 'a'; ascii >= 0 && ascii < 26 {
		cell.keysFound[cell.value] = true
	}
	if ascii := int(cell.value[0]) - 'A'; ascii >= 0 && ascii < 26 {
		cell.keysNeeded[cast.ASCIIIntToChar('a'+ascii)] = true
	}
	for i := 0; i < 4; i++ {
		neighborRow, neighborCol := row+dRow[i], col+dCol[i]
		neighborCell := dijk.grid[neighborRow][neighborCol]
		if !neighborCell.seen && neighborCell.value != "#" {
			dijk.queue = append(dijk.queue, [2]int{neighborRow, neighborCol})
			neighborCell.distance = cell.distance + 1
			for key := range cell.keysFound {
				neighborCell.keysFound[key] = true
			}
			for key := range cell.keysNeeded {
				neighborCell.keysNeeded[key] = true
			}
		}
	}
	dijk.queue = dijk.queue[1:]
	if len(dijk.queue) == 0 {
		return true
	}
	return false
}
handleFrontOfQueue does just that, returns a true if queue is empty upon completion
package main
import (
	"fmt"
	"math"
	"sort"
	"strings"
	"github.com/alexchao26/advent-of-code-go/cast"
	"github.com/alexchao26/advent-of-code-go/util"
)
func main() {
	input := util.ReadFile("../input.txt")
	linesSli := strings.Split(input, "\n")
	quad1, quad2, quad3, quad4 := generateQuadrants(linesSli)
	one := handleGrid(quad1)
	two := handleGrid(quad2)
	three := handleGrid(quad3)
	four := handleGrid(quad4)
	fmt.Printf("Sum for four robots is %v\n", one+two+three+four)
}
func handleGrid(grid [][]string) int {
	removeDoorsWithoutKeys(grid)
	keysToCoordinates := getCoordinatesOfKeys(grid)
	graph := MakeGraph()
	for key, sourceCoordinates := range keysToCoordinates {
		dijkstraGrid := MakeDijkstraGrid(grid, sourceCoordinates)
		for !dijkstraGrid.handleFrontOfQueue() {
		}
		dijkstraGrid.queue = nil
		for otherKey, destinationCoordinates := range keysToCoordinates {
			if key != otherKey && otherKey != "@" {
				row, col := destinationCoordinates[0], destinationCoordinates[1]
				graph.AddEdges(key, dijkstraGrid.grid[row][col])
			}
		}
	}
	return graph.dfsMinmumDistance()
}
func getCoordinatesOfKeys(grid [][]string) map[string][2]int {
	pointsOfInterest := make(map[string][2]int)
	for row, rowSli := range grid {
		for col, cell := range rowSli {
			switch {
			case cell == "@":
				pointsOfInterest["@"] = [2]int{row, col}
			case int(cell[0]) >= 'a' && int(cell[0]) <= 'z':
				pointsOfInterest[cell] = [2]int{row, col}
			}
		}
	}
	return pointsOfInterest
}
type DijkstraGrid struct {
	grid  [][]*dijkstraNode
	queue [][2]int  
}
type dijkstraNode struct {
	value      string           
	distance   int              
	keysFound  map[string]bool  
	keysNeeded map[string]bool  
	seen       bool
}
func MakeDijkstraGrid(grid [][]string, startCoords [2]int) *DijkstraGrid {
	finalGrid := make([][]*dijkstraNode, len(grid))
	startKey := grid[startCoords[0]][startCoords[1]]
	for row, rowSli := range grid {
		finalGrid[row] = make([]*dijkstraNode, len(grid[0]))
		for col, cellString := range rowSli {
			finalGrid[row][col] = &dijkstraNode{
				value:      cellString,
				distance:   math.MaxInt32,                    
				keysFound:  map[string]bool{startKey: true},  
				keysNeeded: make(map[string]bool),            
				seen:       false,                            
			}
			delete(finalGrid[row][col].keysFound, "@")
		}
	}
	finalGrid[startCoords[0]][startCoords[1]].distance = 0
	finalGrid[startCoords[0]][startCoords[1]].seen = true
	queue := [][2]int{
		[2]int{startCoords[0], startCoords[1]},
	}
	return &DijkstraGrid{finalGrid, queue}
}
var dRow [4]int = [4]int{0, 0, -1, 1}
var dCol [4]int = [4]int{-1, 1, 0, 0}