在考虑边界的情况下检查多维数组中的相邻索引

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

我这样做是为了知道相邻方块中出现了多少个星号。

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 == '*');
}

这里的问题是,显然它 returns 是一个异常,因为正在检查不会被检查的索引。跳过这类索引的最佳方法是什么?使用 try/catch 可能有点棘手?谢谢!


编辑:

矩阵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();
    }
}

编辑(2):

来自主要 class.

的 PrintMatrix 和 Open 方法
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;
}

请注意,我使用的是 showedMatrix,这是另一个包含单元格的矩阵,每个单元格的值都是一个简单的 .。我正在使用这个新矩阵,所以我可以更改它的值并打印它。

这是两个失败的测试。

[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);
}

因为我知道这些测试的主要 class 是放置 2 个随机地雷加上我自己用 SetMine() 方法放置的地雷,所以我执行了几次这些测试以确保它失败了。结论是 "2...\n....\n....\n....\n"; 由于某种原因总是 0 而不是 2 或 1。

我建议在矩阵本身上使用迭代器方法,它负责在返回单元格之前检查边界,如下所示:

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));
    }
  }
}

for 循环定义了坐标的“3x3 window”(中心单元格 +/- 1),描述了 9 个单元格的最大块,其边缘受 Math.MinMath.Max 电话。然后,中央单元格本身被 if 检查跳过,结果只返回相邻的单元格。

然后,要计算星号,您只需利用此方法:

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

依靠异常来忽略越界坐标将被认为是一种不好的做法(因为它会使用“异常作为控制流”反模式)并且在性能方面也相当差。

邻居的想法还允许您参数化邻居区域的“大小”,如果这对您有用的话。

您可以向 Matrix class 添加一个方法,即 returns true 如果 x 和 y 都在允许的范围内(0 <= x < M, 0 <= y < N) 对于给定的 Coordinate:

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

那么你可以只计算有效的单元格:

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

此外,在 Matrix class 的所有其他 public 方法中将此方法用于验证目的是有意义的,接受 Coordinate 作为一个论点。