use std::ascii::AsciiExt;
use std::ops::DivAssign;
use std::ops::MulAssign;
use std::ops::SubAssign;
use std::collections::BinaryHeap;
use std::str::FromStr;
use std::collections::HashSet;
use std::collections::BTreeMap;
use std::fmt::Display;
use std::ops::Neg;
use std::ops::Div;
use std::ops::Mul;
use std::ops::Add;
use std::ops::{AddAssign, Sub};
use std::cmp::max;
use std::collections::VecDeque;
use std::cmp::min;
use std::collections::{HashMap, BTreeSet};
use std::cmp::Ordering;
use std::fmt::Debug;

fn read_line() -> String {
    let mut buffer = String::new();
    std::io::stdin().read_line(&mut buffer).expect("No Line");
    buffer.trim().to_owned()
}
fn read_lines<T: std::str::FromStr>(count: usize) -> Vec<T> {
    let mut buffer = String::new();
    let mut vec = Vec::with_capacity(count);
    for _ in 0 .. count {
        std::io::stdin().read_line(&mut buffer).expect("No Line");
        vec.push(buffer.trim().parse().ok().expect("Can't Parse"));
        buffer.clear();
    }
    vec
}
fn read_tabulate<R, T: Fn(&str)->R> (count: usize, transformer: T) -> Vec<R> {
    let mut buffer = String::new();
    let mut vec = Vec::with_capacity(count);
    for _ in 0 .. count {
        std::io::stdin().read_line(&mut buffer).expect("No Line");
        vec.push(transformer(buffer.trim()));
        buffer.clear();
    }
    vec
}
fn read_value<T: std::str::FromStr>() -> T {
    read_line().trim().parse().ok().unwrap()
}
fn read_values<T: std::str::FromStr>() -> Vec<T> {
    read_line().trim().split_whitespace().map(|x| x.parse().ok().expect("Can't Parse")).collect::<Vec<T>>()
}
macro_rules! freeze {
    ($($id:ident), *) => {
        $(let $id = $id;)*
    };
}
macro_rules! read_map {
    ($ident: ident: [$block: block; $size: expr]) => {
        let $ident = (0 .. $size).into_iter().map(|_| $block).collect::<Vec<_>>();
    };
    (mut $ident: ident: [$block: block; $size: expr]) => {
        let mut $ident = (0 .. $size).into_iter().map(|_| $block).collect::<Vec<_>>();
    };
}
macro_rules! read {
    (mut $ident: ident: String) => {
        let mut $ident = read_value::<String>();
    };
    ($ident: ident: String) => {
        let $ident = read_value::<String>();
    };
    (mut $ident: ident: [$ty:ty]) => {
        let mut $ident = read_values::<$ty>();
    };
    ($ident: ident: [$ty:ty]) => {
        let $ident = read_values::<$ty>();
    };
    (mut $ident: ident: [[$ty:ty]; $size: expr]) => {
        let mut $ident = (0 .. $size).into_iter().map(|_| read_values::<$ty>()).collect::<Vec<_>>();
    };
    ($ident: ident: [[$ty:ty]; $size: expr]) => {
        let $ident = (0 .. $size).into_iter().map(|_| read_values::<$ty>()).collect::<Vec<_>>();
    };
    (mut $ident: ident: [$ty:ty; $size:expr]) => {
        let mut $ident = read_lines::<$ty>($size);
    };
    ($ident: ident: [$ty:ty; $size:expr]) => {
        let $ident = read_lines::<$ty>($size);
    };
    ($ident: ident: [$block: block; $size: expr]) => {
        let $ident = (0 .. $size).into_iter().map(|_| $block).collect::<Vec<_>>();
    };
    (mut $ident: ident: [$block: block; $size: expr]) => {
        let mut $ident = (0 .. $size).into_iter().map(|_| $block).collect::<Vec<_>>();
    };
    ($($token: tt)*) => {
        let mut iter = read_values::<String>().into_iter();
        read_from_iter!(iter; $($token)*);
    };
}
macro_rules! read_from_iter {
    ($iter:expr; mut $ident:ident:$ty:ty, $($rest:tt)*) => {
        let mut $ident = $iter.next().unwrap().parse::<$ty>().expect("Can't Parse");
        read_from_iter!($iter; $($rest)*);
    };
    ($iter:expr; $ident:ident:$ty:ty, $($rest:tt)*) => {
        let $ident = $iter.next().unwrap().parse::<$ty>().expect("Can't Parse");
        read_from_iter!($iter; $($rest)*);
    };
    ($iter:expr; mut $ident:ident:$ty:ty) => {
        let mut $ident = $iter.next().unwrap().parse::<$ty>().expect("Can't Parse");
    };
    ($iter:expr; $ident:ident:$ty:ty) => {
        let $ident = $iter.next().unwrap().parse::<$ty>().expect("Can't Parse");
    };
    ($iter: expr; ) => {};
}
struct KeyValue<K, V> {
    key: K,
    value: V
}
impl <K: PartialOrd, V> PartialEq for KeyValue<K, V> {
    fn eq(&self, other: &Self) -> bool {
        self.key.eq(&other.key)
    }
}
impl <K: PartialOrd, V> Eq for KeyValue<K, V> {}
impl <K: PartialOrd, V> PartialOrd for KeyValue<K, V> {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.key.partial_cmp(&other.key)
    }
}
impl <K: PartialOrd, V> Ord for KeyValue<K, V> {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.key.partial_cmp(&other.key).ok_or("Can't Compare").unwrap()
    }
}
#[derive(Copy, Clone, Debug)]
struct Position {
    x: i32, y: i32
}
impl Position {
    fn neighbor(&self) -> Vec<Position> {
        vec![(-1, 0), (0, -1)].into_iter().map(|(dx, dy)| Position{x: self.x + dx, y: self.y + dy} ).filter(|p| 0 <= p.x && p.x < 3 && 0 <= p.y && p.y < 3).collect()
    }
}
impl Sub for Position {
    type Output = Position;
    fn sub(self, rhs: Position) -> <Self as std::ops::Sub<Position>>::Output { 
        Position{x: self.x - rhs.x, y: self.y - rhs.y}
    }
}

#[derive(Clone, Debug)]
struct Piece {
    chars: Vec<char>
}
impl FromStr for Piece {
    type Err = ();
    fn from_str(s: &str) -> std::result::Result<Self, <Self as std::str::FromStr>::Err> { 
        if s.len() != 4 || s.chars().any(|c| !"rgbw".contains(c.to_ascii_lowercase())) {
            Err(())
        }else {
            Ok(Piece{chars: s.chars().collect()})
        }
    }
}
#[derive(Debug)]
struct Placed<'a> {
    piece: &'a Piece,
    position: Position,
    rotate: usize
}
impl <'a> Placed<'a> {
    fn up(&self) -> char {
        self.piece.chars[self.rotate & 3]
    }
    fn right(&self) -> char {
        self.piece.chars[(self.rotate + 1) & 3]
    }
    fn down(&self) -> char {
        self.piece.chars[(self.rotate + 2) & 3]
    }
    fn left(&self) -> char {
        self.piece.chars[(self.rotate + 3) & 3]
    }
    fn is_match<'b>(&self, other: &Placed<'b>) -> bool {
        match self.position - other.position {
            Position{x: -1, y: 0} => Placed::can_face(self.right(), other.left()),
            Position{x: 1, y: 0} => Placed::can_face(self.left(), other.right()),
            Position{x: 0, y: 1} => Placed::can_face(self.up(), other.down()),
            Position{x: 0, y: -1} => Placed::can_face(self.down(), other.up()),
            _ => panic!()
        }
    }
    fn can_face(a: char, b: char) -> bool {
        a != b && AsciiExt::to_ascii_uppercase(&a) == AsciiExt::to_ascii_uppercase(&b)
    }
}
fn put<'a>(pieces: &'a Vec<Piece>, state: &mut Vec<Placed<'a>>, used: &mut Vec<bool>) -> i32 {
    if state.len() == 9 {
        return 1;
    }
    let current = Position{x: state.len() as i32 % 3, y: state.len() as i32 / 3};
    let neighbor = current.neighbor();
    let mut count = 0;
    for i in 0 .. pieces.len() {
        if used[i] {continue;}
        for r in 0 .. 4 {
            let placed = Placed{piece: &pieces[i], position: current, rotate: r};
            if neighbor.iter().any(|pos| !state[pos.x as usize + (pos.y * 3) as usize].is_match(&placed)) {continue;}
            used[i] = true;
            state.push(placed);
            count += put(pieces, state, used);
            state.pop();
            used[i] = false;
        }
    }
    count
}

fn main() {
    read!(n: usize);
    let mut used = vec![false; 9];
    for _ in 0 .. n {
        read!(pieces: [Piece]);
        let mut state = Vec::with_capacity(9);
        println!("{}", put(&pieces, &mut state, &mut used));
    }
}


Measured memory_bytes: 5056