3

Updating this post, once again.

This time to make things clearer. I am trying, to parse in a Grid of size 9x9, but this size can change overtime, its not fixed. This is a board game called Quoridor. What I have at my disposal, is a Board class. This provides me with the following, horizontal bool[,], and vertical bool[,], I can loop over each and print out x, y position. But these differ, depending on if its horizontal direction, or vertical direction and position.

The player can move one step ONLY either north, south, west, or east in terms of direction. The other player (human) can put a wall (obstacle) which covers two blocks horizontally or vertically. My automated player has to build a Graph of Nodes from the board, and refresh the Graph based on the changes on the board and its own position. For example, if the player cannot go to left, from current position, then an edge which connects two nodes will be deleted between them, only if caused by an obstacle. And then the BFS will run again against the Graph and return a new position (x, y) which this (automated) player uses and performs its move.

Every single block on the 9x9 grid, will represent one Node in the Graph. Meaning the number of vertices or nodes List in the Graph will be 9x9=81. Each of the Nodes hold a list or 2D array of size 4 for representing North, South, West, and East which could be of a bool type.

Now, I have provided a sample code of the Graph class I wrote along with a Node class. I hope the latest information herein makes it clear. I have already implemented the BFS algorithm. But this part is what I cannot understand correctly. I watched this video for some ideas: https://www.youtube.com/watch?v=KiCBXu4P-2Y


enter image description here

enter image description here


Code

class Graph<T>
{        
    private int _V;
    private List<T>[] _Nodes;

    public Graph(int v) 
    {
        _V = v;
        _Nodes = new List<T>[v];
        for (int i = 0; i < _Nodes.Length; i++)
            _Nodes [i] = new List<T>();
    }

    public IEnumerable<T> Nodes(int v) { return (IEnumerable<T>)_Nodes[v];}
    public int V { get => _V; }
    public bool EdgeExists(){}
    public void AddEdge(Node n, T u, T w){}
    public void RemoveEdge(){}
}

and,

class Node<T>
{        
    private int _E;    
    private List<T>[] _Adj;

    public Node(int e) 
    {
        _E = e;
        _Adj = new List<T>[e];
        for (int i = 0; i < _Adj.Length; i++)
            _Adj [e] = new List<T>();
    }

    public IEnumerable<T> Adj(int e) { return (IEnumerable<T>)_Adj[e];}
    public int E { get => _E; }

    public void AddEdge(Node n, T u, T w){}
    public void RemoveEdge(T value){}
}

I read the following SO threads:

John Smith
  • 465
  • 4
  • 15
  • 38
  • 4
    What is it you are really trying to do? Without any background information it is difficult to tell if you are on the right track or not. – JonasH Nov 15 '21 at 09:28
  • You take the first **0,0** and then store the closest neighbors to it. – Albin M Nov 15 '21 at 09:29
  • @JonasH, see my EDIT. Thanks for asking. – John Smith Nov 15 '21 at 09:32
  • @JonasH, do you think this is the right direction? I am trying to build a Graph of a grid 9x9 with positions and neighbors for a Quoridor game. – John Smith Nov 15 '21 at 09:39
  • 1
    _And I cannot understand, how to store this information in a Graph with Nodes and Neighbors of 4. _ you don't. You store the Nodes in the graph (can be a simple list). Each node stores information about its neighbors. – c0der Nov 23 '21 at 05:14
  • @c0der, yes, but how is this done, currently I am looping two `bool[,]` 2D arrays, these arrays represent squares in Horizontal and Vertical order. – John Smith Nov 24 '21 at 11:13
  • "depending on if (0,?) is true" what does this mean? – Nigel Nov 24 '21 at 18:09

4 Answers4

0

This is one way to build an adjacency matrix that has the same layout as a 2D array:

internal record Graph
{
    public List<Node> Nodes { get; set; }

    public Graph(int numberOfTiles)
    {
        var matrixSize = (int)Math.Sqrt(numberOfTiles);
        var rows = matrixSize;
        var columns = matrixSize;
        var nodes = new Node[rows, columns];
        for (int row = 0; row < rows; row++)
        {
            for (int column = 0; column < columns; column++)
            {
                nodes[row, column] = new Node(row, column);
            }
        }

        Nodes = new List<Node>(rows * columns);
        foreach (var node in nodes)
        {
            var row = node.Row;
            var column = node.Column;
            if (row > 0) node.West = nodes[row - 1, column];
            if (column > 0) node.North = nodes[row, column - 1];
            if (row < rows - 1) node.East = nodes[row + 1, column];
            if (column < columns - 1) node.South = nodes[row, column + 1];
            Nodes.Add(node);
        }

    }
}

internal record Node
{
    public int Row { get; }
    public int Column { get; }
    public Node[] Neighbors { get; } = new Node[4];
    public Node North
    {
        get => Neighbors[0];
        set => Neighbors[0] = value;
    }
    public Node East
    {
        get => Neighbors[1];
        set => Neighbors[1] = value;
    }
    public Node South
    {
        get => Neighbors[2];
        set => Neighbors[2] = value;
    }
    public Node West
    {
        get => Neighbors[3];
        set => Neighbors[3] = value;
    }
    public Node(int row, int column)
    {
        Row = row;
        Column = column;
    }
}

And this works for BFS. Edges/boundaries are defined by a node's neighbor being null.To create an edge, just set the appropriate neighbor to null. For example to create a horizontal edge, set the East neighbor of the western node to null, and the West neighbor of the eastern node to null.

Nigel
  • 2,961
  • 1
  • 14
  • 32
  • Node will have a list of edges, maximum 4 edges. These four edges represent: W,E,N,S. Based on the values, one is able to determine if the move is going off grid or not i.e. are we within the Grid. – John Smith Nov 24 '21 at 08:58
  • See my Edit. Please. – John Smith Nov 24 '21 at 09:54
  • I am looping, a horizontal and vertical 2D-array. From them I can extract, (0,0), (0,1) <-- horizontal, and (1,0) <-- vertical. Based on them, I can determine if a Node can have an edge or not, depending on if (0,?) is true. These two 2D-arrays are of `bool[,]`. – John Smith Nov 24 '21 at 09:56
  • That is exactly what I have done. UpNeighbor DownNeighbor LeftNeighbor and RightNeighbor are NESW – Nigel Nov 24 '21 at 18:05
  • Given any node you can tell if a move in any direction will take you off the grid: the neighbor in the selected direction will be null – Nigel Nov 24 '21 at 18:07
  • 1
    @JohnSmith Why is your node class generic? – Nigel Nov 24 '21 at 20:23
  • Nigel Bess, it just wrote it Generic. But, it doesn't have to be as such. – John Smith Nov 24 '21 at 21:34
  • Nigel, to build the graph, I am looping over, *a horizontal and vertical 2D-array. From them I can extract, (0,0), (0,1) <-- horizontal, and (1,0) <-- vertical. Based on them, I can determine if a Node can have an edge or not, depending on if (0,?) is true. These two 2D-arrays are of bool[,].* – John Smith Nov 24 '21 at 21:35
  • 1
    @JohnSmith Yes that is exactly what my code does. – Nigel Nov 24 '21 at 21:44
  • 1
    @JohnSmith Regarding the generic thing, what would the type parameter `T` be in normal usage? It seems like T is another node, which actually won't compile the way you have written it – Nigel Nov 24 '21 at 21:45
  • 1
    @JohnSmith since you put a bounty on the question can you please select an answer before the bounty expires? No point letting that reputation go to waste. If you end up selecting my answer I will be happy to help further in chat – Nigel Nov 24 '21 at 21:48
  • Hi, I will get back to you. I am busy with something else atm. I might raise the bounty if it expires. No worries. I need efficient code. Your code isn't. Remember Adjacency list graph. Nodes must have a list of edges, max 4. And it must be efficient O(something). But I get back to you later today. Thx – John Smith Nov 25 '21 at 06:27
  • 1
    @John Smith My algorithm is O(N), Youre not gonna get anything better than that. Every node in my code has 4 edges. What makes you say that my algorithm is innefficient? – Nigel Nov 29 '21 at 18:10
  • Nigel, why aren't you using a 2D array in the Node to store, W, E, N, S the 4 edges? – John Smith Nov 29 '21 at 23:39
  • 1
    @JohnSmith Why would you use a 2D array to store the edges? I made them properties so that you don't have to remember which index in the array maps to which direction. – Nigel Nov 29 '21 at 23:45
  • @JohnSmith your own attempt seems to use a 1D array to store the edges anyway – Nigel Nov 29 '21 at 23:47
  • Because I will be using BFS on this Graph. If someone set an obstacle, the for those coordinates we will remove that edge or connection, and re-run BFS to find a new direction and path. – John Smith Nov 30 '21 at 06:38
  • You can use my approach for BFS… – Nigel Dec 01 '21 at 07:46
  • @JohnSmith So how does my code not work for you? I have built you exactly what you described. – Nigel Dec 02 '21 at 17:51
  • Nigel, I will get back to you on this, thanks a lot in advance. – John Smith Dec 03 '21 at 09:03
  • Nigel Bess, I have updated with a new *Bounty* – John Smith Dec 05 '21 at 19:27
  • @JohnSmith Cool, so how can I improve my answer for your problem? It seems to me that I have already given you the solution – Nigel Dec 06 '21 at 18:14
  • @JohnSmith I have updated my answer to use your nomenclature of "North/East/South/West" as well as storing edges in an array. – Nigel Dec 06 '21 at 18:53
  • **Nigel Bess**, please see my latest update. Read it from the beginning, and hopefully you will understand what I am trying to do here. – John Smith Dec 07 '21 at 19:07
  • 1
    @JohnSmith I'm actually a big fan of that game, so I know exactly what you are trying to do. Now I also see your misunderstanding. Your issue is here: "North, South, West, and East which could be of a bool type.". You don't actually want a bool type. You want north south west and east to be other nodes. In order to set a wall between nodes, just set that neighbor to null – Nigel Dec 07 '21 at 19:13
  • But those represent the edges between nodes, because, if there's an obstacle between two nodes, the edge between them has to be removed. Regarding the bool type, someone I spoke with said so. – John Smith Dec 07 '21 at 19:15
  • @JohnSmith For example. Lets say you place a wall between two nodes that are North/South of each other. For the northern node you set Node.South to null. For the southern node you set Node.North to null. Then when you run BFS, it skips the edges where the node is null – Nigel Dec 07 '21 at 19:15
  • @JohnSmith Please read my example. – Nigel Dec 07 '21 at 19:15
  • Nigel, yes, I read your comment. But, my number one issue, is building the `Graph` with `Nodes` and `Edges` from the **Grid**. As I explained, I to my Disposal, I can access an instance of the current `Board` class and access `Horizontal` and `Vertical` of types `bool[,]` public properties. – John Smith Dec 07 '21 at 19:19
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/239921/discussion-between-john-smith-and-nigel-bess). – John Smith Dec 07 '21 at 19:20
  • Take a look at the constructor for `Graph` in the code I wrote. You pass it the dimensions of the grid and it builds the adjacency matrix. You don't need an `Edge` object. Rather, references from one node to the next *are* the edges – Nigel Dec 07 '21 at 19:21
  • Why are you telling me it doesnt work then? What did you try? What didnt work? My code works – Nigel Dec 13 '21 at 18:15
-1

Based on what you said I understand your question as follows: How the to handle Nodes that on the edges like (x=0,y=0), (x=9,y=5) or (x=9.y=9) ..... you should handle 8 cases

Cases

for case of left top corner the node only have 2 neighbors so set the top and left neighbors Null

Brad Larson
  • 170,088
  • 45
  • 397
  • 571
  • Hi ProWily, welcome! I am trying to figure out, `Node` has one square from the 9x9 grid, meaning that the graph will contain 81 squares. Each Node has 4 edges to denote (W, E, N, S) = West, East, North, South. And I cannot understand, how to store this information in a Graph with Nodes and Neighbors of 4. I have written the code for the Graph and the Node and posted it here. – John Smith Nov 22 '21 at 13:09
  • ProWily, I looked at this link now, seems interesting, https://stackoverflow.com/questions/57582782/how-can-i-find-the-north-east-south-west-and-diagonal-neighbours-in-a-2d-arra Your answer here, doesn't really solve the issue currently. – John Smith Nov 22 '21 at 17:19
  • If we have a 9x9 square matrix we can do the follow: First step, fill the Graph Nodes List with all Nodes. Second Step, make a loop to handle the neighbors. if we say the Node in the middle of the Graph then, currentNode.LeftNode = NodeList[currentNodePosition-1] currentNode.RightNode = NodeList[currentNodePosition+1] currentNode.TopNode = NodeList[currentNodePosition+9] currentNode.BottomNode = NodeList[currentNodePosition-9]. and as I said before you should handle the null cases in the picture above. I hop you get the point – ProWily Nov 22 '21 at 21:40
  • Unfortunately I don't understand. Explain more. – John Smith Nov 22 '21 at 22:15
  • 1
    if you fill a List with Nodes it will help you to calculate the neighbors according to the Indexes. my idea is the Graph List will initialized with 81 Nodes objects. after that start calculate the neighbors – ProWily Nov 23 '21 at 06:59
  • Should the neighbors be, the other positions? Because eage **Edge**, must serve as a connection between two *Nodes*. – John Smith Nov 23 '21 at 07:20
  • 1
    so you make a property Edge to connect the Nodes? so you mean there is no connection directly between the Nodes? – ProWily Nov 23 '21 at 08:35
  • No, there is a connection. But, should, I store for, (0,0) then (0,1) as edge? Or, am I wrong? – John Smith Nov 23 '21 at 08:42
  • we don't need Edge property to find the neighbors. if you want to do it then it is a different approach you need to handle it. but it will be the same idea – ProWily Nov 23 '21 at 11:00
  • (X,Y) you can store it as Node property – ProWily Nov 23 '21 at 11:02
  • Yes, but, for one node 4 edges, but, will the Node (0,1), that comes after Node (0,0), be an edge? What about the other way, (0,0) and (1,0) – John Smith Nov 23 '21 at 11:07
  • Great. if you wanna connect the Nodes with Edges, the Node Class should have 4 Edge properties and the position (X,Y). and the Edge Class have 2 Node properties. then fill the list with 81 Nodes like what you said Node1(0,0), Node2(0,1) and so on. Finally, calculate the connections. – ProWily Nov 23 '21 at 11:45
  • The Node class already has a list of Edges. Show me with C# code. – John Smith Nov 23 '21 at 11:49
  • But, 4 edges represent movement, North, South, West and East – John Smith Nov 23 '21 at 11:50
  • I wrote a basic code so maybe it helps more. https://github.com/ProWily/stackoverflowQ_A/tree/main/Q_69972021 – ProWily Nov 23 '21 at 14:24
  • Row*Column = 9x9 =81, squares – John Smith Nov 23 '21 at 14:55
  • Is it clear? Or my answer is not enough – ProWily Nov 23 '21 at 15:08
  • Will get back to you. Patience. – John Smith Nov 23 '21 at 16:04
  • I am looping, a horizontal and vertical 2D-array. From them I can extract, (0,0), (0,1) <-- horizontal, and (1,0) <-- vertical. Based on them, I can determine if a Node can have an edge or not, depending on if (0,?) is true. These two 2D-arrays are of `bool[,]`. – John Smith Nov 24 '21 at 09:56
-1

If we have a 9x9 square matrix we can do the follow: First step, fill the Graph Nodes List with all Nodes. Second Step, make a loop to handle the neighbors. if we say the Node in the middle of the Graph then, currentNode.LeftNode = NodeList[currentNodePosition-1] currentNode.RightNode = NodeList[currentNodePosition+1] currentNode.TopNode = NodeList[currentNodePosition+9] currentNode.BottomNode = NodeList[currentNodePosition-9]. and as I said before you should handle the null cases in the picture above. I hop you get the point

Ajaz Ahmed
  • 315
  • 4
  • 18
  • You are close, see my edit. Thx. – John Smith Nov 24 '21 at 09:54
  • I am looping, a horizontal and vertical 2D-array. From them I can extract, (0,0), (0,1) <-- horizontal, and (1,0) <-- vertical. Based on them, I can determine if a Node can have an edge or not, depending on if (0,?) is true. These two 2D-arrays are of `bool[,]`. – John Smith Nov 24 '21 at 09:56
  • You have any code to prove this works, with the update information above? – John Smith Nov 24 '21 at 17:42
  • @JohnSmith The algorithm he described is what the code does in my answer – Nigel Nov 24 '21 at 18:10
-1

To store nodes ( with associated data )arranged in a 2D grid into an adjacency graph, I prefer to do things in three steps.

1 Read the node data into a 2D vector 2 Add nodes to a graph class 2 Scan the vector adding links between adjacent nodes to graph class

Let's assume the input is a text file with the node data arranged in space delimited orthoganol rows ( indicated by the 'o' ). For a 3 by 3 it might look like this

o 1 2 3
o 2 5 2
o 3 2 1

Some C++ code to read this into a vector

            std::vector<std::vector<float>> grid;
            int RowCount = 0;
            int ColCount = -1;
            int start = -1;
            int end = -1;
            std::string line;
            while (std::getline(myFile, line))
            {
                std::cout << line << "\n";
                auto token = ParseSpaceDelimited(line);
                if (!token.size())
                    continue;
                switch (token[0][0])
                {
                case 'o':
                {
                    if (ColCount == -1)
                        ColCount = token.size() - 1;
                    else if (token.size() - 1 != ColCount)
                        throw std::runtime_error("Bad column count");
                    std::vector<float> row;
                    for (int k = 1; k < token.size(); k++)
                        row.push_back(atof(token[k].c_str()));
                    grid.push_back(row);
                }
                ... other cases if needed to parse other kinds of input data

Now we can add the nodes to a graph class. I have a handy method ( orthogonalGridNodeName ) that provides human readable names to nodes located on a grid

        cGraph myFinder;
        RowCount = grid.size();

        // add nodes at each grid cell
        for (int row = 0; row < RowCount; row++)
        {
            for (int col = 0; col < ColCount; col++)
            {
                int n = myFinder.findoradd(
                    orthogonalGridNodeName(row, col));
                 // TODO: add data for this node from grid
            }
        }

Now we should be able to add the links between the nodes. Here I assume that every link has a cost of 1.

        // link cells orthogonally
        for (int row = 0; row < RowCount; row++)
            for (int col = 0; col < ColCount; col++)
            {
                int n = row * ColCount + col;

                if (fDirected)
                {
                    if (col > 0)
                    {
                        int left = row * ColCount + col - 1;
                        myFinder.addLink(n, left, 1);
                    }
                }
                if (col < ColCount - 1)
                {
                    int right = row * ColCount + col + 1;
                    myFinder.addLink(n, right, 1);
                }
                if (fDirected)
                {
                    if (row > 0)
                    {
                        int up = (row - 1) * ColCount + col;
                        myFinder.addLink(n, up, 1);
                    }
                }
                if (row < RowCount - 1)
                {
                    int down = (row + 1) * ColCount + col;
                    myFinder.addLink(n, down, 1);
                }
            }

I think this should be easily ported to C#

ravenspoint
  • 19,093
  • 6
  • 57
  • 103