فهرست منبع

Working (cargo build) modules.

Steve Thielemann 9 ماه پیش
والد
کامیت
b374b0752f
3فایلهای تغییر یافته به همراه895 افزوده شده و 864 حذف شده
  1. 12 864
      src/main.rs
  2. 377 0
      src/sudoku.rs
  3. 506 0
      src/sudoku/group.rs

+ 12 - 864
src/main.rs

@@ -3,10 +3,20 @@ use serde_xml_rs::{from_reader, from_str, to_string};
 
 use clap::Parser;
 
-use std::fmt;
+// use std::fmt;
 use std::fs::File;
 
-use std::collections::HashSet;
+// If I don't use group here, it isn't available to sudoku module
+// If I use it here, I get warning about unused module.
+
+// mod group;
+// use crate::group::*;
+mod sudoku;
+use crate::sudoku::*;
+// use crate::sudoku::group::*;
+
+// use crate::group::*;
+// use crate::sudoku::*;
 
 #[derive(Debug, Serialize, Deserialize, PartialEq)]
 struct Graph {
@@ -37,868 +47,6 @@ struct Ksudoku {
     game: Game,
 }
 
-/// Width of the sudoku board.
-const WIDTH: u8 = 9;
-/// Size (width * height) of the board.
-const MAX_SIZE: u8 = 81;
-
-#[derive(Debug)]
-struct Sudoku {
-    board: [u8; MAX_SIZE as usize],
-    possible: [HashSet<u8>; MAX_SIZE as usize],
-}
-
-/// Translate x,y to position in board.
-const fn pos(x: u8, y: u8) -> u8 {
-    x + (y * WIDTH as u8)
-}
-
-/// Translate x,y (with starting index of 1) to position in board.
-const fn pos1(x: u8, y: u8) -> u8 {
-    (x - 1) + ((y - 1) * WIDTH as u8)
-}
-
-/// Translate post to x,y in board.
-const fn xy(pos: u8) -> (u8, u8) {
-    ((pos % WIDTH), (pos / WIDTH))
-}
-
-/*
-(0 .. 10)
-                        .map(|_| HashSet::<usize>::new())
-                        .collect();
-
-let arr: [Vec<u32>; 10] = [(); 10].map(|_| Vec::with_capacity(100));
-
-*/
-
-impl Sudoku {
-    fn new() -> Self {
-        // let b : HashSet<u8> = HashSet::from_iter(1..=9);
-
-        let s = Sudoku {
-            board: [0; MAX_SIZE as usize],
-            possible: [(); MAX_SIZE as usize].map(|_| HashSet::from_iter(1..=9)),
-            // possible: [HashSet::from_iter(1..=9); MAX_SIZE as usize],
-            // possible: [[0; SIZE as usize]; MAX_SIZE as usize],
-            // possible: [(0..MAX_SIZE).map( |_| (1..=9).collect())],
-            // possible: [(1..=9).map(|_| HashSet::new()).collect(); MAX_SIZE as usize],
-        };
-        s
-    }
-
-    fn clear(&mut self) {
-        for x in 0..MAX_SIZE {
-            self.board[x as usize] = 0;
-            self.possible = [(); MAX_SIZE as usize].map(|_| HashSet::from_iter(1..=9));
-            /*
-            self.possible[x as usize].clear();
-            for i in 1..=9 {
-                self.possible[x as usize].insert(i);
-            }
-            */
-            // self.possible[x as usize] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
-        }
-    }
-
-    fn load_xsudoku(&mut self, s: &str) {
-        self.clear();
-        let mut x: u8 = 0;
-        let mut y: u8 = 0;
-        for ch in s.chars() {
-            if ch >= 'b' {
-                self.set(x, y, ch as u8 - 'a' as u8);
-                // self.board[pos(x, y) as usize] = ch as u8 - 'a' as u8;
-            };
-            y += 1;
-            if y >= WIDTH {
-                y = 0;
-                x += 1;
-            }
-        }
-    }
-
-    fn set(&mut self, x: u8, y: u8, value: u8) {
-        self.board[pos(x, y) as usize] = value;
-        // Ok, update the possible
-        let mut g = for_row(y); 
-        // g.for_row(x, y);
-        for g in g.items {
-            // remove value from these sets.
-            self.possible[g as usize].take(&value);
-        }
-        g = for_column(x); 
-        // g.for_column(x, y);
-        for g in g.items {
-            // remove value from these sets.
-            self.possible[g as usize].take(&value);
-        }
-        g = for_cell(which_cell(x,y)); 
-        // g.for_block(x, y);
-        for g in g.items {
-            // remove value from these sets.
-            self.possible[g as usize].take(&value);
-        }
-        self.possible[pos(x, y) as usize].clear();
-    }
-
-    fn set2(&mut self, x: u8, y: u8, value: u8) {
-        self.board[pos(x, y) as usize] = value;
-        // Ok, update the possible
-        let mut g = Group::new();
-        g.for_row(x, y);
-        for g in g.items {
-            // remove value from these sets.
-            self.possible[g as usize].take(&value);
-        }
-        
-        g.for_column(x, y);
-        for g in g.items {
-            // remove value from these sets.
-            self.possible[g as usize].take(&value);
-        }
-        
-        g.for_block(x, y);
-        for g in g.items {
-            // remove value from these sets.
-            self.possible[g as usize].take(&value);
-        }
-        self.possible[pos(x, y) as usize].clear();
-    }
-
-
-    fn display(&self) {
-        println!("╔═══╦═══╦═══╗");
-        for y in 0..WIDTH {
-            print!("║");
-            for x in 0..WIDTH {
-                let item = self.board[pos(x as u8, y as u8) as usize];
-                if item == 0 {
-                    print!(" ");
-                } else if (item >= 1) && (item <= 9) {
-                    print!("{}", item);
-                }
-                if x % 3 == 2 {
-                    print!("║");
-                }
-            }
-            println!("");
-            if y % 3 == 2 {
-                if y + 1 == WIDTH {
-                    println!("╚═══╩═══╩═══╝");
-                } else {
-                    println!("╠═══╬═══╬═══╣");
-                }
-            }
-        }
-    }
-
-    fn display_possible(&self) {
-        for y in 0..WIDTH {
-            for x in 0..WIDTH {
-                let mut possible = String::new();
-
-                for p in self.possible[pos(x, y) as usize].iter() {
-                    possible += format!("{}", p).as_str();
-                }
-
-                // for i in 0..SIZE {
-                // let &pos = self.possible[pos(x, y) as usize];
-                print!("({},{}):", x, y);
-                // print!("{:20}", format!("{:?}", self.possible[pos(x, y) as usize]));
-                print!("{:9}", possible);
-                /*
-                if pos == 0 {
-                    print!(" ");
-                } else {
-                    print!("{}", pos);
-                }
-                */
-                // }
-                // print!(" ");
-            }
-            println!("");
-        }
-    }
-
-    fn solve(&mut self) -> bool {
-        // Pass 1: Look for singles in the possible sets.
-        let mut found_something = false;
-
-        for i in 0..MAX_SIZE {
-            if self.possible[i as usize].len() == 1 {
-                // Get the value
-                let value = self.possible[i as usize].iter().next().cloned().unwrap();
-                // Found one!
-                println!("Set1 {:?} to {}", xy(i), value);
-                self.set(xy(i).0, xy(i).1, value);
-                found_something = true;
-            }
-        }
-
-        let mut g = Group::new();
-        let mut values: HashSet<u8> = HashSet::new();
-
-        let mut group_process = |this: &mut Self, grp: &Group| {
-            // Collect all the possible values within the group.
-            values.clear();
-            for gidx in 0..WIDTH {
-                // println!("possible: {:?}", this.possible[grp.items[gidx as usize] as usize]);
-                values.extend(&this.possible[grp.items[gidx as usize] as usize]);
-                // println!("now     : {:?}", this.possible[grp.items[gidx as usize] as usize]);
-            }
-
-            // println!("values {:?}", values);
-
-            // Now, check for singles.
-            for v in values.iter() {
-                let mut count = 0;
-                let mut pos = 0;
-                for gidx in 0..WIDTH {
-                    if this.possible[grp.items[gidx as usize] as usize].contains(&v) {
-                        count += 1;
-                        pos = grp.items[gidx as usize];
-                        if count > 1 {
-                            break;
-                        }
-                    }
-                }
-                if count == 1 {
-                    // don't need this, it was v!
-                    // let value = this.possible[pos as usize].iter().next().cloned().unwrap();
-                    println!("Set2 {:?} to {}", xy(pos), v);
-                    this.set(xy(pos).0, xy(pos).1, *v);
-                    found_something = true;
-                }
-            }
-        };
-
-        // Change to 0..SIZE ...  Keep it simple.
-        for i in 0..WIDTH {
-            let mut g = for_column(i);
-            // g.for_column(i, 1);
-            group_process(self, &g);
-            g = for_row(i);
-            // g.for_row(1, i);
-            group_process(self, &g);
-            g = for_cell(i);
-            // g.for_iter(i);
-            group_process(self, &g);
-        }
-
-        println!("Looking for pairs...");
-        // PAIR processing.
-        for i in 0..WIDTH {
-            g.for_iter(i);
-
-            for gidx in 0..WIDTH - 1 {
-                let gpos = g.items[gidx as usize];
-                if self.possible[gpos as usize].len() == 2 {
-                    // Found a pair
-                    for fidx in gidx + 1..WIDTH {
-                        let fpos = g.items[fidx as usize];
-                        if self.possible[fpos as usize].len() == 2 {
-                            // Ok, there's another pair
-                            if self.possible[gpos as usize].is_subset(&self.possible[fpos as usize])
-                            {
-                                // Ok, they have the same values!
-                                // Ok, remove the items in the pair from the cell.
-                                // Don't touch the gpos/fpos records.  Keep those!
-                                let mut values: [u8; 2] = [0, 0];
-                                let mut vpos = 0;
-                                for z in self.possible[gpos as usize].iter() {
-                                    values[vpos] = *z;
-                                    vpos += 1;
-                                }
-
-                                let mut pair_removed = false;
-
-                                for remove in 0..WIDTH {
-                                    if (gidx == remove) || (fidx == remove) {
-                                        continue;
-                                    }
-                                    // Ok, these aren't the ones to save, so:
-                                    let rpos = g.items[remove as usize];
-                                    if self.possible[rpos as usize].take(&values[0]).is_some() {
-                                        found_something = true;
-                                        pair_removed = true;
-                                    };
-                                    if self.possible[rpos as usize].take(&values[1]).is_some() {
-                                        found_something = true;
-                                        pair_removed = true;
-                                    };
-                                }
-
-                                if pair_removed {
-                                    println!(
-                                        "Pair found! {} {}: {} {:?} and {} {:?} !",
-                                        gidx,
-                                        fidx,
-                                        gpos,
-                                        xy(gpos),
-                                        fpos,
-                                        xy(fpos)
-                                    );
-                                }
-
-                                // Check the x's and y's to see if we can also process a row/column too.
-                                if xy(gpos).0 == xy(fpos).0 {
-                                    // Matching X - process column
-                                    let column = xy(gpos).0;
-
-                                    vpos = 0;
-                                    for z in self.possible[gpos as usize].iter() {
-                                        values[vpos] = *z;
-                                        vpos += 1;
-                                    }
-                                    for remove in 0..WIDTH {
-                                        if (remove == xy(gpos).1) || (remove == xy(fpos).1) {
-                                            continue;
-                                        }
-                                        if self.possible[pos(column, remove) as usize]
-                                            .take(&values[0])
-                                            .is_some()
-                                        {
-                                            found_something = true;
-                                        };
-                                        if self.possible[pos(column, remove) as usize]
-                                            .take(&values[1])
-                                            .is_some()
-                                        {
-                                            found_something = true;
-                                        };
-                                    }
-                                }
-                                if xy(gpos).1 == xy(fpos).1 {
-                                    // Matching Y - process row
-                                    let row = xy(gpos).1;
-
-                                    vpos = 0;
-                                    for z in self.possible[gpos as usize].iter() {
-                                        values[vpos] = *z;
-                                        vpos += 1;
-                                    }
-                                    for remove in 0..WIDTH {
-                                        if (remove == xy(gpos).0) || (remove == xy(fpos).0) {
-                                            continue;
-                                        }
-                                        if self.possible[pos(remove, row) as usize]
-                                            .take(&values[0])
-                                            .is_some()
-                                        {
-                                            found_something = true;
-                                        };
-                                        if self.possible[pos(remove, row) as usize]
-                                            .take(&values[1])
-                                            .is_some()
-                                        {
-                                            found_something = true;
-                                        };
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        found_something
-    }
-}
-
-// #[derive(Debug)]
-/// Define a Group of sudoku positions.
-/// This defines rows, columns, and cells for analysis.
-#[derive(PartialEq)]
-struct Group {
-    items: [u8; WIDTH as usize],
-}
-
-/// Group of positions for rows.
-const GROUP_ROW: [Group; 9] = [
-    Group {
-        items: [
-            pos1(1, 1),
-            pos1(2, 1),
-            pos1(3, 1),
-            pos1(4, 1),
-            pos1(5, 1),
-            pos1(6, 1),
-            pos1(7, 1),
-            pos1(8, 1),
-            pos1(9, 1),
-        ],
-    },
-    Group {
-        items: [
-            pos1(1, 2),
-            pos1(2, 2),
-            pos1(3, 2),
-            pos1(4, 2),
-            pos1(5, 2),
-            pos1(6, 2),
-            pos1(7, 2),
-            pos1(8, 2),
-            pos1(9, 2),
-        ],
-    },
-    Group {
-        items: [
-            pos1(1, 3),
-            pos1(2, 3),
-            pos1(3, 3),
-            pos1(4, 3),
-            pos1(5, 3),
-            pos1(6, 3),
-            pos1(7, 3),
-            pos1(8, 3),
-            pos1(9, 3),
-        ],
-    },
-    Group {
-        items: [
-            pos1(1, 4),
-            pos1(2, 4),
-            pos1(3, 4),
-            pos1(4, 4),
-            pos1(5, 4),
-            pos1(6, 4),
-            pos1(7, 4),
-            pos1(8, 4),
-            pos1(9, 4),
-        ],
-    },
-    Group {
-        items: [
-            pos1(1, 5),
-            pos1(2, 5),
-            pos1(3, 5),
-            pos1(4, 5),
-            pos1(5, 5),
-            pos1(6, 5),
-            pos1(7, 5),
-            pos1(8, 5),
-            pos1(9, 5),
-        ],
-    },
-    Group {
-        items: [
-            pos1(1, 6),
-            pos1(2, 6),
-            pos1(3, 6),
-            pos1(4, 6),
-            pos1(5, 6),
-            pos1(6, 6),
-            pos1(7, 6),
-            pos1(8, 6),
-            pos1(9, 6),
-        ],
-    },
-    Group {
-        items: [
-            pos1(1, 7),
-            pos1(2, 7),
-            pos1(3, 7),
-            pos1(4, 7),
-            pos1(5, 7),
-            pos1(6, 7),
-            pos1(7, 7),
-            pos1(8, 7),
-            pos1(9, 7),
-        ],
-    },
-    Group {
-        items: [
-            pos1(1, 8),
-            pos1(2, 8),
-            pos1(3, 8),
-            pos1(4, 8),
-            pos1(5, 8),
-            pos1(6, 8),
-            pos1(7, 8),
-            pos1(8, 8),
-            pos1(9, 8),
-        ],
-    },
-    Group {
-        items: [
-            pos1(1, 9),
-            pos1(2, 9),
-            pos1(3, 9),
-            pos1(4, 9),
-            pos1(5, 9),
-            pos1(6, 9),
-            pos1(7, 9),
-            pos1(8, 9),
-            pos1(9, 9),
-        ],
-    },
-];
-
-/// Group of positions for columns.
-const GROUP_COLUMN: [Group; 9] = [
-    Group {
-        items: [
-            pos1(1, 1),
-            pos1(1, 2),
-            pos1(1, 3),
-            pos1(1, 4),
-            pos1(1, 5),
-            pos1(1, 6),
-            pos1(1, 7),
-            pos1(1, 8),
-            pos1(1, 9),
-        ],
-    },
-    Group {
-        items: [
-            pos1(2, 1),
-            pos1(2, 2),
-            pos1(2, 3),
-            pos1(2, 4),
-            pos1(2, 5),
-            pos1(2, 6),
-            pos1(2, 7),
-            pos1(2, 8),
-            pos1(2, 9),
-        ],
-    },
-    Group {
-        items: [
-            pos1(3, 1),
-            pos1(3, 2),
-            pos1(3, 3),
-            pos1(3, 4),
-            pos1(3, 5),
-            pos1(3, 6),
-            pos1(3, 7),
-            pos1(3, 8),
-            pos1(3, 9),
-        ],
-    },
-    Group {
-        items: [
-            pos1(4, 1),
-            pos1(4, 2),
-            pos1(4, 3),
-            pos1(4, 4),
-            pos1(4, 5),
-            pos1(4, 6),
-            pos1(4, 7),
-            pos1(4, 8),
-            pos1(4, 9),
-        ],
-    },
-    Group {
-        items: [
-            pos1(5, 1),
-            pos1(5, 2),
-            pos1(5, 3),
-            pos1(5, 4),
-            pos1(5, 5),
-            pos1(5, 6),
-            pos1(5, 7),
-            pos1(5, 8),
-            pos1(5, 9),
-        ],
-    },
-    Group {
-        items: [
-            pos1(6, 1),
-            pos1(6, 2),
-            pos1(6, 3),
-            pos1(6, 4),
-            pos1(6, 5),
-            pos1(6, 6),
-            pos1(6, 7),
-            pos1(6, 8),
-            pos1(6, 9),
-        ],
-    },
-    Group {
-        items: [
-            pos1(7, 1),
-            pos1(7, 2),
-            pos1(7, 3),
-            pos1(7, 4),
-            pos1(7, 5),
-            pos1(7, 6),
-            pos1(7, 7),
-            pos1(7, 8),
-            pos1(7, 9),
-        ],
-    },
-    Group {
-        items: [
-            pos1(8, 1),
-            pos1(8, 2),
-            pos1(8, 3),
-            pos1(8, 4),
-            pos1(8, 5),
-            pos1(8, 6),
-            pos1(8, 7),
-            pos1(8, 8),
-            pos1(8, 9),
-        ],
-    },
-    Group {
-        items: [
-            pos1(9, 1),
-            pos1(9, 2),
-            pos1(9, 3),
-            pos1(9, 4),
-            pos1(9, 5),
-            pos1(9, 6),
-            pos1(9, 7),
-            pos1(9, 8),
-            pos1(9, 9),
-        ],
-    },
-];
-
-/// Group of positions for cells (3x3 grid).
-const GROUP_CELL: [Group; 9] = [
-    Group {
-        items: [
-            pos1(1, 1),
-            pos1(2, 1),
-            pos1(3, 1),
-            pos1(1, 2),
-            pos1(2, 2),
-            pos1(3, 2),
-            pos1(1, 3),
-            pos1(2, 3),
-            pos1(3, 3),
-        ],
-    },
-    Group {
-        items: [
-            pos1(4, 1),
-            pos1(5, 1),
-            pos1(6, 1),
-            pos1(4, 2),
-            pos1(5, 2),
-            pos1(6, 2),
-            pos1(4, 3),
-            pos1(5, 3),
-            pos1(6, 3),
-        ],
-    },
-    Group {
-        items: [
-            pos1(7, 1),
-            pos1(8, 1),
-            pos1(9, 1),
-            pos1(7, 2),
-            pos1(8, 2),
-            pos1(9, 2),
-            pos1(7, 3),
-            pos1(8, 3),
-            pos1(9, 3),
-        ],
-    },
-    Group {
-        items: [
-            pos1(1, 4),
-            pos1(2, 4),
-            pos1(3, 4),
-            pos1(1, 5),
-            pos1(2, 5),
-            pos1(3, 5),
-            pos1(1, 6),
-            pos1(2, 6),
-            pos1(3, 6),
-        ],
-    },
-    Group {
-        items: [
-            pos1(4, 4),
-            pos1(5, 4),
-            pos1(6, 4),
-            pos1(4, 5),
-            pos1(5, 5),
-            pos1(6, 5),
-            pos1(4, 6),
-            pos1(5, 6),
-            pos1(6, 6),
-        ],
-    },
-    Group {
-        items: [
-            pos1(7, 4),
-            pos1(8, 4),
-            pos1(9, 4),
-            pos1(7, 5),
-            pos1(8, 5),
-            pos1(9, 5),
-            pos1(7, 6),
-            pos1(8, 6),
-            pos1(9, 6),
-        ],
-    },
-    Group {
-        items: [
-            pos1(1, 7),
-            pos1(2, 7),
-            pos1(3, 7),
-            pos1(1, 8),
-            pos1(2, 8),
-            pos1(3, 8),
-            pos1(1, 9),
-            pos1(2, 9),
-            pos1(3, 9),
-        ],
-    },
-    Group {
-        items: [
-            pos1(4, 7),
-            pos1(5, 7),
-            pos1(6, 7),
-            pos1(4, 8),
-            pos1(5, 8),
-            pos1(6, 8),
-            pos1(4, 9),
-            pos1(5, 9),
-            pos1(6, 9),
-        ],
-    },
-    Group {
-        items: [
-            pos1(7, 7),
-            pos1(8, 7),
-            pos1(9, 7),
-            pos1(7, 8),
-            pos1(8, 8),
-            pos1(9, 8),
-            pos1(7, 9),
-            pos1(8, 9),
-            pos1(9, 9),
-        ],
-    },
-];
-
-impl fmt::Debug for Group {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        if f.alternate() {
-            write!(f, "Group {{ {}[{},{}], {}[{},{}], {}[{},{}], {}[{},{}], {}[{},{}], {}[{},{}], {}[{},{}], {}[{},{}], {}[{},{}] }}",
-                self.items[0], xy(self.items[0]).0, xy(self.items[0]).1,
-                self.items[1], xy(self.items[1]).0, xy(self.items[1]).1,
-                self.items[2], xy(self.items[2]).0, xy(self.items[2]).1,
-                self.items[3], xy(self.items[3]).0, xy(self.items[3]).1,
-                self.items[4], xy(self.items[4]).0, xy(self.items[4]).1,
-                self.items[5], xy(self.items[5]).0, xy(self.items[5]).1,
-                self.items[6], xy(self.items[6]).0, xy(self.items[6]).1,
-                self.items[7], xy(self.items[7]).0, xy(self.items[7]).1,
-                self.items[8], xy(self.items[8]).0, xy(self.items[8]).1,
-            )
-        } else {
-            f.debug_struct("Group").field("items", &self.items).finish()
-        }
-    }
-}
-
-fn for_column(i: u8) -> &'static Group {
-    &GROUP_COLUMN[i as usize]
-}
-
-fn for_row(i: u8) -> &'static Group {
-    &GROUP_ROW[i as usize]
-}
-
-fn for_cell(i: u8) -> &'static Group {
-    &GROUP_CELL[i as usize]
-}
-
-/// Which cell contains this x,y?
-fn which_cell(x:u8, y:u8) -> u8 {
-    (x/3)+(y/3)*3
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::Group;
-    use crate::{for_cell, for_column, for_row};
-
-    #[test]
-    fn check_columns() {
-        let mut g = Group::new();
-
-        for i in 0..9 {
-            println!("Index={}", i);
-            g.for_column(i, 1);
-            let new_g = for_column(i);
-            assert_eq!(g, *new_g);
-        }
-    }
-
-    #[test]
-    fn check_rows() {
-        let mut g = Group::new();
-
-        for i in 0..9 {
-            println!("Index={}", i);
-            g.for_row(1, i);
-            let new_g = for_row(i);
-            assert_eq!(g, *new_g);
-        }
-    }
-
-    #[test]
-    fn check_cells() {
-        let mut g = Group::new();
-        for i in 0..9 {
-            println!("Index={}", i);
-            g.for_block((i % 3) * 3, (i / 3) * 3);
-            let new_g = for_cell(i);
-            assert_eq!(g, *new_g);
-        }
-    }
-}
-
-impl Group {
-    fn new() -> Self {
-        Group {
-            items: [0; WIDTH as usize],
-        }
-    }
-
-    fn for_column(&mut self, x: u8, _y: u8) {
-        for y in 0..WIDTH {
-            self.items[y as usize] = pos(x, y);
-        }
-    }
-    fn for_row(&mut self, _x: u8, y: u8) {
-        for x in 0..WIDTH {
-            self.items[x as usize] = pos(x, y);
-        }
-    }
-    fn for_block(&mut self, x: u8, y: u8) {
-        // Find starting block positions
-        let sb_x = x - (x % 3);
-        let sb_y = y - (y % 3);
-        for i in 0..WIDTH {
-            let ix = i % 3;
-            let iy = i / 3;
-            // println!("i = {}, sb.x = {} sb.y = {}, ix = {} iy = {}", i, sb_x, sb_y, ix, iy);
-            self.items[i as usize] = pos(sb_x + ix, sb_y + iy);
-        }
-    }
-
-    fn for_iter(&mut self, i: u8) {
-        let sb_x = (i % 3) * 3;
-        let sb_y = (i / 3) * 3;
-        self.for_block(sb_x, sb_y);
-    }
-
-    fn display(&self) {
-        for i in 0..WIDTH {
-            let v = self.items[i as usize];
-            print!("{} [{},{}] ", v, xy(v).0, xy(v).1);
-        }
-        println!("");
-    }
-}
 
 // Command line arguments - via clap
 

+ 377 - 0
src/sudoku.rs

@@ -0,0 +1,377 @@
+pub mod group;
+use group::*;
+// use crate::group::*;
+// use super::group::*;
+// use crate::group::*;
+
+use std::collections::HashSet;
+
+/// Width of the sudoku board.
+const WIDTH: u8 = 9;
+/// Size (width * height) of the board.
+const MAX_SIZE: u8 = 81;
+
+#[derive(Debug)]
+pub struct Sudoku {
+    pub board: [u8; MAX_SIZE as usize],
+    pub possible: [HashSet<u8>; MAX_SIZE as usize],
+}
+
+/// Translate x,y to position in board.
+pub const fn pos(x: u8, y: u8) -> u8 {
+    x + (y * WIDTH as u8)
+}
+
+/// Translate x,y (with starting index of 1) to position in board.
+pub const fn pos1(x: u8, y: u8) -> u8 {
+    (x - 1) + ((y - 1) * WIDTH as u8)
+}
+
+/// Translate post to x,y in board.
+pub const fn xy(pos: u8) -> (u8, u8) {
+    ((pos % WIDTH), (pos / WIDTH))
+}
+
+/*
+(0 .. 10)
+                        .map(|_| HashSet::<usize>::new())
+                        .collect();
+
+let arr: [Vec<u32>; 10] = [(); 10].map(|_| Vec::with_capacity(100));
+
+*/
+
+impl Sudoku {
+    pub fn new() -> Self {
+        // let b : HashSet<u8> = HashSet::from_iter(1..=9);
+
+        let s = Sudoku {
+            board: [0; MAX_SIZE as usize],
+            possible: [(); MAX_SIZE as usize].map(|_| HashSet::from_iter(1..=9)),
+            // possible: [HashSet::from_iter(1..=9); MAX_SIZE as usize],
+            // possible: [[0; SIZE as usize]; MAX_SIZE as usize],
+            // possible: [(0..MAX_SIZE).map( |_| (1..=9).collect())],
+            // possible: [(1..=9).map(|_| HashSet::new()).collect(); MAX_SIZE as usize],
+        };
+        s
+    }
+
+    pub fn clear(&mut self) {
+        for x in 0..MAX_SIZE {
+            self.board[x as usize] = 0;
+            self.possible = [(); MAX_SIZE as usize].map(|_| HashSet::from_iter(1..=9));
+            /*
+            self.possible[x as usize].clear();
+            for i in 1..=9 {
+                self.possible[x as usize].insert(i);
+            }
+            */
+            // self.possible[x as usize] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
+        }
+    }
+
+    pub fn load_xsudoku(&mut self, s: &str) {
+        self.clear();
+        let mut x: u8 = 0;
+        let mut y: u8 = 0;
+        for ch in s.chars() {
+            if ch >= 'b' {
+                self.set(x, y, ch as u8 - 'a' as u8);
+                // self.board[pos(x, y) as usize] = ch as u8 - 'a' as u8;
+            };
+            y += 1;
+            if y >= WIDTH {
+                y = 0;
+                x += 1;
+            }
+        }
+    }
+
+    pub fn set(&mut self, x: u8, y: u8, value: u8) {
+        self.board[pos(x, y) as usize] = value;
+        // Ok, update the possible
+        let mut g = for_row(y); 
+        // g.for_row(x, y);
+        for g in g.items {
+            // remove value from these sets.
+            self.possible[g as usize].take(&value);
+        }
+        g = for_column(x); 
+        // g.for_column(x, y);
+        for g in g.items {
+            // remove value from these sets.
+            self.possible[g as usize].take(&value);
+        }
+        g = for_cell(which_cell(x,y)); 
+        // g.for_block(x, y);
+        for g in g.items {
+            // remove value from these sets.
+            self.possible[g as usize].take(&value);
+        }
+        self.possible[pos(x, y) as usize].clear();
+    }
+
+    pub fn set2(&mut self, x: u8, y: u8, value: u8) {
+        self.board[pos(x, y) as usize] = value;
+        // Ok, update the possible
+        let mut g = Group::new();
+        g.for_row(x, y);
+        for g in g.items {
+            // remove value from these sets.
+            self.possible[g as usize].take(&value);
+        }
+        
+        g.for_column(x, y);
+        for g in g.items {
+            // remove value from these sets.
+            self.possible[g as usize].take(&value);
+        }
+        
+        g.for_block(x, y);
+        for g in g.items {
+            // remove value from these sets.
+            self.possible[g as usize].take(&value);
+        }
+        self.possible[pos(x, y) as usize].clear();
+    }
+
+
+    pub fn display(&self) {
+        println!("╔═══╦═══╦═══╗");
+        for y in 0..WIDTH {
+            print!("║");
+            for x in 0..WIDTH {
+                let item = self.board[pos(x as u8, y as u8) as usize];
+                if item == 0 {
+                    print!(" ");
+                } else if (item >= 1) && (item <= 9) {
+                    print!("{}", item);
+                }
+                if x % 3 == 2 {
+                    print!("║");
+                }
+            }
+            println!("");
+            if y % 3 == 2 {
+                if y + 1 == WIDTH {
+                    println!("╚═══╩═══╩═══╝");
+                } else {
+                    println!("╠═══╬═══╬═══╣");
+                }
+            }
+        }
+    }
+
+    pub fn display_possible(&self) {
+        for y in 0..WIDTH {
+            for x in 0..WIDTH {
+                let mut possible = String::new();
+
+                for p in self.possible[pos(x, y) as usize].iter() {
+                    possible += format!("{}", p).as_str();
+                }
+
+                // for i in 0..SIZE {
+                // let &pos = self.possible[pos(x, y) as usize];
+                print!("({},{}):", x, y);
+                // print!("{:20}", format!("{:?}", self.possible[pos(x, y) as usize]));
+                print!("{:9}", possible);
+                /*
+                if pos == 0 {
+                    print!(" ");
+                } else {
+                    print!("{}", pos);
+                }
+                */
+                // }
+                // print!(" ");
+            }
+            println!("");
+        }
+    }
+
+    pub fn solve(&mut self) -> bool {
+        // Pass 1: Look for singles in the possible sets.
+        let mut found_something = false;
+
+        for i in 0..MAX_SIZE {
+            if self.possible[i as usize].len() == 1 {
+                // Get the value
+                let value = self.possible[i as usize].iter().next().cloned().unwrap();
+                // Found one!
+                println!("Set1 {:?} to {}", xy(i), value);
+                self.set(xy(i).0, xy(i).1, value);
+                found_something = true;
+            }
+        }
+
+        let mut g = Group::new();
+        let mut values: HashSet<u8> = HashSet::new();
+
+        let mut group_process = |this: &mut Self, grp: &Group| {
+            // Collect all the possible values within the group.
+            values.clear();
+            for gidx in 0..WIDTH {
+                // println!("possible: {:?}", this.possible[grp.items[gidx as usize] as usize]);
+                values.extend(&this.possible[grp.items[gidx as usize] as usize]);
+                // println!("now     : {:?}", this.possible[grp.items[gidx as usize] as usize]);
+            }
+
+            // println!("values {:?}", values);
+
+            // Now, check for singles.
+            for v in values.iter() {
+                let mut count = 0;
+                let mut pos = 0;
+                for gidx in 0..WIDTH {
+                    if this.possible[grp.items[gidx as usize] as usize].contains(&v) {
+                        count += 1;
+                        pos = grp.items[gidx as usize];
+                        if count > 1 {
+                            break;
+                        }
+                    }
+                }
+                if count == 1 {
+                    // don't need this, it was v!
+                    // let value = this.possible[pos as usize].iter().next().cloned().unwrap();
+                    println!("Set2 {:?} to {}", xy(pos), v);
+                    this.set(xy(pos).0, xy(pos).1, *v);
+                    found_something = true;
+                }
+            }
+        };
+
+        // Change to 0..SIZE ...  Keep it simple.
+        for i in 0..WIDTH {
+            let mut g = for_column(i);
+            // g.for_column(i, 1);
+            group_process(self, &g);
+            g = for_row(i);
+            // g.for_row(1, i);
+            group_process(self, &g);
+            g = for_cell(i);
+            // g.for_iter(i);
+            group_process(self, &g);
+        }
+
+        println!("Looking for pairs...");
+        // PAIR processing.
+        for i in 0..WIDTH {
+            g.for_iter(i);
+
+            for gidx in 0..WIDTH - 1 {
+                let gpos = g.items[gidx as usize];
+                if self.possible[gpos as usize].len() == 2 {
+                    // Found a pair
+                    for fidx in gidx + 1..WIDTH {
+                        let fpos = g.items[fidx as usize];
+                        if self.possible[fpos as usize].len() == 2 {
+                            // Ok, there's another pair
+                            if self.possible[gpos as usize].is_subset(&self.possible[fpos as usize])
+                            {
+                                // Ok, they have the same values!
+                                // Ok, remove the items in the pair from the cell.
+                                // Don't touch the gpos/fpos records.  Keep those!
+                                let mut values: [u8; 2] = [0, 0];
+                                let mut vpos = 0;
+                                for z in self.possible[gpos as usize].iter() {
+                                    values[vpos] = *z;
+                                    vpos += 1;
+                                }
+
+                                let mut pair_removed = false;
+
+                                for remove in 0..WIDTH {
+                                    if (gidx == remove) || (fidx == remove) {
+                                        continue;
+                                    }
+                                    // Ok, these aren't the ones to save, so:
+                                    let rpos = g.items[remove as usize];
+                                    if self.possible[rpos as usize].take(&values[0]).is_some() {
+                                        found_something = true;
+                                        pair_removed = true;
+                                    };
+                                    if self.possible[rpos as usize].take(&values[1]).is_some() {
+                                        found_something = true;
+                                        pair_removed = true;
+                                    };
+                                }
+
+                                if pair_removed {
+                                    println!(
+                                        "Pair found! {} {}: {} {:?} and {} {:?} !",
+                                        gidx,
+                                        fidx,
+                                        gpos,
+                                        xy(gpos),
+                                        fpos,
+                                        xy(fpos)
+                                    );
+                                }
+
+                                // Check the x's and y's to see if we can also process a row/column too.
+                                if xy(gpos).0 == xy(fpos).0 {
+                                    // Matching X - process column
+                                    let column = xy(gpos).0;
+
+                                    vpos = 0;
+                                    for z in self.possible[gpos as usize].iter() {
+                                        values[vpos] = *z;
+                                        vpos += 1;
+                                    }
+                                    for remove in 0..WIDTH {
+                                        if (remove == xy(gpos).1) || (remove == xy(fpos).1) {
+                                            continue;
+                                        }
+                                        if self.possible[pos(column, remove) as usize]
+                                            .take(&values[0])
+                                            .is_some()
+                                        {
+                                            found_something = true;
+                                        };
+                                        if self.possible[pos(column, remove) as usize]
+                                            .take(&values[1])
+                                            .is_some()
+                                        {
+                                            found_something = true;
+                                        };
+                                    }
+                                }
+                                if xy(gpos).1 == xy(fpos).1 {
+                                    // Matching Y - process row
+                                    let row = xy(gpos).1;
+
+                                    vpos = 0;
+                                    for z in self.possible[gpos as usize].iter() {
+                                        values[vpos] = *z;
+                                        vpos += 1;
+                                    }
+                                    for remove in 0..WIDTH {
+                                        if (remove == xy(gpos).0) || (remove == xy(fpos).0) {
+                                            continue;
+                                        }
+                                        if self.possible[pos(remove, row) as usize]
+                                            .take(&values[0])
+                                            .is_some()
+                                        {
+                                            found_something = true;
+                                        };
+                                        if self.possible[pos(remove, row) as usize]
+                                            .take(&values[1])
+                                            .is_some()
+                                        {
+                                            found_something = true;
+                                        };
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        found_something
+    }
+}

+ 506 - 0
src/sudoku/group.rs

@@ -0,0 +1,506 @@
+use std::fmt;
+
+// use super::sudoku::*;
+use crate::sudoku::*;
+// mod sudoku;
+// use crate::sudoku::*;
+
+/// Width of the sudoku board.
+const WIDTH: u8 = 9;
+/// Size (width * height) of the board.
+const MAX_SIZE: u8 = 81;
+
+// #[derive(Debug)]
+/// Define a Group of sudoku positions.
+/// This defines rows, columns, and cells for analysis.
+#[derive(PartialEq)]
+pub struct Group {
+    pub items: [u8; WIDTH as usize],
+}
+
+struct Group2([u8;WIDTH as usize]);
+
+/// Group of positions for rows.
+const GROUP_ROW: [Group; 9] = [
+    Group {
+        items: [
+            pos1(1, 1),
+            pos1(2, 1),
+            pos1(3, 1),
+            pos1(4, 1),
+            pos1(5, 1),
+            pos1(6, 1),
+            pos1(7, 1),
+            pos1(8, 1),
+            pos1(9, 1),
+        ],
+    },
+    Group {
+        items: [
+            pos1(1, 2),
+            pos1(2, 2),
+            pos1(3, 2),
+            pos1(4, 2),
+            pos1(5, 2),
+            pos1(6, 2),
+            pos1(7, 2),
+            pos1(8, 2),
+            pos1(9, 2),
+        ],
+    },
+    Group {
+        items: [
+            pos1(1, 3),
+            pos1(2, 3),
+            pos1(3, 3),
+            pos1(4, 3),
+            pos1(5, 3),
+            pos1(6, 3),
+            pos1(7, 3),
+            pos1(8, 3),
+            pos1(9, 3),
+        ],
+    },
+    Group {
+        items: [
+            pos1(1, 4),
+            pos1(2, 4),
+            pos1(3, 4),
+            pos1(4, 4),
+            pos1(5, 4),
+            pos1(6, 4),
+            pos1(7, 4),
+            pos1(8, 4),
+            pos1(9, 4),
+        ],
+    },
+    Group {
+        items: [
+            pos1(1, 5),
+            pos1(2, 5),
+            pos1(3, 5),
+            pos1(4, 5),
+            pos1(5, 5),
+            pos1(6, 5),
+            pos1(7, 5),
+            pos1(8, 5),
+            pos1(9, 5),
+        ],
+    },
+    Group {
+        items: [
+            pos1(1, 6),
+            pos1(2, 6),
+            pos1(3, 6),
+            pos1(4, 6),
+            pos1(5, 6),
+            pos1(6, 6),
+            pos1(7, 6),
+            pos1(8, 6),
+            pos1(9, 6),
+        ],
+    },
+    Group {
+        items: [
+            pos1(1, 7),
+            pos1(2, 7),
+            pos1(3, 7),
+            pos1(4, 7),
+            pos1(5, 7),
+            pos1(6, 7),
+            pos1(7, 7),
+            pos1(8, 7),
+            pos1(9, 7),
+        ],
+    },
+    Group {
+        items: [
+            pos1(1, 8),
+            pos1(2, 8),
+            pos1(3, 8),
+            pos1(4, 8),
+            pos1(5, 8),
+            pos1(6, 8),
+            pos1(7, 8),
+            pos1(8, 8),
+            pos1(9, 8),
+        ],
+    },
+    Group {
+        items: [
+            pos1(1, 9),
+            pos1(2, 9),
+            pos1(3, 9),
+            pos1(4, 9),
+            pos1(5, 9),
+            pos1(6, 9),
+            pos1(7, 9),
+            pos1(8, 9),
+            pos1(9, 9),
+        ],
+    },
+];
+
+/// Group of positions for columns.
+const GROUP_COLUMN: [Group; 9] = [
+    Group {
+        items: [
+            pos1(1, 1),
+            pos1(1, 2),
+            pos1(1, 3),
+            pos1(1, 4),
+            pos1(1, 5),
+            pos1(1, 6),
+            pos1(1, 7),
+            pos1(1, 8),
+            pos1(1, 9),
+        ],
+    },
+    Group {
+        items: [
+            pos1(2, 1),
+            pos1(2, 2),
+            pos1(2, 3),
+            pos1(2, 4),
+            pos1(2, 5),
+            pos1(2, 6),
+            pos1(2, 7),
+            pos1(2, 8),
+            pos1(2, 9),
+        ],
+    },
+    Group {
+        items: [
+            pos1(3, 1),
+            pos1(3, 2),
+            pos1(3, 3),
+            pos1(3, 4),
+            pos1(3, 5),
+            pos1(3, 6),
+            pos1(3, 7),
+            pos1(3, 8),
+            pos1(3, 9),
+        ],
+    },
+    Group {
+        items: [
+            pos1(4, 1),
+            pos1(4, 2),
+            pos1(4, 3),
+            pos1(4, 4),
+            pos1(4, 5),
+            pos1(4, 6),
+            pos1(4, 7),
+            pos1(4, 8),
+            pos1(4, 9),
+        ],
+    },
+    Group {
+        items: [
+            pos1(5, 1),
+            pos1(5, 2),
+            pos1(5, 3),
+            pos1(5, 4),
+            pos1(5, 5),
+            pos1(5, 6),
+            pos1(5, 7),
+            pos1(5, 8),
+            pos1(5, 9),
+        ],
+    },
+    Group {
+        items: [
+            pos1(6, 1),
+            pos1(6, 2),
+            pos1(6, 3),
+            pos1(6, 4),
+            pos1(6, 5),
+            pos1(6, 6),
+            pos1(6, 7),
+            pos1(6, 8),
+            pos1(6, 9),
+        ],
+    },
+    Group {
+        items: [
+            pos1(7, 1),
+            pos1(7, 2),
+            pos1(7, 3),
+            pos1(7, 4),
+            pos1(7, 5),
+            pos1(7, 6),
+            pos1(7, 7),
+            pos1(7, 8),
+            pos1(7, 9),
+        ],
+    },
+    Group {
+        items: [
+            pos1(8, 1),
+            pos1(8, 2),
+            pos1(8, 3),
+            pos1(8, 4),
+            pos1(8, 5),
+            pos1(8, 6),
+            pos1(8, 7),
+            pos1(8, 8),
+            pos1(8, 9),
+        ],
+    },
+    Group {
+        items: [
+            pos1(9, 1),
+            pos1(9, 2),
+            pos1(9, 3),
+            pos1(9, 4),
+            pos1(9, 5),
+            pos1(9, 6),
+            pos1(9, 7),
+            pos1(9, 8),
+            pos1(9, 9),
+        ],
+    },
+];
+
+/// Group of positions for cells (3x3 grid).
+const GROUP_CELL: [Group; 9] = [
+    Group {
+        items: [
+            pos1(1, 1),
+            pos1(2, 1),
+            pos1(3, 1),
+            pos1(1, 2),
+            pos1(2, 2),
+            pos1(3, 2),
+            pos1(1, 3),
+            pos1(2, 3),
+            pos1(3, 3),
+        ],
+    },
+    Group {
+        items: [
+            pos1(4, 1),
+            pos1(5, 1),
+            pos1(6, 1),
+            pos1(4, 2),
+            pos1(5, 2),
+            pos1(6, 2),
+            pos1(4, 3),
+            pos1(5, 3),
+            pos1(6, 3),
+        ],
+    },
+    Group {
+        items: [
+            pos1(7, 1),
+            pos1(8, 1),
+            pos1(9, 1),
+            pos1(7, 2),
+            pos1(8, 2),
+            pos1(9, 2),
+            pos1(7, 3),
+            pos1(8, 3),
+            pos1(9, 3),
+        ],
+    },
+    Group {
+        items: [
+            pos1(1, 4),
+            pos1(2, 4),
+            pos1(3, 4),
+            pos1(1, 5),
+            pos1(2, 5),
+            pos1(3, 5),
+            pos1(1, 6),
+            pos1(2, 6),
+            pos1(3, 6),
+        ],
+    },
+    Group {
+        items: [
+            pos1(4, 4),
+            pos1(5, 4),
+            pos1(6, 4),
+            pos1(4, 5),
+            pos1(5, 5),
+            pos1(6, 5),
+            pos1(4, 6),
+            pos1(5, 6),
+            pos1(6, 6),
+        ],
+    },
+    Group {
+        items: [
+            pos1(7, 4),
+            pos1(8, 4),
+            pos1(9, 4),
+            pos1(7, 5),
+            pos1(8, 5),
+            pos1(9, 5),
+            pos1(7, 6),
+            pos1(8, 6),
+            pos1(9, 6),
+        ],
+    },
+    Group {
+        items: [
+            pos1(1, 7),
+            pos1(2, 7),
+            pos1(3, 7),
+            pos1(1, 8),
+            pos1(2, 8),
+            pos1(3, 8),
+            pos1(1, 9),
+            pos1(2, 9),
+            pos1(3, 9),
+        ],
+    },
+    Group {
+        items: [
+            pos1(4, 7),
+            pos1(5, 7),
+            pos1(6, 7),
+            pos1(4, 8),
+            pos1(5, 8),
+            pos1(6, 8),
+            pos1(4, 9),
+            pos1(5, 9),
+            pos1(6, 9),
+        ],
+    },
+    Group {
+        items: [
+            pos1(7, 7),
+            pos1(8, 7),
+            pos1(9, 7),
+            pos1(7, 8),
+            pos1(8, 8),
+            pos1(9, 8),
+            pos1(7, 9),
+            pos1(8, 9),
+            pos1(9, 9),
+        ],
+    },
+];
+
+impl fmt::Debug for Group {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if f.alternate() {
+            write!(f, "Group {{ {}[{},{}], {}[{},{}], {}[{},{}], {}[{},{}], {}[{},{}], {}[{},{}], {}[{},{}], {}[{},{}], {}[{},{}] }}",
+                self.items[0], xy(self.items[0]).0, xy(self.items[0]).1,
+                self.items[1], xy(self.items[1]).0, xy(self.items[1]).1,
+                self.items[2], xy(self.items[2]).0, xy(self.items[2]).1,
+                self.items[3], xy(self.items[3]).0, xy(self.items[3]).1,
+                self.items[4], xy(self.items[4]).0, xy(self.items[4]).1,
+                self.items[5], xy(self.items[5]).0, xy(self.items[5]).1,
+                self.items[6], xy(self.items[6]).0, xy(self.items[6]).1,
+                self.items[7], xy(self.items[7]).0, xy(self.items[7]).1,
+                self.items[8], xy(self.items[8]).0, xy(self.items[8]).1,
+            )
+        } else {
+            f.debug_struct("Group").field("items", &self.items).finish()
+        }
+    }
+}
+
+pub fn for_column(i: u8) -> &'static Group {
+    &GROUP_COLUMN[i as usize]
+}
+
+pub fn for_row(i: u8) -> &'static Group {
+    &GROUP_ROW[i as usize]
+}
+
+pub fn for_cell(i: u8) -> &'static Group {
+    &GROUP_CELL[i as usize]
+}
+
+/// Which cell contains this x,y?
+pub fn which_cell(x:u8, y:u8) -> u8 {
+    (x/3)+(y/3)*3
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::Group;
+    use crate::{for_cell, for_column, for_row};
+
+    #[test]
+    fn check_columns() {
+        let mut g = Group::new();
+
+        for i in 0..9 {
+            println!("Index={}", i);
+            g.for_column(i, 1);
+            let new_g = for_column(i);
+            assert_eq!(g, *new_g);
+        }
+    }
+
+    #[test]
+    fn check_rows() {
+        let mut g = Group::new();
+
+        for i in 0..9 {
+            println!("Index={}", i);
+            g.for_row(1, i);
+            let new_g = for_row(i);
+            assert_eq!(g, *new_g);
+        }
+    }
+
+    #[test]
+    fn check_cells() {
+        let mut g = Group::new();
+        for i in 0..9 {
+            println!("Index={}", i);
+            g.for_block((i % 3) * 3, (i / 3) * 3);
+            let new_g = for_cell(i);
+            assert_eq!(g, *new_g);
+        }
+    }
+}
+
+impl Group {
+    pub fn new() -> Self {
+        Group {
+            items: [0; WIDTH as usize],
+        }
+    }
+
+    pub fn for_column(&mut self, x: u8, _y: u8) {
+        for y in 0..WIDTH {
+            self.items[y as usize] = pos(x, y);
+        }
+    }
+    pub fn for_row(&mut self, _x: u8, y: u8) {
+        for x in 0..WIDTH {
+            self.items[x as usize] = pos(x, y);
+        }
+    }
+    pub fn for_block(&mut self, x: u8, y: u8) {
+        // Find starting block positions
+        let sb_x = x - (x % 3);
+        let sb_y = y - (y % 3);
+        for i in 0..WIDTH {
+            let ix = i % 3;
+            let iy = i / 3;
+            // println!("i = {}, sb.x = {} sb.y = {}, ix = {} iy = {}", i, sb_x, sb_y, ix, iy);
+            self.items[i as usize] = pos(sb_x + ix, sb_y + iy);
+        }
+    }
+
+    pub fn for_iter(&mut self, i: u8) {
+        let sb_x = (i % 3) * 3;
+        let sb_y = (i / 3) * 3;
+        self.for_block(sb_x, sb_y);
+    }
+
+    pub fn display(&self) {
+        for i in 0..WIDTH {
+            let v = self.items[i as usize];
+            print!("{} [{},{}] ", v, xy(v).0, xy(v).1);
+        }
+        println!("");
+    }
+}