Skip to content

Commit

Permalink
calculate correctly the pending fall moves using virtual board
Browse files Browse the repository at this point in the history
  • Loading branch information
ninetailsrabbit committed Jun 15, 2024
1 parent 2756a71 commit 184fd19
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 33 deletions.
93 changes: 91 additions & 2 deletions Match3Maker.Tests/tests/src/components/BoardTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,6 @@ public void Should_Be_Able_Create_Sequences_Of_Cells_With_Selected_Type() {
Piece prism = new(_pieceFactory.CreateNormalPiece("prism"));
Piece special = new(_pieceFactory.CreateSpecialPiece("special"));


List<Piece> pieces = [square, circle, triangle, prism, special];

var board = new Board(8, 7, 10);
Expand All @@ -837,6 +836,13 @@ public void Should_Be_Able_Create_Sequences_Of_Cells_With_Selected_Type() {
Assert.True(board.CreateSequenceOfCellsWithShape("circle").Cells.All(cell => cell.Piece.Type.Shape.Equals("circle")));
}

[Fact]
public void Should_Throw_Exception_When_Try_To_Move_Pieces_And_Fill_When_Grid_Cells_Are_Not_Prepared() {
var board = new Board(8, 7, 10);

Assert.Throws<ArgumentException>(() => board.MovePiecesAndFillEmptyCells());

}

[Fact]
public void Should_Be_Able_To_Calculate_The_Pending_Fall_Moves_When_Sequence_Is_Consumed() {
Expand All @@ -846,7 +852,6 @@ public void Should_Be_Able_To_Calculate_The_Pending_Fall_Moves_When_Sequence_Is_
Piece prism = new(_pieceFactory.CreateNormalPiece("prism"));
Piece special = new(_pieceFactory.CreateSpecialPiece("special"));


List<Piece> pieces = [square, circle, triangle, prism, special];

var board = new Board(8, 7, 10);
Expand All @@ -860,6 +865,90 @@ public void Should_Be_Able_To_Calculate_The_Pending_Fall_Moves_When_Sequence_Is_
Assert.Equal(board.GridWidth, board.EmptyCells().Count);
Assert.Equal(board.GridWidth, board.PendingFallMoves().Count);
}

[Fact]
public void Should_Create_A_Virtual_Board_Only_With_Fill_Updates_When_First_Row_Consumed() {
Piece square = new(_pieceFactory.CreateNormalPiece("square"));
Piece circle = new(_pieceFactory.CreateNormalPiece("circle"));
Piece triangle = new(_pieceFactory.CreateNormalPiece("triangle"));
Piece prism = new(_pieceFactory.CreateNormalPiece("prism"));
Piece special = new(_pieceFactory.CreateSpecialPiece("special"));

List<Piece> pieces = [square, circle, triangle, prism, special];

var board = new Board(8, 7, 10);

board.AddAvailablePieces(pieces).PrepareGridCells().FillInitialBoard(true);

board.CreateSequenceFromRow(0).Consume();

var virtualBoard = board.MovePiecesAndFillEmptyCells();

//No movements in the first row, only fills
Assert.True(virtualBoard.Updates.All(update => update.CurrentUpdateType.Equals(BoardCellUpdate.UPDATE_TYPE.FILL) && update.CellPieceFill.Cell.Row.Equals(0)));
Assert.Equal(board.GridWidth, virtualBoard.Updates.Count);

// Virtual board has no empty cells after the fill but the original board should keep unaltered.
Assert.Empty(virtualBoard.EmptyCells());
Assert.Equal(board.GridWidth, board.EmptyCells().Count);
}

[Fact]
public void Should_Create_A_Virtual_Board_Only_With_Fill_Updates_On_Entire_Column_Consumed() {
Piece square = new(_pieceFactory.CreateNormalPiece("square"));
Piece circle = new(_pieceFactory.CreateNormalPiece("circle"));
Piece triangle = new(_pieceFactory.CreateNormalPiece("triangle"));
Piece prism = new(_pieceFactory.CreateNormalPiece("prism"));
Piece special = new(_pieceFactory.CreateSpecialPiece("special"));

List<Piece> pieces = [square, circle, triangle, prism, special];

var board = new Board(8, 7, 10);

board.AddAvailablePieces(pieces).PrepareGridCells().FillInitialBoard(true);

board.CreateSequenceFromColumn(2).Consume();

var virtualBoard = board.MovePiecesAndFillEmptyCells();

//No movements in the first row, only fills
Assert.True(virtualBoard.Updates.All(update => update.CurrentUpdateType.Equals(BoardCellUpdate.UPDATE_TYPE.FILL) && update.CellPieceFill.Cell.Column.Equals(2)));
Assert.Equal(board.GridHeight, virtualBoard.Updates.Count);

// Virtual board has no empty cells after the fill but the original board should keep unaltered.
Assert.Empty(virtualBoard.EmptyCells());
Assert.Equal(board.GridHeight, board.EmptyCells().Count);
}

[Fact]
public void Should_Create_A_Virtual_Board_With_Movement_And_Fill() {
Piece square = new(_pieceFactory.CreateNormalPiece("square"));
Piece circle = new(_pieceFactory.CreateNormalPiece("circle"));
Piece triangle = new(_pieceFactory.CreateNormalPiece("triangle"));
Piece prism = new(_pieceFactory.CreateNormalPiece("prism"));
Piece special = new(_pieceFactory.CreateSpecialPiece("special"));

List<Piece> pieces = [square, circle, triangle, prism, special];

var board = new Board(8, 7, 10);
board.AddAvailablePieces(pieces).PrepareGridCells().FillInitialBoard(true);

var sequence = new Sequence([board.Cell(1, 2), board.Cell(2, 2), board.Cell(3, 2)], Sequence.SHAPES.HORIZONTAL);

//No updates in an initial board
Assert.Empty(board.MovePiecesAndFillEmptyCells().Updates);

sequence.Consume();

Assert.Equal(sequence.Size(), board.EmptyCells().Count);

var virtualBoard = board.MovePiecesAndFillEmptyCells();

// 6 Movements that represent moving down the row 0 and 1 after consuming the number 2.
Assert.Equal(sequence.Size() * 2, virtualBoard.MovementUpdates().Count);
Assert.Equal(sequence.Size(), virtualBoard.FillUpdates().Count);
Assert.Empty(virtualBoard.EmptyCells());
}
}

}
2 changes: 1 addition & 1 deletion Match3Maker/Match3Maker.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<PropertyGroup>
<Title>Ninetailsrabbit.Match3Maker</Title>
<Version>1.1.4</Version>
<Version>1.1.5</Version>
<Description>This lightweight library provides the core logic and functionality you need to build engaging match-3 games. Focus on game design and mechanics while leaving the complex logic to this library</Description>
<Copyright>© 2024 Ninetailsrabbit</Copyright>
<Authors>Ninetailsrabbit</Authors>
Expand Down
77 changes: 47 additions & 30 deletions Match3Maker/src/components/Board.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,29 @@

namespace Match3Maker {

public sealed class VirtualBoard(List<GridCell> gridCells) {
public List<GridCell> GridCells = gridCells;
#region Virtual board
public sealed class VirtualBoard {
public List<GridCell> GridCells = [];
public List<BoardCellUpdate> Updates = [];

public VirtualBoard(List<List<GridCell>> gridCells) {
GridCells = gridCells.SelectMany(cells => cells).Select(cell => new GridCell(cell.Column, cell.Row, cell.Piece, cell.CanContainPiece)).ToList();

// Only assign neighbours to fall & side down pieces
GridCells.ForEach(cell => {
cell.NeighbourBottom = Cell(cell.Column, cell.Row + 1);
cell.DiagonalNeighbourBottomRight = Cell(cell.Column + 1, cell.Row + 1);
cell.DiagonalNeighbourBottomLeft = Cell(cell.Column - 1, cell.Row + 1);
});
}

public GridCell? Cell(int column, int row) => GridCells.FirstOrDefault(cell => cell.Column.Equals(column) && cell.Row.Equals(row));
public List<GridCell> EmptyCells() => GridCells.Where(cell => cell.IsEmpty() && cell.CanContainPiece).ToList();
public void AddUpdate(BoardCellUpdate update) {
Updates.Add(update);
}

public List<BoardCellUpdate> MovementUpdates() => Updates.Where(update => update.CurrentUpdateType.Equals(UPDATE_TYPE.MOVEMENT)).ToList();
public List<BoardCellUpdate> FillUpdates() => Updates.Where(update => update.CurrentUpdateType.Equals(UPDATE_TYPE.FILL)).ToList();
}
public sealed class BoardCellUpdate(UPDATE_TYPE currentUpdateType, CellPieceMovement? cellPieceMovement = null, CellPieceFill? cellPieceFill = null) {
public enum UPDATE_TYPE {
Expand All @@ -26,10 +41,10 @@ public enum UPDATE_TYPE {
public bool IsFill() => CurrentUpdateType.Equals(UPDATE_TYPE.FILL);
}


public record CellPieceMovement(GridCell PreviousCell, GridCell NewCell, Piece Piece);
public record CellPieceFill(GridCell Cell, Piece Piece);

#endregion

public class Board {
public static readonly int MIN_GRID_WIDTH = 3;
Expand Down Expand Up @@ -358,13 +373,13 @@ public List<GridCell> LeftCellsFrom(GridCell cell, int distance) {
public List<GridCell> CellsThatCannotContainPiecesFromRow(int row) => CellsFromRow(row).Where(cell => !cell.CanContainPiece).ToList();
public List<GridCell> CellsThatCanContainPiecesFromColumn(int column) => CellsFromColumn(column).Where(cell => cell.CanContainPiece).ToList();
public List<GridCell> CellsThatCannotContainPiecesFromColumn(int column) => CellsFromColumn(column).Where(cell => !cell.CanContainPiece).ToList();
public Sequence CreateSequenceFromRow(int row) => new(CellsThatCanContainPiecesFromRow(row));
public Sequence CreateSequenceFromColumn(int column) => new(CellsThatCanContainPiecesFromColumn(column));
public Sequence CreateSequenceFromRowOfPieceType(int row, Type type) => new(CellsFromRowOfPieceType(row, type));
public Sequence CreateSequenceFromColumnOfPieceType(int column, Type type) => new(CellsFromColumnOfPieceType(column, type));
public Sequence CreateSequenceFromRow(int row) => new(CellsThatCanContainPiecesFromRow(row), Sequence.SHAPES.HORIZONTAL);
public Sequence CreateSequenceFromColumn(int column) => new(CellsThatCanContainPiecesFromColumn(column), Sequence.SHAPES.VERTICAL);
public Sequence CreateSequenceFromRowOfPieceType(int row, Type type) => new(CellsFromRowOfPieceType(row, type), Sequence.SHAPES.HORIZONTAL);
public Sequence CreateSequenceFromColumnOfPieceType(int column, Type type) => new(CellsFromColumnOfPieceType(column, type), Sequence.SHAPES.VERTICAL);
public Sequence CreateSequenceOfCellsWithPieceType(Type type) => new(CellsWithPieceType(type));
public Sequence CreateSequenceFromRowOfShape(int row, string shape) => new(CellsFromRowOfShape(row, shape));
public Sequence CreateSequenceFromColumnOfShape(int column, string shape) => new(CellsFromColumnOfShape(column, shape));
public Sequence CreateSequenceFromRowOfShape(int row, string shape) => new(CellsFromRowOfShape(row, shape), Sequence.SHAPES.HORIZONTAL);
public Sequence CreateSequenceFromColumnOfShape(int column, string shape) => new(CellsFromColumnOfShape(column, shape), Sequence.SHAPES.VERTICAL);
public Sequence CreateSequenceOfCellsWithShape(string shape) => new(CellsWithShape(shape));

public List<GridCell> CellsFromColumn(int column) {
Expand Down Expand Up @@ -516,30 +531,32 @@ public void RemoveMatchesFromBoard() {
}

public VirtualBoard MovePiecesAndFillEmptyCells() {
var gridCellsCopy = GridCells.SelectMany(cells => cells).Select(cell => new GridCell(cell.Column, cell.Row, cell.Piece, cell.CanContainPiece)).ToList();
if (GridCells.IsEmpty())
throw new ArgumentException("The board need to have the grid cells prepared, this operation cannot be done");

VirtualBoard virtualBoard = new(gridCellsCopy);
VirtualBoard virtualBoard = new(GridCells);

if (GridCells.Count > 0 && IsFree()) {
var pendingMoves = PendingFallMoves(virtualBoard.GridCells);

var pendingMoves = PendingFallMoves(virtualBoard.GridCells);
while (pendingMoves.Count > 0) {
foreach (var currentCell in pendingMoves) {
GridCell? bottomCell = currentCell.NeighbourBottom;

while (pendingMoves.Count > 0) {
virtualBoard.Updates.Clear();
if (bottomCell is not null) {
bottomCell.AssignPiece(currentCell.Piece);
currentCell.RemovePiece();

foreach (var currentCell in pendingMoves) {
GridCell? bottomCell = currentCell.NeighbourBottom;
virtualBoard.AddUpdate(new BoardCellUpdate(UPDATE_TYPE.MOVEMENT, new(currentCell, bottomCell, bottomCell.Piece)));
}
}

if (bottomCell is not null) {
bottomCell.AssignPiece(currentCell.Piece);
currentCell.RemovePiece();
pendingMoves = PendingFallMoves(virtualBoard.GridCells);
}

virtualBoard.AddUpdate(new BoardCellUpdate(UPDATE_TYPE.MOVEMENT, new(currentCell, bottomCell, bottomCell.Piece)));
}
}
foreach (var emptyCell in virtualBoard.EmptyCells()) {
GenerateRandomPieceOnCell(emptyCell);

pendingMoves = PendingFallMoves(virtualBoard.GridCells);
}
virtualBoard.AddUpdate(new BoardCellUpdate(UPDATE_TYPE.FILL, null, new(emptyCell, emptyCell.Piece)));
}

return virtualBoard;
Expand All @@ -548,10 +565,10 @@ public VirtualBoard MovePiecesAndFillEmptyCells() {
public List<GridCell> PendingFallMoves(IEnumerable<GridCell>? cells = null) {
cells ??= GridCells.SelectMany(cells => cells).Select(cell => cell).ToList();

return cells.Where(
cell => cell.HasPiece() &&
cell.Piece.Type.CanBeMoved() &&
cell.NeighbourBottom is GridCell bottomCell && bottomCell.IsEmpty())
return cells.Where(cell => cell.HasPiece() && cell.Piece.Type.CanBeMoved() && cell.NeighbourBottom is not null)
.Where(cell => (SelectedFillMode.Equals(FILL_MODES.FALL_DOWN) && cell.NeighbourBottom.IsEmpty()) ||
(SelectedFillMode.Equals(FILL_MODES.SIDE_DOWN) && (cell.NeighbourBottom.IsEmpty() || cell.NeighbourBottom.HasPiece()) && (cell.DiagonalNeighbourBottomRight.IsEmpty() || cell.DiagonalNeighbourBottomLeft.IsEmpty()))
)
.ToList();
}
#endregion
Expand Down

0 comments on commit 184fd19

Please sign in to comment.