Home > Net >  Check adjacent indices in a multidimensional array having into account the borders
Check adjacent indices in a multidimensional array having into account the borders

Time:12-22

I did this so I can know how many asterisks appear in the adjacent squares.

private int CheckAdjacents(Coordinate cord)
{
    List<Coordinate> coordinates = new List<Coordinate>()
    {
        new Coordinate(cord.X - 1, cord.Y - 1), 
        new Coordinate(cord.X, cord.Y-1),
        new Coordinate(cord.X   1, cord.Y -1),  
        new Coordinate(cord.X   1, cord.Y),
        new Coordinate(cord.X   1, cord.Y   1), 
        new Coordinate(cord.X, cord.Y   1),
        new Coordinate(cord.X - 1, cord.Y   1), 
        new Coordinate(cord.X - 1, cord.Y)
    };

    return coordinates.Count(x => _matrix.At(x).Value == '*');
}

The thing here is that obviously it returns an exception because is checking indexes that wouldn't be checked. What'd be the best way to skip those kind of indexes? Using a try/catch could be kinda tricky? Thanks!


EDIT:

Matrix class

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace MineSweeper
{
    public record Coordinate (int X, int Y);
    public record Size(int M, int N);

    public class Matrix
    {
        private readonly Size _size;
        private readonly Cell[,] _matrix;
        private const char InitValue = '.';

        public Matrix(Size size)
        {
            _size = size;
            _matrix = new Cell[size.M, size.N];
            Initialize();
        }

        private void Initialize()
        { 
            for (int m = 0; m < _size.M; m  )
                for (int n = 0; n < _size.N; n  )
                    _matrix[m, n] = new Cell(InitValue);
        }

        public Size GetSize()
            => _size;

        public Cell At(Coordinate coordinate) 
            => _matrix[coordinate.X, coordinate.Y];
        
        public void SetMine(Coordinate coordinate) 
            => _matrix[coordinate.X, coordinate.Y] = new Cell('*');

        public void ChangeValue(Coordinate coordinate, char value)
            => _matrix[coordinate.X, coordinate.Y] = new Cell(value);
        
        public Cell Open(Coordinate coordinate)
            => _matrix[coordinate.X, coordinate.Y];

        public IEnumerable ToList()
            => _matrix.Cast<Cell>().ToList();

        private string CellsAsString()
            => string.Concat(_matrix.OfType<Cell>().Select(c => c.Value));
        
        public override bool Equals(object other)
            => this.CellsAsString().Equals((other as Matrix)?.CellsAsString());
        
        public override int GetHashCode()
            => this.CellsAsString().GetHashCode();
    }
}

EDIT(2):

PrintMatrix and Open methods from the main class.

public void Open(Coordinate coordinate)
{
    if (_matrix.At(coordinate).Value == '*')
        HasLose = true;

    int numOfMines = _matrix.NeighborsOf(coordinate).Count(cell => cell.Value == '*');
    _showedMatrix.ChangeValue(coordinate, char.Parse(numOfMines.ToString()));

    HasWin = PlayerHasWin();
}


public String PrintMatrix()
{
    string temp = "";
    for (int x = 0; x < _size.M; x  )
    {
        for (int y = 0; y < _size.N; y  )
        {
            temp  = _showedMatrix.At(new Coordinate(x, y)).Value;
        }
        temp  = '\n';
    }

    return temp;
}

Notice that I'm using a showedMatrix which is another matrix with cells and its value for each one is a simple .. I'm using this new matrix so I can change its value and printing it.

These are the two tests that fail.

[Fact]
public void CellIsOpenWithoutAMineButWithOneMineAdjacent()
{
    string printExpected = "1...\n....\n....\n....\n";

    Matrix matrix = new Matrix(new(4, 4));
    matrix.SetMine(new(0,1));
    
    MineSweeper mineSweeper = new(matrix, 2);
    mineSweeper.Open(new(0,0));
    
    mineSweeper.PrintMatrix().Should().Be(printExpected);
}

[Fact]
public void CellIsOpenWithoutAMineButWithTwoMineAdjacent()
{
    string printExpected = "2...\n....\n....\n....\n";

    Matrix matrix = new Matrix(new(4, 4));
    matrix.SetMine(new(0,1));
    matrix.SetMine(new(1,0));
    
    MineSweeper mineSweeper = new(matrix, 2);
    mineSweeper.Open(new(0,0));
    
    mineSweeper.PrintMatrix().Should().Be(printExpected);
}

Since I'm aware that my main class for these tests is putting 2 random mines plus the mines I'm putting by myself with the SetMine() method, I executed these tests several times to make sure that it was failing. The conclusion was that "2...\n....\n....\n....\n"; for some reason is always a 0 instead of a 2, or a 1.

CodePudding user response:

I'd suggest an iterator method on the matrix itself, that took care of checking the bounds before returning the cells, something like this:

IEnumerable<Cell> NeighborsOf(Coordinate coord)
{
  // if coord is not in bounds of the matrix, throw an argument exception

  for (int x = Math.Max(coord.X - 1, 0); x <= Math.Min(coord.X   1, _size.M - 1); x  )
  {
    for (int y = Math.Max(coord.Y - 1, 0); y <= Math.Min(coord.Y   1, _size.N - 1); y  )
    {
      if ((x,y) == (coord.X, coord.Y)) continue;

      yield return At(new Coordinate(x, y));
    }
  }
}

The for loops define the "3x3 window" of coordinates (central cell /- 1) describing a maximum block of 9 cells, that has its edges limited by the Math.Min and Math.Max calls. Then, the central cell itself is skipped by the if check, resulting only on adjacent cells being returned.

Then, to count the asterisks, you just leverage this method:

_matrix.NeighborsOf(coordinate).Count(cell => cell.Value == '*');

Relying on exceptions to ignore the out-of-bounds coordinates would be considered a bad practice (as it would be an use of the "exception as control flow" anti-pattern) and also fairly poor performance-wise.

The neighbor idea also allows you to parameterize the "size" of the neighbor region, if that's useful to you.

CodePudding user response:

You could add a method to the Matrix class, that returns true if both x and y are within their allowed ranges (0 <= x < M, 0 <= y < N) for a given Coordinate:

public bool IsValid(Coordinate coordinate) =>
         0 <= coordinate.X && coordinate.X < _size.M &&
         0 <= coordinate.Y && coordinate.Y < _size.N;

Then you can count only the valid cells:

return coordinates.Count(x => _matrix.IsValid(x) && _matrix.At(x).Value == '*');

Additionally, it makes sense to use this method for validation purpose in all other public methods of the Matrix class accepting Coordinate as an argument.

  • Related