//! `micro_autotile` provides an implementation of the LDTK auto-tiling algorithm, for use in //! programs at runtime. The representation is compatible with that saved by LDTK, meaning that //! definitions can be loaded directly from LDTK JSON exports. //! //! Creating a single rule works like this: //! //! 1. Create a `TileMatcher` out of `TileStatus` entries. //! * Tile Matchers are squares represented as fixed size flat arrays //! * Currently only 1x1 and 3x3 matchers are supported, 5x5 matchers are incompatible //! * Since a Tile Matcher is a rule, they are usually created statically or loaded as //! an asset that will not change much / at all //! 2. Create a `TileOutput` that represents the value produces by this rule when it matches //! * An output value of `Skip` will cause the rule to be a noop. This has utility when combined //! with a rule's `chance` value, as part of a set of rules //! * A `Single` output will always produce the same value //! * A `Random` output will produce one of the provided values at random //! 3. Combine these into an `AutoTileRule` //! * There are a number of convenience methods for doing this process without mistakes in a single function call //! //! To utilise your matcher, you'll need to provide a specifically formatted slice of your input data (typically a sub-grid //! of a tile map). If you're matching a single tile, you can use the convenience method `TileLayout::single`, otherwise //! you will need to provide a 9 element array that represents 3 rows and 3 columns of data, in the following format: //! //! ```text //! Flat array data //! [1, 2, 3, 4, 5, 6, 7, 8, 9] //! ``` //! //! ```text //! Formatted in the way it would appear if laid out in a tile map //! [ //! 1, 2, 3, # First row, all three columns //! 4, 5, 6, # Second row, all three columns //! 7, 8, 9, # Third row, all three columns //! ] //! ``` //! //! As we can see, the fifth element of the array is the centre tile of our matching grid. In fact, `TileLayout::single` constructs //! a 9 element array where the fifth element is `Some(your_value)`, and the rest are simply `None`. This is possible because the //! actual data represents each element as an `Option` (not as the simple numbers above), which allows matching up against edges of //! data arrays, or against non-regular shapes. Putting this together to match against data from our tile map, we have the following: //! //! e.g. //! ```rust //! # use micro_autotile::{TileLayout, TileMatcher}; //! use micro_autotile::{AutoTileRule, AutoRuleSet}; //! //! # fn main() { //! const WALL_TILE: i32 = 0; //! const GROUND_TILE: i32 = 1; //! //! // Match a 1x1 ground tile, output the index within the spritesheet that we'll use for rendering //! let match_1_x_1 = AutoTileRule::exact(GROUND_TILE, 57); //! //! assert_eq!( //! match_1_x_1.resolve_match(&TileLayout::single(GROUND_TILE)), //! Some(57) //! ); //! //! // More realistically, we might have some tile data for a ground tile with other data arround it. //! // When we match against a rule, we're always trying to produce a value for the _central_ value in //! // our `TileLayout` (the fifth element) //! let enclosed_ground = TileLayout([ //! Some(WALL_TILE), Some(WALL_TILE), Some(WALL_TILE), //! Some(WALL_TILE), Some(GROUND_TILE), Some(WALL_TILE), //! Some(WALL_TILE), Some(WALL_TILE), Some(WALL_TILE), //! ]); //! //! assert_eq!( //! match_1_x_1.resolve_match(&enclosed_ground), //! Some(57) //! ); //! //! // There may also be situations in which you just want to know that a given layout matches a rule, without //! // concern for producing a value for that layout. You can directly use a `TileMatcher` for this //! assert!(TileMatcher::single(GROUND_TILE).matches(&enclosed_ground)); //! # } //! ``` //! //! There's already a lot of utility to these structures, but we still need to manually run a set of //! rules against our maps and do some work with the `AutoTileRule::chance` property to figure out //! what the final output should be for a given layout. //! //! Introducing the `AutoRuleSet` struct, that represents a sequence of rules that should be evaluated //! to produce an output. It provides similar methods to the individual AutoTileRule, but will execute //! against a set of rules at once. There are also convenience methods for combining AutoRuleSet //! instances //! //! ```rust //! # use micro_autotile::{TileLayout, TileMatcher}; //! use micro_autotile::{AutoTileRule, AutoRuleSet}; //! //! # fn main() { //! use micro_autotile::{TileMatcher, TileStatus}; //! const WALL_TILE: i32 = 0; //! const GROUND_TILE: i32 = 1; //! const OTHER_TILE: i32 = 342; //! //! let wall_rules = AutoRuleSet(vec![ //! AutoTileRule::single_when(TileMatcher([ // Top Left Corner //! TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE), //! TileStatus::IsNot(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE), //! TileStatus::IsNot(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE), //! ]), 54), //! AutoTileRule::single_when(TileMatcher([ // Top Right Corner //! TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE), TileStatus::IsNot(WALL_TILE), //! TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::IsNot(WALL_TILE), //! TileStatus::Is(WALL_TILE), TileStatus::Is(WALL_TILE), TileStatus::IsNot(WALL_TILE), //! ]), 55), //! // ... Etc //! ]); //! //! let ground_rules = AutoRuleSet(vec![ //! // Use decorated tiles in 10% of cases //! AutoTileRule::single_any_chance(GROUND_TILE, vec![45, 46, 47], 0.1), //! // Fall back to the basic tile if we don't match previously //! AutoTileRule::exact(GROUND_TILE, 44), //! ]); //! //! // Easily merge rule sets in an ordered way //! let combined_rules = wall_rules + ground_rules; //! //! let sublayout = TileLayout([ //! Some(OTHER_TILE), Some(GROUND_TILE), Some(GROUND_TILE), //! Some(WALL_TILE), Some(WALL_TILE), Some(OTHER_TILE), //! Some(WALL_TILE), Some(WALL_TILE), Some(GROUND_TILE), //! ]); //! //! // We've got a layout that represents the top right corner of a wall, the second rule in our //! // set - the value of the tiles that match "IsNot(WALL_TILE)" are irrelevant, as long as they //! // exist (Option::Some) //! let output = combined_rules.resolve_match(&sublayout); //! assert_eq!(output, Some(55)); //! # } //! ``` mod layout; mod autotile; mod output; mod utils; pub use layout::*; pub use autotile::*; pub use output::*;