{"id":21864616,"url":"https://github.com/ninetailsrabbit/match3maker","last_synced_at":"2025-04-14T20:57:36.529Z","repository":{"id":242053080,"uuid":"808555920","full_name":"ninetailsrabbit/Match3Maker","owner":"ninetailsrabbit","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","archived":false,"fork":false,"pushed_at":"2024-08-16T09:25:29.000Z","size":663,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-28T09:11:16.456Z","etag":null,"topics":["csharp","godot","match3","plugin","puzzle"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ninetailsrabbit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-31T10:07:36.000Z","updated_at":"2024-10-30T19:12:24.000Z","dependencies_parsed_at":"2024-05-31T11:40:53.792Z","dependency_job_id":"abff5345-91a1-4541-b1dc-2db24c767d23","html_url":"https://github.com/ninetailsrabbit/Match3Maker","commit_stats":null,"previous_names":["ninetailsrabbit/match3maker"],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ninetailsrabbit%2FMatch3Maker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ninetailsrabbit%2FMatch3Maker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ninetailsrabbit%2FMatch3Maker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ninetailsrabbit%2FMatch3Maker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ninetailsrabbit","download_url":"https://codeload.github.com/ninetailsrabbit/Match3Maker/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248961186,"owners_count":21189991,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","godot","match3","plugin","puzzle"],"created_at":"2024-11-28T04:10:31.050Z","updated_at":"2025-04-14T20:57:36.506Z","avatar_url":"https://github.com/ninetailsrabbit.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Match3 Maker\n\n![license](https://badgen.net/static/License/MIT/yellow)\n[![readme](https://badgen.net/static/README/📃/yellow)](https://github.com/ninetailsrabbit/Match3Maker/README.md)\n![csharp](https://img.shields.io/badge/C%23-239120?style//for-the-badge\u0026logo//c-sharp\u0026logoColor//white)\n\nThis 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.\n\n---\n\n\u003cp align=\"center\"\u003e\n\u003cimg alt=\"Godot-XTension-Pack\" src=\"Match3Maker/icon.png\" width=\"250\"\u003e\n\u003c/p\u003e\n\n---\n\n- [Match3 Maker](#match3-maker)\n  - [Getting started](#getting-started)\n    - [Requirements](#requirements)\n    - [.csproj](#csproj)\n    - [Installation via CLI](#installation-via-cli)\n  - [Components](#components)\n    - [GridCell](#gridcell)\n    - [Piece](#piece)\n      - [Creating new piece types](#creating-new-piece-types)\n      - [Creating one piece](#creating-one-piece)\n    - [Sequence](#sequence)\n    - [Board](#board)\n      - [Creating a new board](#creating-a-new-board)\n  - [ISequenceFinder](#isequencefinder)\n  - [IPieceGenerator](#ipiecegenerator)\n\n## Getting started\n\nThis library empowers you to bring your match-3 game concepts to life quickly and efficiently. Don't get bogged down in the technical details - focus on what makes your game stand out!\n\n**_It not handle any visual components, it contains the logic for you to use it in your project and link the corresponding UI components_**\n\n### Requirements\n\n**This package uses standard csharp library code and does not use third party libraries.**\n\n- Net 8.0\n\n### .csproj\n\nAdd the package directly into your .csproj\n\n```xml\n\n\n\u003cItemGroup\u003e\n## Latest stable release\n  \u003cPackageReference Include=\"Ninetailsrabbit.Match3Maker\"/\u003e\n\n## Manual version\n  \u003cPackageReference Include=\"Ninetailsrabbit.Match3Maker\" Version=\"1.0.0\" /\u003e\n\u003c/ItemGroup\u003e\n```\n\n### Installation via CLI\n\nFurther information can be found on the [official microsoft documentation](https://learn.microsoft.com/en-us/nuget/consume-packages/install-use-packages-nuget-cli)\n\n```sh\nnuget install Ninetailsrabbit.Match3Maker\n\n# Or choosing version\n\nnuget install Ninetailsrabbit.Match3Maker -Version 1.0.0\n\n# Using dotnet\ndotnet add package Ninetailsrabbit.Match3Maker --version 1.0.0\n```\n\n## Components\n\nThe main logic has been simplified in few classes that represents each with a different functionality.\n\n### GridCell\n\nEach cell of the board can be accessed through this class. The position of the cells are immutable once the board is initialised so you can avoid common errors.\n\n**_You never actually create them yourself, it is recommended to initialise them from the Board class._**\n\n```csharp\n// public GridCell(int column, int row, Piece? piece = null, bool canContainPiece = true)\nGridCell cell = new GridCell(2, 3);\n\n// You have available the following properties\ncell.Id // A unique GUID created for each new instance\ncell.column // The column position\ncell.Row // The row position\ncell.CanContainPiece // Defines if can contain a piece\ncell.Piece // The current Piece assigned to this cell\n\n// Neighbours, you can access easily the nearest GridCells after initialize the board to the corresponding neighbour in the board. This improves performance as it only needs to be calculated once.\n\n//Null is returned if the cell does not exist (for example, a bottom border cell does not have a neighbour bottom)\ncell.NeighbourUp;\ncell.NeighbourBottom;\ncell.NeighbourRight;\ncell.NeighbourLeft;\ncell.DiagonalNeighbourTopRight;\ncell.DiagonalNeighbourTopLeft;\ncell.DiagonalNeighbourBottomRight;\ncell.DiagonalNeighbourBottomLeft;\n\n\ncell.Position(); // Vector2(3, 2) The position in the board where X is the row and Y is the column\n\n// Shortcuts to detect the assigned piece\ncell.IsEmpty();\ncell.HasPiece();\n\n// Assign a piece only if it's IsEmpty() and CanContainPiece is true\ncell.AssignPiece(new Piece(\"triangle\"));\n\n// Remove and return the current assigned piece if exists or null.\ncell.RemovePiece();\n\n// Manual check if the pieces can be swapped between this cells.\ncell.CanSwapPieceWith(GridCell otherCell);\n\n// Swap the piece with the other cell if both have pieces and they are not locked (CanSwapPieceWith is called internally).\n// SwappedPiece or SwapRejected events are raised whether it has been successful or not.\ncell.SwapPieceWith(GridCell otherCell);\n\n// Position detection\ncell.InSameRowAs(GridCell otherCell);\ncell.InSameColumnAs(GridCell otherCell);\n\n// Can be use with other cell or a Vector2 position\ncell.InSamePositionAs(GridCell otherCell);\ncell.InSamePositionAs(Vector2 position);\n\ncell.IsRowNeighbourOf(GridCell otherCell);\ncell.IsColumnNeighbourOf(GridCell otherCell);\n\ncell.IsAdjacentTo(GridCell otherCell);\ncell.InDiagonalWith(GridCell otherCell);\n\n// The IEquatable is implemented and two GridCells are equals if they are on the same position\npublic bool Equals(GridCell? other) {\n    return InSamePositionAs(other);\n}\n```\n\n### Piece\n\nA core Piece that manage most of the logic when it comes to interact with. It can be easily extended providing your own types so you're free to decide which one match and which one not. Are intended to handle the internal logic and not its display as a UI.\n\n#### Creating new piece types\n\nTo start adding valid pieces into the board, you need to implement the `IPieceType` interface in your class\n\n```csharp\n public interface IPieceType {\n     public string Shape {  get; set; }\n     public Color? Color { get; set; }\n     public bool MatchWith(Piece piece);\n     public bool CanBeShuffled();\n     public bool CanBeMoved();\n }\n\n```\n\nThis library provides three types by default _(Normal, Special, Obstacle)_ that you can use in your match-3 game but nothing stops you to create your own custom ones.\n\n```csharp\n  public class NormalPieceType : IPieceType {\n      public string Shape { get =\u003e _shape; set =\u003e _shape = value; }\n      public Color? Color { get =\u003e _color; set =\u003e _color = value; }\n\n      private string _shape;\n      private Color? _color = null;\n\n      public NormalPieceType(string shape, Color? color = null) {\n          Shape = shape;\n          Color = color;\n      }\n\n      public bool MatchWith(Piece piece) {\n        return piece.Type is not ObstaclePieceType\n            \u0026\u0026 Shape.Trim().Equals(piece.Type.Shape, StringComparison.OrdinalIgnoreCase)\n            \u0026\u0026 Color.Equals(piece.Type.Color);\n      }\n\n      public bool CanBeShuffled() =\u003e true;\n      public bool CanBeMoved() =\u003e true;\n  }\n```\n\n```csharp\n\n    public class SpecialPieceType : IPieceType {\n        public string Shape { get =\u003e _shape; set =\u003e _shape = value; }\n        public Color? Color { get =\u003e _color; set =\u003e _color = value; }\n\n        private string _shape;\n        private Color? _color = null;\n\n        public SpecialPieceType(string shape, Color? color = null) {\n            Shape = shape;\n            Color = color;\n        }\n\n        public bool MatchWith(Piece piece) {\n\n            return typeof(SpecialPieceType).IsAssignableFrom(piece.Type.GetType())\n                \u0026\u0026 Shape.Trim().Equals(piece.Type.Shape, StringComparison.OrdinalIgnoreCase)\n                \u0026\u0026 Color.Equals(piece.Type.Color);\n        }\n\n        public bool CanBeShuffled() =\u003e true;\n        public bool CanBeMoved() =\u003e true;\n    }\n```\n\n```csharp\n\nusing System.Drawing;\n\nnamespace Match3Maker {\n    public class ObstaclePieceType : IPieceType {\n        public string Shape { get =\u003e _shape; set =\u003e _shape = value; }\n        public Color? Color { get =\u003e _color; set =\u003e _color = value; }\n\n        private string _shape;\n        private Color? _color = null;\n\n        public ObstaclePieceType(string shape, Color? color = null) {\n            Shape = shape;\n            Color = color;\n        }\n\n        public bool MatchWith(Piece piece) =\u003e false;\n        public bool CanBeShuffled() =\u003e false;\n        public bool CanBeMoved() =\u003e false;\n\n    }\n\n}\n```\n\n#### Creating one piece\n\n```csharp\n// Only the shape\nvar piece = new Piece(new NormalPieceType(\"circle\"));\n// Or\nvar piece = new Piece(new NormalPieceType(\"circle\", Color.Green));\n\n// Static constructor\nvar piece = Piece.create(new NormalPieceType(\"square\", Color.Red));\n\n\n// Properties\npiece.Id\npiece.Type // IPieceType\npiece.Locked\n\n// Probability properties to use with IPieceGenerator\npiece.Weight\npiece.TotalAccumWeight\n\n// Lock methods, just use as information for you to decide whether to move the piece, mix it or match it\npiece.Lock()\npiece.Unlock();\n\n// When you initialize the board the pieces are already instanciated with the proper type, this clone works when it comes to generate new pieces in the board.\npiece.Clone();\n```\n\n### Sequence\n\nA sequence by definition does not need to follow any rules, the cells from this sequence are considered a match. This provides flexibility to for example remove an entire row in the board without the pieces having to match each other or live in the same row \u0026 column.\n\n**The constructor validate the cells passed as parameter and only assign the ones that has a piece.**\n\nThe calculations to retrieve the list of cells are done before create the sequence. That's what an `ISequenceFinder` implementation does since it knows how to allocate the proper `SHAPE`.\n\n```csharp\n// Available shapes\n\n public enum SHAPES {\n     HORIZONTAL,\n     VERTICAL,\n     T_SHAPE,\n     L_SHAPE\n }\n```\n\n```csharp\nvar cells = new List\u003cGridCell\u003e() {new GridCell(1, 0), new GridCell(2, 0), new GridCell(3, 0)}\nvar sequence = new Sequence(cells, Sequence.SHAPES.HORIZONTAL)\n\n// Get the current sequence cells\nsequence.Cells();\n\n// Give the size of the sequence\nsequence.Size()\n\n//Remove all the pieces from the cells\nsequence.Consume();\n\n// Get all the pieces from this sequence\nsequence.Pieces();\n\n//Position related, useful for ISequenceFinder to help find shapes.\nsequence.MiddleCell();\nsequence.TopEdgeCell();\nsequence.BottomEdgeCell();\nsequence.LeftEdgeCell();\nsequence.RightEdgeCell();\n\n// Shape shorcuts\nsequence.IsHorizontal();\nsequence.IsVertical();\nsequence.IsTShape();\nsequence.IsLShape();\n\n// To clone sequences before consuming and allow to pass the cells that were affected through events\nsequence.Clone();\n\n\n```\n\n### Board\n\nThis is the main class that you will actually use, you can manipulate any aspect of the dashboard and extract the necessary information for the UI.\n\n#### Creating a new board\n\n```csharp\n\n// PieceWeightGenerator from library is provided when null\n// SequenceFinder from library is provided when null\npublic Board(\n    int gridWidth,\n    int gridHeight,\n    int initialMoves,\n    IPieceGenerator? pieceGenerator = null,\n    ISequenceFinder? sequenceFinder = null)\n\n\n// Grid size can be passed as integer or Vector2.\nvar board = new Board(5, 7, 25);\nvar board = new Board(new Vector2(7, 5), 25);\n// Or\nvar board = Board.Create(5, 7, 25);\n\n// You can provide your custom implementations of IPieceGenerator or ISequenceFinder\nvar board = Board.Create(4, 4, 20, new YourCustomPieceGenerator(), new YourSequenceFinder());\n\n\n// Add available pieces to roll, they need to be defined to fill the board correctly\n Piece square = new(new NormalPieceType(\"square\"));\n Piece circle = new(new NormalPieceType(\"circle\"));\n Piece triangle = new(new NormalPieceType(\"triangle\"));\n\n List\u003cPiece\u003e pieces = [square, circle, triangle];\n\n// Further methods are provided to add or remove pieces on the board any time\nboard.AddAvailablePieces(pieces);\nboard.AddAvailablePiece(pieces.First());\nboard.RemoveAvailablePieces(pieces);\nboard.RemoveAvailablePiece(pieces.Last());\n\n\nboard.AddAvailablePieces(pieces)\n     .PrepareGridCells() // Initialize the GridCell classes with the width \u0026 height provided, overwrite parameter needs to be true if called a second time\n     .FillInitialBoard(false); // public Board FillInitialBoard(bool allowMatchesOnStart = false, Dictionary\u003cstring, Piece\u003e? preSelectedPieces = null)\n\n\n// This function run when PreparedGridCells is used, assign the neighbour cells for all of them in the board.\n// This function is rarely called manually but here it is in case you want to update the cells,  change their size or want to prepare them manually.\nboard.UpgradeGridCellsNeighbours();\n\n// You can change properties any time\nboard.ChangeGridWidth(7)\n     .ChangeGridHeight(8)\n     .ChangeGridSize(new Vector2(8, 7)) //Shorcut alternative\n     .ChangeFillMode(Board.FILL_MODES.SIDE_DOWN)\n     .ChangeCellSize(new Vector2(32, 32)) // Size information from the cell to display on your UI\n     .ChangeOffset(new Vector2(5, 10)) // Offset separation between cells to display on your UI\n     .ChangeRemainingMoves(20);\n\n// Increase or decrease board remaining moves which represents the remaining moves the player has left to use on this board.\n// EVENT: When remaining moves reachs zero raises the event \"SpentAllMoves\" and lock the board.\nboard.IncreaseMove();\nboard.DecreaseMove();\n\n//Plural\nboard.IncreaseMoves(2);\nboard.DecreaseMoves(3);\n\n// Lock or unlock this board, has no immediate effect on the board, it is information to be used externally.\nboard.Lock();\nboard.Unlock();\n\n//Access current cells with\nboard.GridCells;\n\n// Cell from column 0 and row 1, null is returned if it does not exists.\nboard.Cell(0, 1)\nboard.Cell(new Vector2(1, 0))\n\n//Returns the calculated cell position with the offset for a grid cell\nboard.CellPosition(board.Cell(2, 5)); // Vector2(250, 50) random vector for example purposes\n\n// Get the cells of selected column \u0026 row\nboard.CellsFromColumn(1);\nboard.CellsFromRow(3);\n\n// Get empty cells from board\nboard.EmptyCells();\nboard.EmptyCellsFromRow(3);\nboard.EmptyCellsFromColumn(2);\n\n// Get cells that contains a piece of selected type\nboard.CellsFromRowOfPieceType(2, typeof(SpecialPieceType));\nboard.CellsFromColumnOfPieceType(1, typeof(NormalPieceType));\nboard.CellsWithPieceType(typeof(NormalPieceType));\n\n// Returns the 2 upper cells from the origin one provided, only returns the valid cells,\n// it will never go out of bounds even if you use large numbers or out of range of that cell.\nboard.UpperCellsFrom(board.Cell(3, 3), 2); // GridCell(3, 2), GridCell(3, 1)\nboard.BottomCellsFrom(board.Cell(3, 3), 2); // GridCell(3, 4), GridCell(3, 5)\nboard.RightCellsFrom(board.Cell(3, 3), 2); // GridCell(4, 3), GridCell(5, 3)\nboard.LeftCellsFrom(board.Cell(3, 3), 2); // GridCell(1, 3), GridCell(2, 3)\n\n// Same syntax to retrieve the pieces instead of the cells\nboard.UpperCellsPiecesFrom(board.Cell(3, 3), 2);\nboard.BottomCellsPiecesFrom(board.Cell(2, 1), 1);\nboard.RightCellsPiecesFrom(board.Cell(4, 1), 5);\nboard.LeftCellsPiecesFrom(board.Cell(7, 2), 3);\n\n\n// Find a grid cell that contains the piece provided, it uses the Id property internally for the search.\n// The piece instance provided on this method must be already assigned to a cell in the board.\nboard.FindGridCellWithPiece(piece);\n//Or\nboard.FindGridCellWithPiece(piece.Id);\nboard.FindGridCellWithPiece(piece.Id.ToString());\n\n// Shuffle a board and returns a Dictionary\u003cGridCell, GridCell\u003e with the grid cells affected\n// The Key GridCell means that has changed piece with the Value GridCell (can be read as the opposite also)\n// Pieces with a IPieceType where CanBeShuffled() returns false are not affected in this process.\nvar result = board.Shuffle();\n\n// Remove current matches from board, used internally when FilledInitialBoard() is called\n// This remove the current sequences and create new pieces so the board does not have any current sequences active.\nboard.RemoveMatchesFromBoard();\n\n```\n\n## ISequenceFinder\n\nThe behaviour to find the match shapes can be implemented using this interface. By default this library provides you a default `SequenceFinder`.\n\n```csharp\n\npublic interface ISequenceFinder {\n\n    #region Properties\n    bool HorizontalShape { get; set; }\n    bool VerticalShape { get; set; }\n    bool TShape { get; set; }\n    bool LShape { get; set; }\n    int MinMatch { get; set; }\n    int MaxMatch { get; set; }\n    int MinSpecialMatch { get; set; }\n    int MaxSpecialMatch { get; set; }\n    #endregion\n\n    #region Find functions\n    public List\u003cSequence\u003e FindHorizontalSequences(IEnumerable\u003cGridCell\u003e cells);\n    public List\u003cSequence\u003e FindVerticalSequences(IEnumerable\u003cGridCell\u003e cells);\n    public Sequence? FindTShapeSequence(Sequence sequenceA, Sequence sequenceB);\n    public Sequence? FindLShapeSequence(Sequence sequenceA, Sequence sequenceB);\n    public List\u003cSequence\u003e FindBoardSequences(Board board);\n    public List\u003cSequence\u003e FindHorizontalBoardSequences(Board board);\n    public List\u003cSequence\u003e FindVerticalBoardSequences(Board board);\n    public Sequence? FindMatchFromCell(Board board, GridCell cell);\n    #endregion\n\n    #region Options\n    public ISequenceFinder ChangeMinMatchTo(int value) {\n        MinMatch = value;\n\n        return this;\n    }\n    public ISequenceFinder ChangeMaxMatchTo(int value) {\n        MaxMatch = value;\n\n        return this;\n    }\n    public ISequenceFinder EnableHorizontalShape() {\n        HorizontalShape = true;\n        return this;\n    }\n\n    public ISequenceFinder DisableHorizontalShape() {\n        HorizontalShape = false;\n        return this;\n    }\n\n    public ISequenceFinder EnableVerticalShape() {\n        VerticalShape = true;\n        return this;\n    }\n\n    public ISequenceFinder DisableVerticalShape() {\n        VerticalShape = false;\n        return this;\n    }\n\n    public ISequenceFinder EnableLShape() {\n        LShape = true;\n        return this;\n    }\n\n    public ISequenceFinder DisableLShape() {\n        LShape = false;\n        return this;\n    }\n\n    public ISequenceFinder EnableTShape() {\n        TShape = true;\n        return this;\n    }\n\n    public ISequenceFinder DisableTShape() {\n        TShape = false;\n        return this;\n    }\n    #endregion\n}\n```\n\n## IPieceGenerator\n\nThe behaviour to generate new pieces after consuming them it's implemented with this interface. By default this library provides you a `PieceWeightGenerator` that uses the `Weight and TotalAccumWeight` property from the pieces.\n\nThe higher the weight, the more changes to appear in the next roll.\n\n```csharp\n    public interface IPieceGenerator {\n\n      public Piece Roll(List\u003cPiece\u003e pieces, IEnumerable\u003cType\u003e? only = null);\n    }\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fninetailsrabbit%2Fmatch3maker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fninetailsrabbit%2Fmatch3maker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fninetailsrabbit%2Fmatch3maker/lists"}