534 lines
17 KiB
C++
534 lines
17 KiB
C++
#include <gtest/gtest.h>
|
|
#include "sudoku.h"
|
|
#include <chrono>
|
|
#include <nd-wfc/wfc.hpp>
|
|
#include <fstream>
|
|
#include <algorithm>
|
|
|
|
// Forward declaration for helper function
|
|
std::vector<Sudoku> loadPuzzlesFromFile(const std::string& filename);
|
|
|
|
static auto sudokuTestSolver = WFC::Builder<Sudoku, uint8_t>()
|
|
.DefineIDs<1, 2, 3, 4, 5, 6, 7, 8, 9>()
|
|
.Variable<1, 2, 3, 4, 5, 6, 7, 8, 9>([](Sudoku&, size_t index, WFC::WorldValue<uint8_t> val, auto& constrainer) {
|
|
size_t x = index % 9;
|
|
size_t y = index / 9;
|
|
|
|
// Add row constraints (same row, different columns)
|
|
for (size_t i = 0; i < 9; ++i) {
|
|
if (i != x) constrainer.Exclude(val, i + y * 9);
|
|
}
|
|
|
|
// Add column constraints (same column, different rows)
|
|
for (size_t i = 0; i < 9; ++i) {
|
|
if (i != y) constrainer.Exclude(val,x + i * 9);
|
|
}
|
|
|
|
// Add box constraints (3x3 box)
|
|
size_t box_x = (x / 3) * 3;
|
|
size_t box_y = (y / 3) * 3;
|
|
for (size_t j = 0; j < 3; ++j) {
|
|
for (size_t k = 0; k < 3; ++k) {
|
|
size_t cell_x = box_x + j;
|
|
size_t cell_y = box_y + k;
|
|
size_t cell_index = cell_x + cell_y * 9;
|
|
if (cell_index != index) {
|
|
constrainer.Exclude(val, cell_index);
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.build();
|
|
|
|
// Test fixture for Sudoku tests
|
|
class SudokuTest : public ::testing::Test {
|
|
protected:
|
|
|
|
void SetUp() override {
|
|
// Common test setup
|
|
}
|
|
|
|
void TearDown() override {
|
|
// Common test cleanup
|
|
}
|
|
|
|
// Helper function to create a solved Sudoku
|
|
Sudoku createSolvedSudoku() {
|
|
Sudoku sudoku;
|
|
std::string solved = "534678912672195348198342567859761423426853791713924856961537284287419635345286179";
|
|
sudoku.loadFromString(solved);
|
|
return sudoku;
|
|
}
|
|
|
|
// Helper function to create an easy puzzle
|
|
Sudoku createEasyPuzzle() {
|
|
Sudoku sudoku;
|
|
std::string easy = "530070000600195000098000060800060003400803001700020006060000280000419005000080079";
|
|
sudoku.loadFromString(easy);
|
|
return sudoku;
|
|
}
|
|
|
|
Sudoku SolvePuzzle(Sudoku& sudoku) {
|
|
sudokuTestSolver.Run(sudoku, true);
|
|
return sudoku;
|
|
}
|
|
};
|
|
|
|
// Basic functionality tests
|
|
TEST_F(SudokuTest, EmptySudoku) {
|
|
Sudoku sudoku;
|
|
|
|
for (int row = 0; row < 9; ++row) {
|
|
for (int col = 0; col < 9; ++col) {
|
|
EXPECT_EQ(sudoku.get(row, col), 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_F(SudokuTest, SetAndGet) {
|
|
Sudoku sudoku;
|
|
|
|
// Test setting and getting values
|
|
sudoku.set(0, 0, 5);
|
|
EXPECT_EQ(sudoku.get(0, 0), 5);
|
|
|
|
sudoku.set(8, 8, 9);
|
|
EXPECT_EQ(sudoku.get(8, 8), 9);
|
|
|
|
sudoku.set(4, 4, 7);
|
|
EXPECT_EQ(sudoku.get(4, 4), 7);
|
|
}
|
|
|
|
TEST_F(SudokuTest, LoadFromString) {
|
|
Sudoku sudoku;
|
|
std::string puzzle = "530070000600195000098000060800060003400803001700020006060000280000419005000080079";
|
|
|
|
EXPECT_TRUE(sudoku.loadFromString(puzzle));
|
|
|
|
// Verify specific known values
|
|
EXPECT_EQ(sudoku.get(0, 0), 5);
|
|
EXPECT_EQ(sudoku.get(0, 1), 3);
|
|
EXPECT_EQ(sudoku.get(0, 6), 0); // Empty cell
|
|
}
|
|
|
|
TEST_F(SudokuTest, LoadInvalidString) {
|
|
Sudoku sudoku;
|
|
|
|
// Test with string that's too short
|
|
EXPECT_FALSE(sudoku.loadFromString("123"));
|
|
|
|
// Test with invalid characters
|
|
EXPECT_FALSE(sudoku.loadFromString("53007000060019500009800006080006000340080300170002000606000028000041900500008007a"));
|
|
}
|
|
|
|
TEST_F(SudokuTest, Clear) {
|
|
Sudoku sudoku;
|
|
sudoku.set(0, 0, 5);
|
|
sudoku.set(1, 1, 3);
|
|
sudoku.set(2, 2, 7);
|
|
|
|
EXPECT_EQ(sudoku.get(0, 0), 5);
|
|
EXPECT_EQ(sudoku.get(1, 1), 3);
|
|
EXPECT_EQ(sudoku.get(2, 2), 7);
|
|
|
|
sudoku.clear();
|
|
|
|
for (int row = 0; row < 9; ++row) {
|
|
for (int col = 0; col < 9; ++col) {
|
|
EXPECT_EQ(sudoku.get(row, col), 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_F(SudokuTest, MemorySize) {
|
|
EXPECT_EQ(sizeof(Sudoku), 41);
|
|
}
|
|
|
|
TEST_F(SudokuTest, SetInvalidValue) {
|
|
Sudoku sudoku;
|
|
|
|
// Test setting value > 9
|
|
EXPECT_FALSE(sudoku.set(0, 0, 10));
|
|
EXPECT_FALSE(sudoku.set(0, 0, 15));
|
|
EXPECT_FALSE(sudoku.set(0, 0, 255));
|
|
|
|
// Valid values should work
|
|
EXPECT_TRUE(sudoku.set(0, 0, 9));
|
|
EXPECT_EQ(sudoku.get(0, 0), 9);
|
|
}
|
|
|
|
// Validation tests
|
|
TEST_F(SudokuTest, ValidMoves) {
|
|
auto sudoku = createEasyPuzzle();
|
|
|
|
// Test valid moves - place numbers where there are empty cells
|
|
EXPECT_TRUE(sudoku.set(0, 2, 1)); // Should be valid - empty cell
|
|
EXPECT_EQ(sudoku.get(0, 2), 1);
|
|
|
|
EXPECT_TRUE(sudoku.set(1, 1, 4)); // Should be valid - empty cell
|
|
EXPECT_EQ(sudoku.get(1, 1), 4);
|
|
|
|
EXPECT_TRUE(sudoku.set(2, 0, 2)); // Should be valid - empty cell, different value
|
|
EXPECT_EQ(sudoku.get(2, 0), 2);
|
|
}
|
|
|
|
TEST_F(SudokuTest, InvalidMoves) {
|
|
auto sudoku = createEasyPuzzle();
|
|
|
|
// Test moves that conflict with existing numbers
|
|
EXPECT_FALSE(sudoku.set(0, 0, 6)); // Conflicts with existing 5 in row
|
|
EXPECT_FALSE(sudoku.set(0, 1, 6)); // Conflicts with existing 3 in column (try different value)
|
|
EXPECT_FALSE(sudoku.set(2, 2, 9)); // Conflicts with existing 8 in same box
|
|
|
|
// Test setting same value as existing (should work)
|
|
EXPECT_TRUE(sudoku.set(0, 0, 5)); // Same as existing value
|
|
EXPECT_EQ(sudoku.get(0, 0), 5);
|
|
EXPECT_TRUE(sudoku.set(0, 1, 3)); // Same as existing value
|
|
EXPECT_EQ(sudoku.get(0, 1), 3);
|
|
}
|
|
|
|
TEST_F(SudokuTest, SolvedPuzzle) {
|
|
auto sudoku = createSolvedSudoku();
|
|
|
|
EXPECT_TRUE(sudoku.isValid());
|
|
EXPECT_TRUE(sudoku.isSolved());
|
|
}
|
|
|
|
TEST_F(SudokuTest, PartialPuzzle) {
|
|
auto sudoku = createEasyPuzzle();
|
|
|
|
EXPECT_TRUE(sudoku.isValid());
|
|
EXPECT_FALSE(sudoku.isSolved());
|
|
}
|
|
|
|
// Board conversion tests
|
|
TEST_F(SudokuTest, GetBoard) {
|
|
auto sudoku = createEasyPuzzle();
|
|
auto board = sudoku.getBoard();
|
|
|
|
EXPECT_EQ(board.size(), 81);
|
|
EXPECT_EQ(board[0], 5); // First cell
|
|
EXPECT_EQ(board[1], 3); // Second cell
|
|
}
|
|
|
|
TEST_F(SudokuTest, ToString) {
|
|
Sudoku sudoku;
|
|
sudoku.set(0, 0, 5);
|
|
sudoku.set(0, 1, 3);
|
|
|
|
std::string str = sudoku.toString();
|
|
EXPECT_EQ(str.length(), 81);
|
|
EXPECT_EQ(str[0], '5');
|
|
EXPECT_EQ(str[1], '3');
|
|
}
|
|
|
|
// Validator tests
|
|
TEST_F(SudokuTest, ValidatorValidSolution) {
|
|
auto sudoku = createSolvedSudoku();
|
|
auto board = sudoku.getBoard();
|
|
|
|
EXPECT_TRUE(SudokuValidator::isValidSolution(board));
|
|
EXPECT_TRUE(SudokuValidator::isValidPartial(board));
|
|
EXPECT_FALSE(SudokuValidator::hasConflicts(board));
|
|
}
|
|
|
|
TEST_F(SudokuTest, ValidatorInvalidSolution) {
|
|
auto sudoku = createSolvedSudoku();
|
|
auto board = sudoku.getBoard();
|
|
|
|
// Create a conflict
|
|
board[1] = 5; // This creates duplicate 5 in first row
|
|
|
|
EXPECT_FALSE(SudokuValidator::isValidSolution(board));
|
|
EXPECT_FALSE(SudokuValidator::isValidPartial(board));
|
|
EXPECT_TRUE(SudokuValidator::hasConflicts(board));
|
|
|
|
auto conflicts = SudokuValidator::findConflicts(board);
|
|
EXPECT_GT(conflicts.size(), 0);
|
|
}
|
|
|
|
// Performance tests
|
|
TEST_F(SudokuTest, PerformanceGetOperations) {
|
|
auto sudoku = createEasyPuzzle();
|
|
|
|
// Time 100,000 get operations
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
|
|
for (int i = 0; i < 100000; ++i) {
|
|
int row = i % 9;
|
|
int col = (i / 9) % 9;
|
|
volatile uint8_t value = sudoku.get(row, col);
|
|
(void)value; // Prevent optimization
|
|
}
|
|
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
|
|
|
std::cout << "100,000 get operations took: " << duration.count() << " microseconds" << std::endl;
|
|
std::cout << "Average per operation: " << (duration.count() / 100000.0) << " microseconds" << std::endl;
|
|
}
|
|
|
|
TEST_F(SudokuTest, PerformanceSetOperations) {
|
|
Sudoku sudoku;
|
|
|
|
// Time 100,000 set operations
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
|
|
for (int i = 0; i < 100000; ++i) {
|
|
int row = i % 9;
|
|
int col = (i / 9) % 9;
|
|
int value = (i % 9) + 1;
|
|
sudoku.set(row, col, value);
|
|
}
|
|
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
|
|
|
std::cout << "100,000 set operations took: " << duration.count() << " microseconds" << std::endl;
|
|
std::cout << "Average per operation: " << (duration.count() / 100000.0) << " microseconds" << std::endl;
|
|
}
|
|
|
|
// Edge case tests
|
|
TEST_F(SudokuTest, EdgeCases) {
|
|
Sudoku sudoku;
|
|
|
|
// Test all edge positions
|
|
EXPECT_TRUE(sudoku.set(0, 0, 1)); // Top-left
|
|
EXPECT_TRUE(sudoku.set(0, 8, 2)); // Top-right
|
|
EXPECT_TRUE(sudoku.set(8, 0, 3)); // Bottom-left
|
|
EXPECT_TRUE(sudoku.set(8, 8, 4)); // Bottom-right
|
|
|
|
EXPECT_EQ(sudoku.get(0, 0), 1);
|
|
EXPECT_EQ(sudoku.get(0, 8), 2);
|
|
EXPECT_EQ(sudoku.get(8, 0), 3);
|
|
EXPECT_EQ(sudoku.get(8, 8), 4);
|
|
}
|
|
|
|
TEST_F(SudokuTest, WFCIntegration)
|
|
{
|
|
auto sudoku = createEasyPuzzle();
|
|
sudokuTestSolver.Run(sudoku, true);
|
|
EXPECT_TRUE(sudoku.isSolved());
|
|
}
|
|
|
|
// Tests loading and solving puzzles from data files
|
|
TEST_F(SudokuTest, LoadAndSolveEasyPuzzles)
|
|
{
|
|
std::vector<Sudoku> easyPuzzles = loadPuzzlesFromFile("/home/connor/repos/nd-wfc/demos/sudoku/data/Sudoku_easy.txt");
|
|
|
|
ASSERT_GT(easyPuzzles.size(), 0) << "No easy puzzles loaded";
|
|
|
|
int solvedCount = 0;
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
|
|
for (size_t i = 0; i < easyPuzzles.size(); ++i) {
|
|
auto& sudoku = easyPuzzles[i];
|
|
EXPECT_TRUE(sudoku.isValid()) << "Puzzle " << i << " is not valid";
|
|
|
|
auto puzzleStart = std::chrono::high_resolution_clock::now();
|
|
sudokuTestSolver.Run(sudoku, true);
|
|
auto puzzleEnd = std::chrono::high_resolution_clock::now();
|
|
|
|
EXPECT_TRUE(sudoku.isSolved()) << "Puzzle " << i << " was not solved";
|
|
|
|
if (sudoku.isSolved()) {
|
|
solvedCount++;
|
|
}
|
|
|
|
auto puzzleDuration = std::chrono::duration_cast<std::chrono::milliseconds>(puzzleEnd - puzzleStart);
|
|
if (i < 5) { // Only print timing for first 5 puzzles to avoid spam
|
|
std::cout << "Easy puzzle " << i << " solved in " << puzzleDuration.count() << "ms" << std::endl;
|
|
}
|
|
}
|
|
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
auto totalDuration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
|
|
|
|
std::cout << "Easy puzzles: solved " << solvedCount << "/" << easyPuzzles.size()
|
|
<< " in " << totalDuration.count() << " seconds" << std::endl;
|
|
|
|
EXPECT_EQ(solvedCount, easyPuzzles.size()) << "Not all easy puzzles were solved";
|
|
}
|
|
|
|
TEST_F(SudokuTest, LoadAndSolveMediumPuzzles)
|
|
{
|
|
std::vector<Sudoku> mediumPuzzles = loadPuzzlesFromFile("/home/connor/repos/nd-wfc/demos/sudoku/data/Sudoku_medium.txt");
|
|
|
|
ASSERT_GT(mediumPuzzles.size(), 0) << "No medium puzzles loaded";
|
|
|
|
int solvedCount = 0;
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
|
|
for (size_t i = 0; i < mediumPuzzles.size(); ++i) {
|
|
auto& sudoku = mediumPuzzles[i];
|
|
EXPECT_TRUE(sudoku.isValid()) << "Puzzle " << i << " is not valid";
|
|
|
|
auto puzzleStart = std::chrono::high_resolution_clock::now();
|
|
sudokuTestSolver.Run(sudoku, true);
|
|
auto puzzleEnd = std::chrono::high_resolution_clock::now();
|
|
|
|
EXPECT_TRUE(sudoku.isSolved()) << "Puzzle " << i << " was not solved";
|
|
|
|
if (sudoku.isSolved()) {
|
|
solvedCount++;
|
|
}
|
|
|
|
auto puzzleDuration = std::chrono::duration_cast<std::chrono::milliseconds>(puzzleEnd - puzzleStart);
|
|
if (i < 5) { // Only print timing for first 5 puzzles to avoid spam
|
|
std::cout << "Medium puzzle " << i << " solved in " << puzzleDuration.count() << "ms" << std::endl;
|
|
}
|
|
}
|
|
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
auto totalDuration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
|
|
|
|
std::cout << "Medium puzzles: solved " << solvedCount << "/" << mediumPuzzles.size()
|
|
<< " in " << totalDuration.count() << " seconds" << std::endl;
|
|
|
|
EXPECT_EQ(solvedCount, mediumPuzzles.size()) << "Not all medium puzzles were solved";
|
|
}
|
|
|
|
TEST_F(SudokuTest, LoadAndSolveHardPuzzles)
|
|
{
|
|
std::vector<Sudoku> hardPuzzles = loadPuzzlesFromFile("/home/connor/repos/nd-wfc/demos/sudoku/data/Sudoku_hard.txt");
|
|
|
|
ASSERT_GT(hardPuzzles.size(), 0) << "No hard puzzles loaded";
|
|
|
|
int solvedCount = 0;
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
|
|
for (size_t i = 0; i < hardPuzzles.size(); ++i) {
|
|
auto& sudoku = hardPuzzles[i];
|
|
EXPECT_TRUE(sudoku.isValid()) << "Puzzle " << i << " is not valid";
|
|
|
|
auto puzzleStart = std::chrono::high_resolution_clock::now();
|
|
sudokuTestSolver.Run(sudoku, true);
|
|
auto puzzleEnd = std::chrono::high_resolution_clock::now();
|
|
|
|
EXPECT_TRUE(sudoku.isSolved()) << "Puzzle " << i << " was not solved";
|
|
|
|
if (sudoku.isSolved()) {
|
|
solvedCount++;
|
|
}
|
|
|
|
auto puzzleDuration = std::chrono::duration_cast<std::chrono::milliseconds>(puzzleEnd - puzzleStart);
|
|
if (i < 5) { // Only print timing for first 5 puzzles to avoid spam
|
|
std::cout << "Hard puzzle " << i << " solved in " << puzzleDuration.count() << "ms" << std::endl;
|
|
}
|
|
}
|
|
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
auto totalDuration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
|
|
|
|
std::cout << "Hard puzzles: solved " << solvedCount << "/" << hardPuzzles.size()
|
|
<< " in " << totalDuration.count() << " seconds" << std::endl;
|
|
|
|
EXPECT_EQ(solvedCount, hardPuzzles.size()) << "Not all hard puzzles were solved";
|
|
}
|
|
|
|
TEST_F(SudokuTest, LoadAndSolveDiabolicalPuzzles)
|
|
{
|
|
std::vector<Sudoku> diabolicalPuzzles = loadPuzzlesFromFile("/home/connor/repos/nd-wfc/demos/sudoku/data/Sudoku_diabolical.txt");
|
|
|
|
ASSERT_GT(diabolicalPuzzles.size(), 0) << "No diabolical puzzles loaded";
|
|
|
|
int solvedCount = 0;
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
|
|
for (size_t i = 0; i < diabolicalPuzzles.size(); ++i) {
|
|
auto& sudoku = diabolicalPuzzles[i];
|
|
EXPECT_TRUE(sudoku.isValid()) << "Puzzle " << i << " is not valid";
|
|
|
|
auto puzzleStart = std::chrono::high_resolution_clock::now();
|
|
sudokuTestSolver.Run(sudoku, true);
|
|
auto puzzleEnd = std::chrono::high_resolution_clock::now();
|
|
|
|
EXPECT_TRUE(sudoku.isSolved()) << "Puzzle " << i << " was not solved";
|
|
|
|
if (sudoku.isSolved()) {
|
|
solvedCount++;
|
|
}
|
|
|
|
auto puzzleDuration = std::chrono::duration_cast<std::chrono::milliseconds>(puzzleEnd - puzzleStart);
|
|
if (i < 5) { // Only print timing for first 5 puzzles to avoid spam
|
|
std::cout << "Diabolical puzzle " << i << " solved in " << puzzleDuration.count() << "ms" << std::endl;
|
|
}
|
|
}
|
|
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
auto totalDuration = std::chrono::duration_cast<std::chrono::seconds>(end - start);
|
|
|
|
std::cout << "Diabolical puzzles: solved " << solvedCount << "/" << diabolicalPuzzles.size()
|
|
<< " in " << totalDuration.count() << " seconds" << std::endl;
|
|
|
|
EXPECT_EQ(solvedCount, diabolicalPuzzles.size()) << "Not all diabolical puzzles were solved";
|
|
}
|
|
|
|
// Test loading a single puzzle from each difficulty file
|
|
TEST_F(SudokuTest, LoadAndSolveFirstPuzzleFromEachFile)
|
|
{
|
|
const std::string dataPath = "/home/connor/repos/nd-wfc/demos/sudoku/data";
|
|
const std::vector<std::string> files = {"Sudoku_easy.txt", "Sudoku_medium.txt", "Sudoku_hard.txt", "Sudoku_diabolical.txt"};
|
|
|
|
for (const auto& filename : files) {
|
|
std::string filepath = dataPath + "/" + filename;
|
|
std::ifstream file(filepath);
|
|
|
|
ASSERT_TRUE(file.is_open()) << "Failed to open file " << filename;
|
|
|
|
std::string firstLine;
|
|
std::getline(file, firstLine);
|
|
|
|
// Remove whitespace
|
|
firstLine.erase(std::remove_if(firstLine.begin(), firstLine.end(),
|
|
[](char c) { return std::isspace(c); }), firstLine.end());
|
|
|
|
ASSERT_FALSE(firstLine.empty()) << "No puzzle data found in " << filename;
|
|
|
|
Sudoku puzzle;
|
|
ASSERT_TRUE(puzzle.loadFromString(firstLine)) << "Failed to load puzzle from first line of " << filename;
|
|
|
|
EXPECT_TRUE(puzzle.isValid()) << "Loaded puzzle from " << filename << " is not valid";
|
|
|
|
auto start = std::chrono::high_resolution_clock::now();
|
|
sudokuTestSolver.Run(puzzle, true);
|
|
auto end = std::chrono::high_resolution_clock::now();
|
|
|
|
EXPECT_TRUE(puzzle.isSolved()) << "Failed to solve first puzzle from " << filename;
|
|
|
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
|
std::cout << "First puzzle from " << filename << " solved in " << duration.count() << "ms" << std::endl;
|
|
}
|
|
}
|
|
|
|
// Helper function to load multiple puzzles from a file (one puzzle per line)
|
|
std::vector<Sudoku> loadPuzzlesFromFile(const std::string& filename) {
|
|
std::vector<Sudoku> puzzles;
|
|
std::ifstream file(filename);
|
|
|
|
if (!file.is_open()) {
|
|
return puzzles;
|
|
}
|
|
|
|
std::string line;
|
|
while (std::getline(file, line)) {
|
|
// Remove whitespace
|
|
line.erase(std::remove_if(line.begin(), line.end(),
|
|
[](char c) { return std::isspace(c); }), line.end());
|
|
|
|
if (line.empty()) continue;
|
|
|
|
Sudoku sudoku;
|
|
if (sudoku.loadFromString(line)) {
|
|
puzzles.push_back(std::move(sudoku));
|
|
}
|
|
}
|
|
|
|
return puzzles;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|