diff --git a/.gitlab-ci.yaml b/.gitlab-ci.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..23b350fa2c962c8b6d91204536ef6a9a0757efde
--- /dev/null
+++ b/.gitlab-ci.yaml
@@ -0,0 +1,22 @@
+image: rust:1.82-alpine
+stages:
+  - test
+
+lint:
+  stage: test
+  script:
+    - cargo fmt --all --check
+    - cargo clippy --offline --frozen --locked -- -D warnings
+
+test:
+  stage: test
+  script:
+    - cargo test
+
+package:
+  stage: test
+  script:
+    - cargo package --release
+  dependencies:
+    - test
+    - lint
\ No newline at end of file
diff --git a/src/layout.rs b/src/layout.rs
index 48d0c2b31ab5295636d5239ea9102a3155c3c0ff..dc96cd12142dad1a44271c22bc342ff620acfcbb 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -82,13 +82,13 @@ impl TileLayout {
 	}
 
 	/// Construct a filled grid of tile data
-	pub fn filled(values: [i32; TILE_GRID_SIZE]) -> Self {
-		TileLayout(values.map(Some))
+	pub fn filled(values: [impl IntoTile; TILE_GRID_SIZE]) -> Self {
+		TileLayout(values.map(|val| Some(val.into_tile())))
 	}
 
 	/// Construct a filled grid of identical tile data
-	pub fn spread(value: i32) -> Self {
-		TileLayout([Some(value); TILE_GRID_SIZE])
+	pub fn spread(value: impl IntoTile) -> Self {
+		TileLayout([Some(value.into_tile()); TILE_GRID_SIZE])
 	}
 
 	/// Filter the layout data so that it only contains the tiles surrounding the target tile. This
@@ -190,13 +190,33 @@ impl Default for TileMatcher {
 }
 
 impl TileMatcher {
+	/// Create a matcher that only evaluates one rule against the central target tile
+	///
+	/// ## Examples
+	///
+	/// ```
+	/// # use micro_autotile::{TileLayout, TileMatcher, TileStatus};
+	/// let rule = TileStatus::IsNot(123);
+	/// let matcher = TileMatcher::single(rule);
+	///
+	/// assert!(matcher.matches(&TileLayout::single(456)));
+	/// assert!(matcher.matches(&TileLayout::spread(1)));
+	/// ```
 	pub const fn single(value: TileStatus) -> Self {
 		let mut rules = [TileStatus::Ignore; TILE_GRID_SIZE];
 		rules[GRID_CENTER] = value;
 		TileMatcher(rules)
 	}
 
-	/// Create a 1x1 matcher, with any rule for the target tile
+	/// Create a matcher that only checks if the target tile matches the given value
+	///
+	/// ## Examples
+	///
+	/// ```
+	/// # use micro_autotile::{TileLayout, TileMatcher};
+	/// let matcher = TileMatcher::single_match(123);
+	/// assert!(matcher.matches(&TileLayout::single(123)));
+	/// ```
 	pub const fn single_match(value: i32) -> Self {
 		Self::single(TileStatus::Is(value))
 	}
@@ -211,6 +231,37 @@ impl TileMatcher {
 
 	/// Load data from an LDTK JSON file. Supports arbitrary sized matchers for any square grid.
 	/// Other sizes of matcher will result in `None`
+	///
+	/// ## Examples
+	///
+	/// A simple 1x1 LDTK Array
+	///
+	/// ```
+	/// # use micro_autotile::{TileMatcher, TileLayout};
+	/// let matcher = TileMatcher::from_ldtk_array(vec![1]).expect("Invalid input");
+	/// assert!(matcher.matches(&TileLayout::single(1)));
+	/// ```
+	///
+	/// A 3x3 matcher with more complex rules
+	///
+	/// ```
+	/// # use micro_autotile::{TileMatcher, TileLayout};
+	/// let rule_input: Vec<i64> = vec![
+ 	///     100, -100, 100,
+	///     0, 5, 0,
+	///     0, 0, 0
+	/// ];
+	///
+	/// let tile_section: [i32; 9] = [
+	///     100, 5, 100,
+	///     24, 5, 293,
+	///     34, 21, 22
+	/// ];
+	///
+	/// let matcher = TileMatcher::from_ldtk_array(rule_input).expect("Invalid input");
+	/// let map_layout = TileLayout::try_from(tile_section.as_slice()).expect("Invalid input");
+	/// assert!(matcher.matches(&map_layout));
+	/// ```
 	pub fn from_ldtk_array(value: Vec<i64>) -> Option<Self> {
 		Self::try_from(value.as_slice()).ok()
 	}