<file_sep>import { SudokuCellPosition } from "./SudokuCellPosition";

describe( 'SudokuCellPosition', () => {
  describe( '.row', () => {
    it( 'returns 0 for too low out of range values', () => {
      let cellPosition = new SudokuCellPosition( 0 );
      expect( cellPosition.row ).toBe( 0 );
    });
    it( 'returns 0 for too high out of range values', () => {
      let cellPosition = new SudokuCellPosition( 82 );
      expect( cellPosition.row ).toBe( 0 );
    });
    it( 'returns 1 for cellIndex 1', () => {
      let cellPosition = new SudokuCellPosition( 1 );
      expect( cellPosition.row ).toBe( 1 );
    });
    it( 'returns 2 for cellIndex 10', () => {
      let cellPosition = new SudokuCellPosition( 10 );
      expect( cellPosition.row ).toBe( 2 );
    });
    it( 'returns 2 for cellIndex 18', () => {
      let cellPosition = new SudokuCellPosition( 18 );
      expect( cellPosition.row ).toBe( 2 );
    });
  });
  describe( '.column', () => {
    it( 'returns 0 for too low out of range values', () => {
      let cellPosition = new SudokuCellPosition( 0 );
      expect( cellPosition.column ).toBe( 0 );
    });
    it( 'returns 0 for too high out of range values', () => {
      let cellPosition = new SudokuCellPosition( 82 );
      expect( cellPosition.column ).toBe( 0 );
    });
    it( 'returns 1 for cellIndex 1', () => {
      let cellPosition = new SudokuCellPosition( 1 );
      expect( cellPosition.column ).toBe( 1 );
    });
    it( 'returns 2 for cellIndex 11', () => {
      let cellPosition = new SudokuCellPosition( 11 );
      expect( cellPosition.column ).toBe( 2 );
    });
    it( 'returns 1 for cellIndex 9', () => {
      let cellPosition = new SudokuCellPosition( 9 );
      expect( cellPosition.column ).toBe( 9 );
    });
  });
  describe( '.square', () => {
    it( 'returns 0 for too low out of range values', () => {
      let cellPosition = new SudokuCellPosition( 0 );
      expect( cellPosition.square ).toBe( 0 );
    });
    it( 'returns 0 for too high out of range values', () => {
      let cellPosition = new SudokuCellPosition( 82 );
      expect( cellPosition.square ).toBe( 0 );
    });
    it( 'returns 1 for cellIndex 1' , () => {
      let cellPosition = new SudokuCellPosition( 1 );
      expect( cellPosition.square ).toBe( 1 );
    });
    it( 'returns 2 for cellIndex 4' , () => {
      let cellPosition = new SudokuCellPosition( 4 );
      expect( cellPosition.square ).toBe( 2 );
    });
    it( 'returns 1 for cellIndex 19' , () => {
      let cellPosition = new SudokuCellPosition( 28 );
      expect( cellPosition.square ).toBe( 4 );
    });
  });
});
<file_sep>import { CellSolver } from '../CellSolver';
import { Utility } from "../../../Utility";

export class RowCellSolver extends CellSolver {
  private _row: string;

  constructor( sudoku: string ) {
    super( sudoku );
  }
  public solve( cellIndex: number): number {
    if( this.isAlreadySolved( cellIndex ) === true )
      throw new Error('The cell you are trying to solve is already solved.');

    let rowIndex = Utility.cellPosition( cellIndex ).row;
    this._row = this._slicer.sliceRow( rowIndex );

    if( this.hasOneUnsolvedCellInRow() === false )
      return 0;

    let i = 0;
    while( this._row.includes( i.toString() ) )
      i++;

    return i;
  }
  private hasOneUnsolvedCellInRow(): boolean {
    return this._row.indexOf('0') === this._row.lastIndexOf('0');
  }
}
<file_sep>import { CellSolver } from "../CellSolver";
import { Utility } from "../../../Utility";

export class ColumnCellSolver extends CellSolver {
  private _column: string;

  canSolve(index: number): boolean {
    if( this._sudoku.charAt( index-1 ) === '0' ) {
      let columnIndex = index % 9;
      let slicer = Utility.slicer( this._sudoku );
      this._column = slicer.sliceColumn( columnIndex );
      if( this._column.indexOf('0') === this._column.lastIndexOf('0') )
        return true;
    }
    return false;
  }
  solve(index: number): number {
    let columnIndex = index % 9;
    let slicer = Utility.slicer( this._sudoku );
    this._column = slicer.sliceColumn( columnIndex );
    for( let i=1; i<10; i++) {
      if( this._column.includes( i.toString() ) === false )
        return i;
    }
  }

}
<file_sep>export class SudokuCellPosition {
  private _index;
  private _row = 0;
  private _column = 0;
  private _square = 0;

  constructor( index: number ) {
    this._index = index;
    if( this.isOutOfBounds() === false ) {
      this.calculateRow();
      this.calculateColumn();
      // NOTE: run calculateSquare AFTER _row and _column are set
      this.calculateSquare();
    }
  }
  get row(): number {
    return this._row;
  }
  get column(): number {
    return this._column;
  }
  get square(): number {
    return this._square;
  }
  private calculateRow(): void {
    this._row = Math.ceil( this._index / 9 );
  }
  private calculateColumn(): void {
    if( this._index % 9 !== 0 )
      this._column = this._index % 9;
    else // if( this._index % 9 === 0 )
      this._column = 9;
  }
  private calculateSquare(): void {
    this._square = Math.ceil(this.column / 3) + Math.floor( this._row / 3 ) * 3 ;
  }
  private isOutOfBounds(): boolean {
    return this._index < 1 || this._index > 81;
  }

}
<file_sep>
export class SudokuMutator {
  static mutate( sudoku: string, index: number, value: number ): string {
    let beforeIndex = sudoku.substring( 0, index );
    let afterIndex = sudoku.substring( index + 1 );;
    return beforeIndex + value + afterIndex;
  }
}
<file_sep>import { SudokuTreeNode } from './SudokuTree';

describe( 'SudokuTree', () => {
  it( 'getSudoku() should return its initial sudoku', () => {
    let sudoku = '123456789789123456456789123312845967697312845845697312231574698968231574574968231';
    let treeNode = new SudokuTreeNode( sudoku );
    expect( treeNode.sudoku ).toBe( sudoku );
  });
  it( 'getSudoku() should return its initial sudoku (2)', () => {
    let sudoku = '835729416497561238261843759149387562573692841962415397358976124914258673726134985';
    let treeNode = new SudokuTreeNode( sudoku );
    expect( treeNode.sudoku ).toBe( sudoku );
  });
  it( 'getSudoku() should have one child with the value of 1 added', () => {
    let sudoku = '123456789789123456456789123312845967697312845845697312231574698968231574574968230';
    let solution = '123456789789123456456789123312845967697312845845697312231574698968231574574968231';
    let treeNode = new SudokuTreeNode( sudoku );
    let childNodes = treeNode.children;

    expect( childNodes.length ).toBe( 1 );
    expect( childNodes[0].children.length ).toBe( 0 );
    expect( childNodes[0].sudoku ).toBe( solution );
  });
  it( 'getSudoku() should have one child with the value of 1 added at a different position', () => {
    let sudoku = '023456789789123456456789123312845967697312845845697312231574698968231574574968231';
    let solution = '123456789789123456456789123312845967697312845845697312231574698968231574574968231';
    let treeNode = new SudokuTreeNode( sudoku );
    let childNodes = treeNode.children;

    expect( childNodes.length ).toBe( 1 );
    expect( childNodes[0].children.length ).toBe( 0 );
    expect( childNodes[0].sudoku ).toBe( solution );
  });
  it( 'getSudoku() should have one child with a different value', () => {
    let sudoku = '103456789789123456456789123312845967697312845845697312231574698968231574574968231';
    let solution = '123456789789123456456789123312845967697312845845697312231574698968231574574968231';
    let treeNode = new SudokuTreeNode( sudoku );
    let childNodes = treeNode.children;

    expect( childNodes.length ).toBe( 1 );
    expect( childNodes[0].children.length ).toBe( 0 );
    expect( childNodes[0].sudoku ).toBe( solution );
  });
})
<file_sep>import { SquareCellSolver } from "./CellSolver/SquareSolver/SquareSolver";
import { RowCellSolver } from "./CellSolver/RowSolver/RowCellSolver";
import { ColumnCellSolver } from "./CellSolver/ColumnSolver/ColumnCellSolver";

export class SudokuSolver { // facade
  private _sudoku;

  constructor( sudoku: string ) {
    this._sudoku = sudoku;
  }
  columnSquareSolver() {
    return new ColumnCellSolver(this._sudoku );
  }
  rowCellSolver() {
    return new RowCellSolver( this._sudoku );
  }
  squareCellSolver() {
    return new SquareCellSolver( this._sudoku );
  }
}
<file_sep>import { RowCellSolver } from './RowCellSolver';

describe( 'RowCellSolver', () => {

  it( 'does not solve an empty sudoku', () => {
    let sudoku = '000000000000000000000000000000000000000000000000000000000000000000000000000000000';
    let solver = new RowCellSolver( sudoku );
    expect( solver.solve( 1 ) ).toBe( 0 );
  });

  it( 'throws error when a value is already solved', () => {
    let sudoku = '123456780000000000000000000000000000000000000000000000000000000000000000000000000';
    let solver = new RowCellSolver( sudoku );
    expect( () => { solver.solve( 1 ) } ).toThrow( new Error('The cell you are trying to solve is already solved.') );
  });

  it( 'solves sudokuRow no 1', () => {
    let sudoku = '023456789000000000000000000000000000000000000000000000000000000000000000000000000';
    let solver = new RowCellSolver( sudoku );
    expect( solver.solve( 1 ) ).toBe( 1 );
  });

  it( 'solve sudokuRow no 1 with a value other than 1', () => {
    let sudoku = '013456789000000000000000000000000000000000000000000000000000000000000000000000000';
    let solver = new RowCellSolver( sudoku );
    expect( solver.solve( 1 ) ).toBe( 2 );
  });

  it( 'solve sudokuRow other than no 1', () => {
    let sudoku = '000000000013456789000000000000000000000000000000000000000000000000000000000000000';
    let solver = new RowCellSolver( sudoku );
    expect( solver.solve( 10 ) ).toBe( 2 );
  });

  it( 'solve sudokuRow no 1 using a value from a different column', () => {
    let sudoku = '913456780000000000000000000000000000000000000000000000000000000000000000000000000';
    let solver = new RowCellSolver( sudoku );
    expect( solver.solve( 9 ) ).toBe( 2 );
  });


});
<file_sep>import { CellSolver } from "../CellSolver";
import { Utility } from "../../../Utility";

export class SquareCellSolver extends CellSolver {
  private _square: string;

  constructor( sudoku: string ) {
    super( sudoku );
  }
  public solve( cellIndex: number ): number {
    let squareIndex = Utility.cellPosition( cellIndex ).square;
    this._square = this._slicer.sliceSquare( squareIndex );
    // TODO: test out of bounds / invalid index
    // TODO: test isALreadySOlved ( see row for example )
    if( this.hasOneUnsolvedCellInSquare() === false )
      return 0;

    let i = 0;
    while( this._square.includes( i.toString() ) )
      i++;

    return i;
  }
  private hasOneUnsolvedCellInSquare(): boolean {
    return this._square.indexOf('0') === this._square.lastIndexOf('0');
  }
}
<file_sep>import { SudokuValidator } from './SudokuValidator';

describe( 'SudokuValidator', () => {
  // NOTE: SudokuValidator should only check if the structural integrity is violated. NOT if the sudoku can be solved

  it( 'should validate if an empty 9x9 sudoku', () => {
    // 81 length
    let sudoku = '000000000000000000000000000000000000000000000000000000000000000000000000000000000';
    let validator = new SudokuValidator( sudoku );
    expect( validator.validate() ).toBeTruthy();
  });
  it( 'should invalidate a sudokustring with an invalid length (e.g. 80)', () => {
    let sudoku = '00000000000000000000000000000000000000000000000000000000000000000000000000000000';
    let validator = new SudokuValidator( sudoku );
    expect( validator.validate() ).toBeFalsy();
  });
  it( 'should validate a sudokuRow if it contains no identical values', () => {
    let sudoku = '100000000000000000000000000000000000000000000000000000000000000000000000000000000';
    let validator = new SudokuValidator( sudoku );
    expect( validator.validate() ).toBeTruthy();
  });
  it( 'should invalidate a sudokuRow if it contains identical values (1)', () => {
    let sudoku = '110000000000000000000000000000000000000000000000000000000000000000000000000000000';
    let validator = new SudokuValidator( sudoku );
    expect( validator.validate() ).toBeFalsy();
  });
  it( 'should invalidate a sudokuRow if it contains identical values in the last row (2)', () => {
    let sudoku = '000000000000000000000000000000000000000000000000000000000000000000000000000000011';
    let validator = new SudokuValidator( sudoku );
    expect( validator.validate() ).toBeFalsy();
  });
  it( 'should invalidate a sudokuRow if it contains identical values other than 1', () => {
    let sudoku = '000000000000000000000000000000000000000000000000000000000000000000000000000000022';
    let validator = new SudokuValidator( sudoku );
    expect( validator.validate() ).toBeFalsy();
  });
  it( 'should invalidate a sudokuColumn if it contains identical values', () => {
    let sudoku = '100000000100000000000000000000000000000000000000000000000000000000000000000000000';
    let validator = new SudokuValidator( sudoku );
    expect( validator.validate() ).toBeFalsy();
  });
  it( 'should invalidate a sudokuColumn if it contains identical values in column 2', () => {
    let sudoku = '010000000010000000000000000000000000000000000000000000000000000000000000000000000';
    let validator = new SudokuValidator( sudoku );
    expect( validator.validate() ).toBeFalsy();
  });
  it( 'should invalidate a sudokuSquare if it contains identical values in square 1', () => {
    let sudoku = '100000000010000000000000000000000000000000000000000000000000000000000000000000000';
    let validator = new SudokuValidator( sudoku );
    expect( validator.validate() ).toBeFalsy();
  });
  it( 'should invalidate a sudokuSquare other than 1', () => {
    let sudoku = '200000000020000000000000000000000000000000000000000000000000000000000000000000000';
    let validator = new SudokuValidator( sudoku );
    expect( validator.validate() ).toBeFalsy();
  });
});
<file_sep>import { SudokuSlicer9x9 } from './SudokuSlicer';

describe( 'SudokuSlicer', () => {
  describe( '.sliceRow', () => {
    it( '(1) should return row one (1)', () => {
      let sudoku = '100000000000000000000000000000000000000000000000000000000000000000000000000000000';
      let slicer = new SudokuSlicer9x9( sudoku );
      expect( slicer.sliceRow( 1 ) ).toBe( '100000000' );
    });
    it( '(1) should return row one (2)', () => {
      let sudoku = '205000000000000000000000000000000000000000000000000000000000000000000000000000000';
      let slicer = new SudokuSlicer9x9( sudoku );
      expect( slicer.sliceRow( 1 ) ).toBe( '205000000' );
    });
    it( '(9) should return row nine', () => {
      let sudoku = '000000000000000000000000000000000000000000000000000000000000000000000000000000123';
      let slicer = new SudokuSlicer9x9( sudoku );
      expect( slicer.sliceRow( 9 ) ).toBe( '000000123' );
    });
  });
  describe( '.sliceColumn', () => {
    it( 'should return row one #1', () => {
      let sudoku = '100000000000000000000000000000000000000000000000000000000000000000000000000000000';
      let slicer = new SudokuSlicer9x9( sudoku );
      expect( slicer.sliceColumn( 1 ) ).toBe( '100000000' );
    });
    it( 'should return row one #2', () => {
      let sudoku = '010000000000000000000000000000000000000000000000000000000000000000000000000000000';
      let slicer = new SudokuSlicer9x9( sudoku );
      expect( slicer.sliceColumn( 1 ) ).toBe( '000000000' );
    });
    it( 'should return row nine', () => {
      let sudoku = '000000000000000000000000000000000000000000000000000000000000000000000000000000001';
      let slicer = new SudokuSlicer9x9( sudoku );
      expect( slicer.sliceColumn( 9 ) ).toBe( '000000001' );
    });
  });

  describe( '.sliceColumn', () => {
    it( 'should return square one #1', () => {
      let sudoku = '100000000000000000000000000000000000000000000000000000000000000000000000000000000';
      let slicer = new SudokuSlicer9x9( sudoku );
      expect( slicer.sliceSquare( 1 ) ).toBe( '100000000' );
    });
    it( 'should return square one #2', () => {
      let sudoku = '100000000010000000000000000000000000000000000000000000000000000000000000000000000';
      let slicer = new SudokuSlicer9x9( sudoku );
      expect( slicer.sliceSquare( 1 ) ).toBe( '100010000' );
    });
    it( 'should return square nine', () => {
      let sudoku = '000000000000000000000000000000000000000000000000000000000000000000000001000000101';
      let slicer = new SudokuSlicer9x9( sudoku );
      expect( slicer.sliceSquare( 9 ) ).toBe( '000001101' );
    });
  });
});
<file_sep>import { Utility } from '../../Utility';

export abstract class CellSolver {
  protected _sudoku: string;
  protected _slicer;
  protected _solution: number;

  constructor( sudoku: string ) {
    this._sudoku = sudoku;
    this._slicer = Utility.slicer( sudoku );
  }
  abstract solve( index: number ): number;
  
  protected isAlreadySolved( cellIndex ): boolean {
    return this._sudoku.charAt( cellIndex - 1 ) !== '0';
  }
}
<file_sep>import { Utility } from '../Utility';

export class SudokuTreeNode {
  // this is a composite generating a tree of all possible paths to a solution for a sudoku
  private _sudoku;
  // _parent = null;
  private _children: Array<SudokuTreeNode> = [];

  constructor( sudoku: string ){
    this._sudoku = sudoku;
    if( this._sudoku.includes('0') ){ // check if isSolved
      let index = this._sudoku.indexOf('0');
      for( let value=1; value<10 ; value++ ) {
        let childSudoku = Utility.mutate( this._sudoku, index, value );
        if( Utility.validate( childSudoku ) ) {
          this.addChild( childSudoku );
        }
      }
    } else {
      // console.log( this._sudoku );
    }
  }

  get sudoku(): string {
    return this._sudoku;
  }
  get children() {
    return this._children;
  }
  public addChild( sudoku ) {
    this._children.push( new SudokuTreeNode( sudoku ) );
  }
  // removeChild
}

// cannot stop iteration with a huge amount of solutions ( e.g. an empty sudoku )
<file_sep>import { ColumnCellSolver } from './ColumnCellSolver';

describe( 'ColumnCellSolver', () => {
  it( 'does not solve an empty sudoku', () => {
    let sudoku = '000000000000000000000000000000000000000000000000000000000000000000000000000000000';
    let solver = new ColumnCellSolver( sudoku );
    expect( solver.canSolve( 1 ) ).toBeFalsy();
  });
  it( 'does not solve a sudoku when a value is already solved', () => {
    let sudoku = '100000000000000000000000000000000000000000000000000000000000000000000000000000000';
    let solver = new ColumnCellSolver( sudoku );
    expect( solver.canSolve( 1 ) ).toBeFalsy();
  });
  it( 'solve returns 1 if sudoku is solved', () => {
    let sudoku = '000000000200000000300000000400000000500000000600000000700000000800000000900000000';
    let solver = new ColumnCellSolver( sudoku );
    expect( solver.canSolve( 1 ) ).toBeTruthy();
    expect( solver.solve( 1 ) ).toBe( 1 );
  });
  it( 'solve returns 2 if sudoku is solved', () => {
    let sudoku = '000000000100000000300000000400000000500000000600000000700000000800000000900000000';
    let solver = new ColumnCellSolver( sudoku );
    expect( solver.canSolve( 1 ) ).toBeTruthy();
    expect( solver.solve( 1 ) ).toBe( 2 );
  });
  it( 'solve returns can also solve different columns', () => {
    let sudoku = '200000000100000000000000000400000000500000000600000000700000000800000000900000000';
    let solver = new ColumnCellSolver( sudoku );
    expect( solver.canSolve( 19 ) ).toBeTruthy();
    expect( solver.solve( 19 ) ).toBe( 3 );
  });

});
<file_sep>import { RecursiveSudokuSolver } from './SudokuSolver';

describe( 'SudokuSolver', () => {
  it( 'should return an empty array with an invalid sudoku', () => {
    let sudoku = '110000000000000000000000000000000000000000000000000000000000000000000000000000000';
    let solver = new RecursiveSudokuSolver( sudoku );
    expect( solver.solve() ).toEqual( [] );
  });
  // it( 'should return one solution for a solved sudoku', () => {
  //
  // });
});
<file_sep>import { Utility } from '../../Utility';

export abstract class SudokuSolver {
  // abstract factory
  protected _sudoku: string;
  protected _solver: SudokuSolver;

  constructor( sudoku: string ) {
    this._sudoku = sudoku;
  }
  abstract solve();
//   solve( algorithm: string = 'recursive'): Array<string> {
//     switch( algorithm ) {
//       case 'recursive':
//         this._solver = new RecursiveSudokuSolver( this._sudoku );
//     }
//
//     return  this._solver.solve();
//   }
}

export class RecursiveSudokuSolver extends SudokuSolver {
  // uses a tree structure from composite
  private _solutions = [];
  solve(): Array<string> {
    if( Utility.validate( this._sudoku ) === false ) {
      return this._solutions;
    }
  }


  // composite???
  // should I seperate creating a tree from applying solution
  // create a leaf if added value is valid
  // iterate over structure to retreive the solutions
  // each branch / leaf has a solution

}














class RecursiveSudokuComposite {
  private _sudokuState: string;
  private _parent: RecursiveSudokuComposite;
  private _children: Array<RecursiveSudokuComposite>;
  // store as {1: RecursiveSudokuComposite, 2: RecursiveSudokuComposite, 3: null, etc... } ???
  constructor( sudoku: string ) {
    this._sudokuState = sudoku;
  }
  protected add( parent: RecursiveSudokuComposite ): void {
    // if solution is valid add to parent
    // then check if there are valid children for this
  }
  protected remove( parent: RecursiveSudokuComposite ): void {}
  public get_children(){};

  // remove if no children after 1-9 are tested
  // LEAFS are solutions maybe use an Observer to log them???
  // another Observer to backlog from solution up to the initial sudokuState = just call parent() until none exist??? May be easier, but have to keep the entire structure in memory then.
}

class RecursiveSudokuLeaf {
  private _sudokuState: string;
  private _parent: RecursiveSudokuComposite;
  private _children: Array<RecursiveSudokuComposite>;
  // store as {1: RecursiveSudokuComposite, 2: RecursiveSudokuComposite, 3: null, etc... } ???
  constructor( sudoku: string ) {
    this._sudokuState = sudoku;
  }
  protected add( parent: RecursiveSudokuComposite ): void {
    // if solution is valid add to parent
    // then check if there are valid children for this
  }
  protected remove( parent: RecursiveSudokuComposite ): void {} // child removes parent or parent removes child ( garbage collection if invalid??? ) ?
  public get_children(): Array<RecursiveSudokuLeaf> {
    return [];
  };

  // remove if no children after 1-9 are tested
  // LEAFS are solutions maybe use an Observer to log them???
  // another Observer to backlog from solution up to the initial sudokuState = just call parent() until none exist??? May be easier, but have to keep the entire structure in memory then.
}
<file_sep>// top-level facade for all Sudoku
// also defines operations

class Sudoku {
  // create( ... ): string // sudokustring // parameters could define complexity? unsolved cells and possible solutions possibly impossible combination.. // maybe get sudokus from a db? // or obtain from tree and count 'splits'
  // solve( sudoku ): Array<string> // a series of sudokustrings that are valid solutions
}
<file_sep>export abstract class SudokuSlicer {
  protected _sudoku: string;

  constructor( sudoku: string ) {
    this._sudoku = sudoku;
  }

  abstract sliceRow( index: number ): string;
  abstract sliceColumn( index: number ): string;
  abstract sliceSquare( index: number ): string;
}

export class SudokuSlicer9x9 extends SudokuSlicer {
  sliceSquare( index: number ): string {
    let square = '';
    index--;
    let rowPosition = Math.floor( index / 3 )*27;
    let columnPosition = index % 3 * 3;
    let topLeftCorner = rowPosition + columnPosition;

    for( let i=0; i<9;i++ ) {
      let stringPosition = topLeftCorner + i + Math.floor(i/3)*6;
      square += this._sudoku.charAt( stringPosition );
    }

    return square;
  }
  sliceColumn( index: number ): string {
    index--;
    let column = '';
    for( let i=0; i<9; i++ ) {
      column += this._sudoku.charAt( i*9+index );
    }

    return column;
  }
  sliceRow( index: number ): string {
    let lastChar = 9*index;
    let firstChar = 9*index - 9;
    return this._sudoku.slice( firstChar, lastChar );
  }
}
<file_sep>import { Utility } from '../Utility';

export class SudokuValidator {
  protected _sudoku;
  protected _slicer;
  private _validator;

  public constructor( sudoku: string ) {
    this._sudoku = sudoku;
  }

  public validate(): boolean {
    if( this.setValidator() === false ) return false;
    if( this.validRows() === false ) return false;
    if( this.validColumns() === false ) return false;
    if( this.validSquares() === false ) return false;
    else return true;
  }
  protected validRows(): boolean {
    return this._validator.validRows();
  }
  protected validColumns(): boolean {
    return this._validator.validColumns();
  }
  protected validSquares(): boolean {
    return this._validator.validSquares();
  }
  private setValidator(): boolean {
    if( this._sudoku.length === 81 ) {
        this._validator = new SudokuValidator9x9( this._sudoku );
        return true;
    } else {
      console.error( 'this sudokurepresentation has an invalid length' );
      return false;
    }
  }
}

class SudokuValidator9x9 extends SudokuValidator {
  constructor( sudoku: string ) {
    super( sudoku );
    this._slicer = Utility.slicer( this._sudoku );
  }

  protected validRows(): boolean {
    let validCharacters = [ '1', '2', '3', '4', '5', '6', '7', '8', '9' ];

    for( let i=0; i<validCharacters.length; i++ ) {
      if( this.validCharacterInRows( validCharacters[i] ) === false ) {
        return false;
      }
    }

    return true;
  }
  protected validColumns(): boolean {
    let validCharacters = [ '1', '2', '3', '4', '5', '6', '7', '8', '9' ];

    for( let i=0; i<validCharacters.length; i++ ) {
      if( this.validCharacterInColumns( validCharacters[i] ) === false ) {
        return false;
      }
    }

    return true;
  }
  protected validSquares(): boolean {
    let validCharacters = [ '1', '2', '3', '4', '5', '6', '7', '8', '9' ];

    for( let i=0; i<validCharacters.length; i++ ) {
      if( this.validCharacterInSquares( validCharacters[i] ) === false ) {
        return false;
      }
    }

    return true;
  }

  private validCharacterInRows( character: string ): boolean {
    for( let i=1; i<10; i++ ) {
      let row = this._slicer.sliceRow( i );
      if( this.validCharacterInArea( character, row ) === false )
        return false;
    }

    return true;
  }
  private validCharacterInColumns( character: string ): boolean {
    for( let i=1; i<10; i++ ) {
      let column = this._slicer.sliceColumn( i );
      if( this.validCharacterInArea( character, column ) === false )
        return false;
    }

    return true;
  }
  private validCharacterInSquares( character: string ) {
    for( let i=1; i<10; i++ ) {
      let column = this._slicer.sliceSquare( i );
      if( this.validCharacterInArea( character, column ) === false )
        return false;
    }

    return true;
  }
  private validCharacterInArea( character: string, area: string ): boolean {
    return area.indexOf( character ) === area.lastIndexOf( character );
  }
}
<file_sep>import { SquareCellSolver } from "./SquareSolver";

describe( 'SquareSolver', () => {
  it( 'does not solve an empty sudoku', () => {
    let sudoku = '000000000000000000000000000000000000000000000000000000000000000000000000000000000';
    let solver = new SquareCellSolver( sudoku );
    expect( solver.solve( 1 ) ).toBe( 0 );
  });
  it( 'does not solve a fully solved square', () => {
    let sudoku ='123000000456000000789000000000000000000000000000000000000000000000000000000000000';
    let solver = new SquareCellSolver( sudoku );
    expect( solver.solve( 1 ) ).toBe( 0 );
  });
  it( 'solves sudokuSquare no 1', () => {
    let sudoku = '023000000456000000789000000000000000000000000000000000000000000000000000000000000';
    let solver = new SquareCellSolver( sudoku );
    expect( solver.solve( 1 ) ).toBe( 1 );
  });

  it( 'solves sudokuSquare no 1 with a value other than 1', () => {
    let sudoku = '013000000456000000789000000000000000000000000000000000000000000000000000000000000';
    let solver = new SquareCellSolver( sudoku );
    expect( solver.solve( 1 ) ).toBe( 2 );
  });

  it( 'solves sudokusquare no 2', () => {
    let sudoku = '000023000000456000000789000000000000000000000000000000000000000000000000000000000';
    let solver = new SquareCellSolver( sudoku );
    expect( solver.solve( 4 ) ).toBe( 1 );
  });
});
<file_sep>import { SudokuMutator } from './SudokuMutator';

describe( 'SudokuMutator', () => {
  it( 'be able to mutate the first value', () => {
    let sudoku = '000000000000000000000000000000000000000000000000000000000000000000000000000000000';
    let mutatedSudoku = '100000000000000000000000000000000000000000000000000000000000000000000000000000000';
    expect( SudokuMutator.mutate( sudoku, 0, 1 ) ).toBe( mutatedSudoku );
  });
  it( 'be able to mutate the second value', () => {
    let sudoku = '000000000000000000000000000000000000000000000000000000000000000000000000000000000';
    let mutatedSudoku = '010000000000000000000000000000000000000000000000000000000000000000000000000000000';
    expect( SudokuMutator.mutate( sudoku, 1, 1 ) ).toBe( mutatedSudoku );
  });
  it( 'be able to mutate values other than 1', () => {
    let sudoku = '000000000000000000000000000000000000000000000000000000000000000000000000000000000';
    let mutatedSudoku = '020000000000000000000000000000000000000000000000000000000000000000000000000000000';
    expect( SudokuMutator.mutate( sudoku, 1, 2 ) ).toBe( mutatedSudoku );
  });
});

// what if a mutator tries to mutate a taken value???
  // return false would be odd
  // should that occur ever?
    // Maybe for undo operations?

// what if a mutation results to an invalid sudoku
 // external or internal isValid check? External.. can call validator?
  // do I perform the check always? Yes.
  // do I need alternate external behavior after checking isValid? Yes if not valid code may take another course... So isValid should be called externally
  // may even be useful solving sudokus? only run isValid() when asked for performance reasons?
 // also invalid trying to overwrite occupied index if unable isValid()


// idea undo functionality undo to the last valid state ( changes from view )
// possibly ask if sudoku isSolved??? // Sudoku isValid and no 0's in string.
// ask if a position isTaken()
<file_sep>import { SudokuSlicer, SudokuSlicer9x9 } from './SudokuSlicer/SudokuSlicer';
import{ SudokuValidator } from './SudokuValidator/SudokuValidator';
import { SudokuMutator } from './SudokuMutator/SudokuMutator';
import { SudokuTreeNode } from './SudokuTree/SudokuTree';
import { SudokuCellPosition } from './CellPosition/SudokuCellPosition';
// all utility classes should exclusively be called through this facade
export { SudokuSlicer9x9 } from './SudokuSlicer/SudokuSlicer';

export class Utility {
  public static slicer( sudoku: string ): SudokuSlicer {
    return new SudokuSlicer9x9( sudoku );
  }
  public static validate( sudoku: string ): boolean {
    // Validator only has one method to validate a sudoku so there is a minor implementation
    let _validator = new SudokuValidator( sudoku );
    return _validator.validate();
  }
  public static mutate( sudoku, index, value ): string {
    return SudokuMutator.mutate( sudoku, index, value );
  }
  public static generateTree( sudoku: string ) {
    // take extreme care not to process big trees
    return new SudokuTreeNode( sudoku );
  }
  public static cellPosition( cellIndex: number ): SudokuCellPosition {
    return new SudokuCellPosition( cellIndex );
  }
}
