Skip to content

Commit

Permalink
Add bit array for cell visibilities (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
mpewsey authored Oct 17, 2022
1 parent 17c3a64 commit 2b34d66
Show file tree
Hide file tree
Showing 6 changed files with 351 additions and 3 deletions.
24 changes: 24 additions & 0 deletions src/ManiaMap.Tests/TestArray2D.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,30 @@ public void TestEmptyInitializer()
Assert.AreEqual(0, array.Array.Length);
}

[TestMethod]
public void TestClear()
{
var array = new Array2D<int>(2, 3);

for (int i = 0; i < array.Rows; i++)
{
for (int j = 0; j < array.Columns; j++)
{
array[i, j] = i + j;
}
}

array.Clear();

for (int i = 0; i < array.Rows; i++)
{
for (int j = 0; j < array.Columns; j++)
{
Assert.AreEqual(0, array[i, j]);
}
}
}

[TestMethod]
public void TestToString()
{
Expand Down
139 changes: 139 additions & 0 deletions src/ManiaMap.Tests/TestBitArray2D.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace MPewsey.ManiaMap.Tests
{
[TestClass]
public class TestBitArray2D
{
[TestMethod]
public void TestEmptyInitializer()
{
var array = new BitArray2D();
Assert.AreEqual(0, array.Rows);
Assert.AreEqual(0, array.Columns);
Assert.AreEqual(0, array.Array.Length);
}

[TestMethod]
public void TestIndexSetterAndGetter()
{
const int rows = 16;
const int columns = 2;

for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
var value = 1 << (i * columns + j);
var array = new BitArray2D(rows, columns);
Assert.AreEqual(rows, array.Rows);
Assert.AreEqual(columns, array.Columns);
Assert.AreEqual(1, array.Array.Length);
Assert.AreEqual(0, array.Array[0]);
Assert.IsFalse(array[i, j]);
array[i, j] = true;
Assert.AreEqual(value, array.Array[0]);
Assert.IsTrue(array[i, j]);
array[i, j] = false;
Assert.AreEqual(0, array.Array[0]);
Assert.IsFalse(array[i, j]);
}
}
}

[TestMethod]
public void TestToArrayString()
{
var values = new int[,]
{
{ 0, 1, 0, 0, 1, 1, 1, 0 },
{ 1, 0, 1, 1, 0, 0, 0, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1 },
{ 0, 1, 0, 1, 0, 1, 0, 1 },
{ 1, 0, 1, 0, 1, 0, 1, 0 },
};

var array = new BitArray2D(values.GetLength(0), values.GetLength(1));
Assert.AreEqual(2, array.Array.Length);

for (int i = 0; i < values.GetLength(0); i++)
{
for (int j = 0; j < values.GetLength(1); j++)
{
array[i, j] = values[i, j] == 1;
}
}

var str = array.ToArrayString();
Console.WriteLine(str);
var expected = "[[01001110]\n [10110001]\n [11111111]\n [01010101]\n [10101010]]";
Assert.AreEqual(expected, str);
}

[TestMethod]
public void TestClear()
{
var array = new BitArray2D(2, 3);

for (int i = 0; i < array.Rows; i++)
{
for (int j = 0; j < array.Columns; j++)
{
array[i, j] = true;
}
}

array.Clear();

for (int i = 0; i < array.Rows; i++)
{
for (int j = 0; j < array.Columns; j++)
{
Assert.IsFalse(array[i, j]);
}
}
}

[TestMethod]
public void TestGetOrDefault()
{
var array = new BitArray2D(2, 3);
array[1, 1] = true;
Assert.IsTrue(array.GetOrDefault(-1, -1, true));
Assert.IsTrue(array.GetOrDefault(1, 1));
}

[TestMethod]
public void TestToString()
{
var result = new BitArray2D(1, 2).ToString();
var expected = $"BitArray2D(Rows = 1, Columns = 2)";
Assert.AreEqual(expected, result);
}

[TestMethod]
public void TestInitializeNegativeRow()
{
Assert.ThrowsException<ArgumentException>(() => new BitArray2D(-1, 1));
}

[TestMethod]
public void TestInitializeNegativeColumn()
{
Assert.ThrowsException<ArgumentException>(() => new BitArray2D(1, -1));
}

[TestMethod]
public void TestGetOutOfBoundsIndex()
{
Assert.ThrowsException<IndexOutOfRangeException>(() => new BitArray2D()[-1, -1]);
}

[TestMethod]
public void TestSetOutOfBoundsIndex()
{
Assert.ThrowsException<IndexOutOfRangeException>(() => new BitArray2D()[-1, -1] = true);
}
}
}
8 changes: 8 additions & 0 deletions src/ManiaMap/Array2D.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@ public string ToArrayString()
return builder.ToString();
}

/// <summary>
/// Sets the contents of the array to the default value.
/// </summary>
public void Clear()
{
System.Array.Clear(Array, 0, Array.Length);
}

/// <summary>
/// Sets all elements of the array to the value.
/// </summary>
Expand Down
177 changes: 177 additions & 0 deletions src/ManiaMap/BitArray2D.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;

namespace MPewsey.ManiaMap
{
/// <summary>
/// An 2D array of bits.
/// </summary>
[DataContract]
public class BitArray2D
{
/// <summary>
/// The number of bits in each chunk.
/// </summary>
public const int ChunkSize = 32;

/// <summary>
/// The number of rows in the array.
/// </summary>
[DataMember(Order = 1)]
public int Rows { get; private set; }

/// <summary>
/// The number of columns in the array.
/// </summary>
[DataMember(Order = 2)]
public int Columns { get; private set; }

/// <summary>
/// A flat array of data chunks.
/// </summary>
[DataMember(Order = 3)]
public int[] Array { get; private set; } = System.Array.Empty<int>();

/// <summary>
/// Accesses the bit at the specified index.
/// </summary>
/// <param name="row">The row index.</param>
/// <param name="column">The column index.</param>
/// <exception cref="IndexOutOfRangeException">Raised if the index is out of bounds.</exception>
public bool this[int row, int column]
{
get
{
if (!IndexExists(row, column))
throw new IndexOutOfRangeException($"Index out of range: ({row}, {column}).");

var index = Index(row, column);
return (Array[index.X] & (1 << index.Y)) != 0;
}
set
{
if (!IndexExists(row, column))
throw new IndexOutOfRangeException($"Index out of range: ({row}, {column}).");

var index = Index(row, column);

if (value)
Array[index.X] |= 1 << index.Y;
else
Array[index.X] &= ~(1 << index.Y);
}
}

/// <summary>
/// Initializes an empty array.
/// </summary>
public BitArray2D()
{

}

/// <summary>
/// Initializes an array by size.
/// </summary>
/// <param name="rows">The number of rows in the array.</param>
/// <param name="columns">The number of columns in the array.</param>
/// <exception cref="ArgumentException">Raised if either the input rows or columns are negative.</exception>
public BitArray2D(int rows, int columns)
{
if (rows < 0)
throw new ArgumentException($"Rows cannot be negative: {rows}.");
if (columns < 0)
throw new ArgumentException($"Columns cannot be negative: {columns}.");

if (rows > 0 && columns > 0)
{
Rows = rows;
Columns = columns;
Array = new int[(int)Math.Ceiling(rows * (double)columns / ChunkSize)];
}
}

public override string ToString()
{
return $"BitArray2D(Rows = {Rows}, Columns = {Columns})";
}

/// <summary>
/// Returns a string of all array elements.
/// </summary>
public string ToArrayString()
{
var size = 2 + ChunkSize * Array.Length + 4 * Rows;
var builder = new StringBuilder(size);
builder.Append('[');

for (int i = 0; i < Rows; i++)
{
builder.Append('[');

for (int j = 0; j < Columns; j++)
{
if (this[i, j])
builder.Append('1');
else
builder.Append('0');
}

builder.Append(']');

if (i < Rows - 1)
builder.Append("\n ");
}

builder.Append(']');
return builder.ToString();
}

/// <summary>
/// Clears all active bits in the array.
/// </summary>
public void Clear()
{
System.Array.Clear(Array, 0, Array.Length);
}

/// <summary>
/// Returns true if the index exists.
/// </summary>
/// <param name="row">The row.</param>
/// <param name="column">The column.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IndexExists(int row, int column)
{
return (uint)row < Rows && (uint)column < Columns;
}

/// <summary>
/// Returns a vector with the chunk index and position within the chunk
/// for the specified row-column index.
/// </summary>
/// <param name="row">The row.</param>
/// <param name="column">The column.</param>
private Vector2DInt Index(int row, int column)
{
var index = row * Columns + column;
return new Vector2DInt(index / ChunkSize, index % ChunkSize);
}

/// <summary>
/// Returns the value at the specified index if it exists. If not,
/// returns the fallback value.
/// </summary>
/// <param name="row">The row index.</param>
/// <param name="column">The column index.</param>
/// <param name="fallback">The fallback value.</param>
public bool GetOrDefault(int row, int column, bool fallback = false)
{
if (IndexExists(row, column))
return this[row, column];
return fallback;
}
}
}
2 changes: 1 addition & 1 deletion src/ManiaMap/ManiaMap.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<RepositoryUrl>https://github.com/mpewsey/ManiaMap</RepositoryUrl>
<PackageTags>procedural-generation;roguelike;metroidvania;videogames</PackageTags>
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
<VersionPrefix>2.2.0</VersionPrefix>
<VersionPrefix>2.3.0</VersionPrefix>
<Configurations>Debug;Release;Unity</Configurations>
<PackageProjectUrl>https://mpewsey.github.io/ManiaMap/</PackageProjectUrl>
</PropertyGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/ManiaMap/RoomState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class RoomState
/// An array of room cell visibilities.
/// </summary>
[DataMember(Order = 2)]
public Array2D<bool> VisibleCells { get; private set; }
public BitArray2D VisibleCells { get; private set; }

/// <summary>
/// A set of acquired collectable location ID's.
Expand Down Expand Up @@ -58,7 +58,7 @@ protected IEnumerable<int> FlagIds
public RoomState(Room room)
{
Id = room.Id;
VisibleCells = new Array2D<bool>(room.Template.Cells.Rows, room.Template.Cells.Columns);
VisibleCells = new BitArray2D(room.Template.Cells.Rows, room.Template.Cells.Columns);
}

/// <summary>
Expand Down

0 comments on commit 2b34d66

Please sign in to comment.