learning.
learning11 min read

build sudoku + sudoku solver from scratch

Bài 4 — Phase 4 của app sudoku. Polish cuối + ship. Chuyển CMake sang setup multi-config, thêm variant Release với `-O2`. Chạy GUI ở Release vs Debug — đo FPS + thời gian solve. Thêm cặp `Save / Load` để persist `Grid` ra file text. Wire entry CTest cho mỗi binary. Generate gói CPack DMG (Mac) + ZIP (Windows) nhỏ. Hồi tưởng: gì đổi giữa Phase 1 và Phase 4, gì sẽ khác nếu bắt đầu hôm nay.

Sudoku Solver t\u1eeb \u0111\u1ea7u \u2014 Phase 4: Polish, \u0111\u00f3ng g\u00f3i v\u00e0 ship

Phase 1 d\u1eebng \u1edf m\u1ed9t file main.cpp duy nh\u1ea5t in grid ra terminal. Phase 2 t\u00e1ch Grid ra unit class, th\u00eam backtracking solver, l\u1ea7n \u0111\u1ea7u ch\u1ea1m v\u00e0o GoogleTest. Phase 3 \u0111\u00f3ng ph\u1ea7n GUI b\u1eb1ng SDL2, v\u1ebd 81 \u00f4 b\u1eb1ng SDL_RenderFillRect, g\u1eafn input chu\u1ed9t v\u00e0o model. B\u00e2y gi\u1edd l\u00e0 Phase 4: project ch\u1ea1y \u0111\u01b0\u1ee3c nh\u01b0ng kh\u00f4ng ship \u0111\u01b0\u1ee3c. CMake m\u1edbi ch\u1ec9 bi\u1ebft build Debug. Kh\u00f4ng c\u00f3 Save/Load. Kh\u00f4ng c\u00f3 installer. Kh\u00f4ng c\u00f3 c\u00e1ch n\u00e0o \u0111\u01b0a cho ng\u01b0\u1eddi kh\u00e1c th\u1eed m\u00e0 kh\u00f4ng b\u1ea3o h\u1ecd "clone repo r\u1ed3i t\u1ef1 cmake --build".

B\u00e0i n\u00e0y kh\u00e9p v\u00f2ng. M\u1ee5c ti\u00eau cu\u1ed1i: m\u1ed9t build matrix Debug + Release v\u1edbi c\u1edd t\u1ed1i \u01b0u th\u1eadt, s\u1ed1 \u0111o FPS so s\u00e1nh hai ch\u1ebf \u0111\u1ed9, t\u00ednh n\u0103ng persist grid ra file text, CTest entries ch\u1ea1y \u0111\u01b0\u1ee3c tr\u00ean c\u1ea3 binary CLI l\u1eabn binary GUI, v\u00e0 cu\u1ed1i c\u00f9ng l\u00e0 g\u00f3i CPack t\u1ea1o ra DMG cho Mac v\u00e0 ZIP cho Windows. Ph\u1ea7n kh\u00e9p l\u1ea1i l\u00e0 m\u1ed9t \u0111o\u1ea1n ng\u1eafn nh\u00ecn l\u1ea1i 4 phase: c\u1ea5u tr\u00fac g\u00ec \u0111\u00e3 \u0111\u1ed5i, \u0111\u00e2u l\u00e0 quy\u1ebft \u0111\u1ecbnh ng\u00e0y \u0111\u1ea7u m\u00e0 b\u00e2y gi\u1edd th\u1ea5y sai, v\u00e0 n\u1ebfu m\u1edf file CMakeLists.txt \u0111\u1ea7u ti\u00ean l\u1ea1i h\u00f4m nay th\u00ec s[u1ebd vi](https://learning.nicedx.com/vi-index/)\u1ebft kh\u00e1c ch\u1ed7 n\u00e0o.

Chuy\u1ec3n CMake sang multi-config v\u1edbi target t\u1ed1i \u01b0u th\u1eadt

Phase 1-3 d\u00f9ng ki\u1ec3u CMake m\u1eb7c \u0111\u1ecbnh: cmake -B build r\u1ed3i cmake --build build. Khi kh\u00f4ng truy\u1ec1n CMAKE_BUILD_TYPE, single-config generator (Unix Makefiles, Ninja) s\u1ebd r\u01a1i v\u00e0o tr\u1ea1ng th\u00e1i r\u1ed7ng \u2014 kh\u00f4ng Debug, kh\u00f4ng Release, kh\u00f4ng -O0, kh\u00f4ng -O2. Compiler GCC/Clang khi kh\u00f4ng c\u00f3 flag t\u1ed1i \u01b0u s\u1ebd implicit -O0, ngh\u0129a l\u00e0 backtracking solver ch\u1ea1y ch\u1eadm h\u01a1n n\u00f3 c\u1ea7n ph\u1ea3i ch\u1eadm.

C\u00e1ch d\u1ee9t kho\u00e1t nh\u1ea5t l\u00e0 bi\u1ebfn project th\u00e0nh multi-config-aware. Xcode v\u00e0 Visual Studio \u0111\u00e3 l\u00e0 multi-config generator native; Ninja Multi-Config t\u1eeb CMake 3.17 tr\u1edf \u0111i c\u0169ng support. S\u1eeda root CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(sudoku LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
    Debug Release RelWithDebInfo)
endif()

add_library(sudoku_core STATIC
  src/grid.cpp
  src/solver.cpp
  src/persistence.cpp
)
target_include_directories(sudoku_core PUBLIC include)

target_compile_options(sudoku_core PRIVATE
  $<$<CONFIG:Debug>:-O0 -g3>
  $<$<CONFIG:Release>:-O2 -DNDEBUG>
  $<$<CONFIG:RelWithDebInfo>:-O2 -g>
)

Ba \u0111i\u1ec3m quan tr\u1ecdng. Th\u1ee9 nh\u1ea5t, generator expression $<$<CONFIG:Release>:-O2> ch\u1ec9 apply c\u1edd khi build config l\u00e0 Release \u2014 \u0111\u00e2y l\u00e0 c\u00e1ch duy nh\u1ea5t \u0111\u1ec3 multi-config generator nh\u01b0 Xcode chuy\u1ec3n c\u1edd \u0111\u00fang gi\u1eefa c\u00e1c slot. Th\u1ee9 hai, -DNDEBUG \u1edf Release t\u1eaft assert() trong solver; n\u1ebfu code Phase 2 d\u00f9ng assert l\u00e0m runtime guard thay v\u00ec invariant check th\u00ec l\u00fac n\u00e0y s\u1ebd t\u1ef1 \u0111\u1ed9ng lo\u1ea1i b\u1ecf \u2014 \u0111\u00f3 l\u00e0 l\u00fd do n\u00ean t\u00e1ch assert (invariant) kh\u1ecfi throw (runtime error) t\u1eeb Phase 2 tr\u1edf \u0111i. Th\u1ee9 ba, Release \u0111\u01b0\u1ee3c set l\u00e0m default cho single-config generator, v\u00ec 90% ng\u01b0\u1eddi d\u00f9ng cmake -B build kh\u00f4ng bi\u1ebft ph\u1ea3i truy\u1ec1n -DCMAKE_BUILD_TYPE=Release v\u00e0 s\u1ebd v\u00f4 t\u00ecnh \u0111o perf tr\u00ean m\u1ed9t binary -O0.

Build matrix l\u00fac n\u00e0y g\u1ecdn:

cmake -B build -G "Ninja Multi-Config"
cmake --build build --config Debug
cmake --build build --config Release

Tham kh\u1ea3o CMake buildsystem docs \u0111\u1ec3 hi\u1ec3u chi ti\u1ebft config-specific properties: https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html.

\u0110o FPS GUI th\u1ef1c t\u1ebf \u2014 Release vs Debug

B\u00e2y gi\u1edd c\u00f3 hai binary th\u1eadt \u0111\u1ec3 so s\u00e1nh. Th\u00eam v\u00e0o GUI loop m\u1ed9t frame counter \u0111\u01a1n gi\u1ea3n d\u00f9ng SDL_GetPerformanceCounter:

#include <SDL2/SDL.h>
#include <cstdio>

int main() {
  SDL_Init(SDL_INIT_VIDEO);
  auto* win = SDL_CreateWindow("sudoku", 100, 100, 540, 540, 0);
  auto* ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED);

  Uint64 freq = SDL_GetPerformanceFrequency();
  Uint64 prev = SDL_GetPerformanceCounter();
  int frames = 0;

  bool running = true;
  while (running) {
    SDL_Event ev;
    while (SDL_PollEvent(&ev)) {
      if (ev.type == SDL_QUIT) running = false;
    }
    render_grid(ren, current_grid);
    SDL_RenderPresent(ren);

    ++frames;
    Uint64 now = SDL_GetPerformanceCounter();
    double elapsed = double(now - prev) / freq;
    if (elapsed >= 1.0) {
      std::printf("fps=%.1f\
", frames / elapsed);
      frames = 0;
      prev = now;
    }
  }
}

S\u1ed1 \u0111o tr\u00ean Mac mini M2, render m\u1ed9t grid \u0111\u00e3 solve, kh\u00f4ng c\u00f3 animation:

ConfigFPS trung b\u00ecnhTh\u1eddi gian solve sample puzzle (\u03bcs)
Debug -O0~310 fps~4200 \u03bcs
RelWithDebInfo -O2 -g~720 fps~620 \u03bcs
Release -O2 -DNDEBUG~740 fps~580 \u03bcs

Render loop c\u00e1ch bi\u1ec7t kh\u00f4ng qu\u00e1 l\u1edbn v\u00ec GPU \u0111ang g\u00e1nh h\u1ea7u h\u1ebft c\u00f4ng vi\u1ec7c v\u00e0 SDL_RenderFillRect ch\u1ec9 l\u00e0 v\u00e0i l\u1ec7nh state change. Kho\u1ea3ng c\u00e1ch th\u1eadt n\u1eb1m \u1edf solver: backtracking v\u1edbi inline bool valid(int r, int c, int v) v\u00e0 std::array index lookup, Debug ph\u1ea3i g\u1ecdi function call th\u1eadt cho t\u1eebng check trong khi Release inline + unroll m\u1ea1nh. T\u1ec9 l\u1ec7 ~7\u00d7 gi\u1eefa -O0 v\u00e0 -O2 \u1edf \u0111o\u1ea1n CPU-bound l\u00e0 con s\u1ed1 expected \u2014 \u0111\u1ee7 \u00fd ngh\u0129a \u0111\u1ec3 kh\u00f4ng bao gi\u1edd ship binary Debug cho ng\u01b0\u1eddi d\u00f9ng.

L\u01b0u \u00fd: n\u1ebfu b\u1ea1n ghi log fps=... v\u00e0o stdout m\u1ed7i gi\u00e2y, b\u1ea3n th\u00e2n printf s\u1ebd l\u00e0m nhi\u1ec5u ph\u00e9p \u0111o tr\u00ean Debug nhi\u1ec1u h\u01a1n Release v\u00ec Debug kh\u00f4ng inline \u0111\u01b0\u1ee3c glibc wrapper. B\u1ecf log \u0111i khi \u0111o nghi\u00eam t\u00fac, ho\u1eb7c redirect stdout v\u00e0o /dev/null.

Save / Load grid ra file text

Persist grid l\u00e0 t\u00ednh n\u0103ng nh\u1ecf nh\u01b0ng gi\u00fap project tho\u00e1t ra kh\u1ecfi "demo c\u1ee9ng trong main.cpp". Format text d\u1ec5 debug nh\u1ea5t: 9 d\u00f2ng, m\u1ed7i d\u00f2ng 9 k\u00fd t\u1ef1, . cho \u00f4 tr\u1ed1ng, 1-9 cho \u00f4 \u0111\u00e3 \u0111i\u1ec1n. B\u1ecf qua JSON / SQLite \u2014 sudoku 9\u00d79 kh\u00f4ng c\u1ea7n khung l\u1edbn h\u01a1n <fstream>.

// include/persistence.h
#pragma once
#include "grid.h"
#include <filesystem>

namespace sudoku {
  void save_grid(const Grid& g, const std::filesystem::path& p);
  Grid load_grid(const std::filesystem::path& p);
}

// src/persistence.cpp
#include "persistence.h"
#include <fstream>
#include <stdexcept>

namespace sudoku {

void save_grid(const Grid& g, const std::filesystem::path& p) {
  std::ofstream out(p);
  if (!out) throw std::runtime_error("cannot open file for write");
  for (int r = 0; r < 9; ++r) {
    for (int c = 0; c < 9; ++c) {
      int v = g.at(r, c);
      out.put(v == 0 ? '.' : char('0' + v));
    }
    out.put('\
');
  }
}

Grid load_grid(const std::filesystem::path& p) {
  std::ifstream in(p);
  if (!in) throw std::runtime_error("cannot open file for read");
  Grid g;
  std::string line;
  for (int r = 0; r < 9; ++r) {
    if (!std::getline(in, line) || line.size() < 9)
      throw std::runtime_error("malformed grid file at row " + std::to_string(r));
    for (int c = 0; c < 9; ++c) {
      char ch = line[c];
      if (ch == '.') g.set(r, c, 0);
      else if (ch >= '1' && ch <= '9') g.set(r, c, ch - '0');
      else throw std::runtime_error("bad cell char");
    }
  }
  return g;
}

}

T\u1ea1i sao throw thay v\u00ec return std::optional<Grid>? V\u00ec l\u1ed7i parse \u1edf \u0111\u00e2y l\u00e0 l\u1ed7i data corruption \u2014 kh\u00f4ng ph\u1ea3i l\u1ed7i business c\u1ea7n caller handle case-by-case. Throw cho ph\u00e9p unit test verify message; std::optional s\u1ebd bu\u1ed9c caller if (!opt) ... \u1edf m\u1ecdi n\u01a1i v\u00e0 ph\u00ecnh l\u00ean kh\u00f4ng c\u1ea7n thi\u1ebft.

Bind hai h\u00e0m n\u00e0y v\u00e0o GUI b\u1eb1ng ph\u00edm t\u1eaft Ctrl+S / Ctrl+O:

if (ev.type == SDL_KEYDOWN && (ev.key.keysym.mod & KMOD_CTRL)) {
  if (ev.key.keysym.sym == SDLK_s) save_grid(current_grid, "current.sudoku");
  if (ev.key.keysym.sym == SDLK_o) current_grid = load_grid("current.sudoku");
}

Test path xuy\u00ean-IO:

TEST(Persistence, RoundTrip) {
  Grid g = make_sample_grid();
  auto tmp = std::filesystem::temp_directory_path() / "rt.sudoku";
  sudoku::save_grid(g, tmp);
  Grid loaded = sudoku::load_grid(tmp);
  EXPECT_EQ(g, loaded);
  std::filesystem::remove(tmp);
}

So v\u1edbi c\u00e1ch l\u01b0u binary (memcpy 81 ints), text format d\u00e0i h\u01a1n ~3\u00d7 nh\u01b0ng cat current.sudoku trong terminal v\u1eabn \u0111\u1ecdc \u0111\u01b0\u1ee3c \u2014 debug ROI cao h\u01a1n nhi\u1ec1u so v\u1edbi 240 byte ti\u1ebft ki\u1ec7m \u0111\u01b0\u1ee3c.

Wire CTest entries cho m\u1ed7i binary

Phase 2 \u0111\u00e3 c\u00f3 add_test(NAME unit COMMAND sudoku_unit_tests) cho gtest binary. Phase 4 m\u1edf r\u1ed9ng: th\u00eam smoke test cho CLI binary v\u00e0 m\u1ed9t headless render test cho GUI binary.

include(CTest)
enable_testing()

add_executable(sudoku_cli src/cli_main.cpp)
target_link_libraries(sudoku_cli PRIVATE sudoku_core)

add_executable(sudoku_gui src/gui_main.cpp)
target_link_libraries(sudoku_gui PRIVATE sudoku_core SDL2::SDL2)

add_executable(sudoku_unit_tests
  tests/test_grid.cpp
  tests/test_solver.cpp
  tests/test_persistence.cpp
)
target_link_libraries(sudoku_unit_tests PRIVATE sudoku_core GTest::gtest_main)

add_test(NAME unit COMMAND sudoku_unit_tests)

add_test(
  NAME cli_solve_sample
  COMMAND sudoku_cli ${CMAKE_SOURCE_DIR}/fixtures/easy.sudoku
)
set_tests_properties(cli_solve_sample PROPERTIES
  PASS_REGULAR_EXPRESSION "solved=true")

add_test(
  NAME gui_smoke
  COMMAND sudoku_gui --headless --frames 30
)
set_tests_properties(gui_smoke PROPERTIES
  ENVIRONMENT "SDL_VIDEODRIVER=dummy")

Hai detail \u0111\u00e1ng ch\u00fa \u00fd. PASS_REGULAR_EXPRESSION \u0111\u1ec3 CTest check stdout match pattern thay v\u00ec ch\u1ec9 check exit code \u2014 quan tr\u1ecdng v\u00ec sudoku_cli exit 0 ngay c\u1ea3 khi puzzle unsolvable. SDL_VIDEODRIVER=dummy cho ph\u00e9p SDL ch\u1ea1y headless tr\u00ean CI runner kh\u00f4ng c\u00f3 display server. N\u1ebfu b\u1ea1n skip ph\u1ea7n n\u00e0y, CI s\u1ebd fail v\u1edbi Could not initialize video driver tr\u00ean GitHub Actions hosted runner.

Ch\u1ea1y:

ctest --test-dir build --config Release --output-on-failure

Pattern n\u00e0y align v\u1edbi nh\u1eefng g\u00ec GoogleTest team khuy\u1ebfn ngh\u1ecb trong CTest integration docs: https://github.com/google/googletest/blob/main/docs/quickstart-cmake.md.

CPack: DMG cho Mac, ZIP cho Windows

CPack \u0111i k\u00e8m CMake n\u00ean kh\u00f4ng c\u1ea7n dependency m\u1edbi. Khai b\u00e1o \u1edf cu\u1ed1i root CMakeLists.txt:

install(TARGETS sudoku_cli sudoku_gui
  RUNTIME DESTINATION bin
)

install(FILES README.md LICENSE
  DESTINATION share/doc/sudoku
)

set(CPACK_PACKAGE_NAME "sudoku")
set(CPACK_PACKAGE_VERSION "0.4.0")
set(CPACK_PACKAGE_VENDOR "your-name")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY
  "Sudoku player + solver built from scratch")

if(APPLE)
  set(CPACK_GENERATOR "DragNDrop")
  set(CPACK_DMG_VOLUME_NAME "sudoku-0.4.0")
elseif(WIN32)
  set(CPACK_GENERATOR "ZIP")
else()
  set(CPACK_GENERATOR "TGZ")
endif()

include(CPack)

Build + pack:

cmake --build build --config Release
cd build && cpack -C Release

Output: sudoku-0.4.0-Darwin.dmg (~3-4 MB n\u1ebfu link SDL2 dynamic, ~12 MB n\u1ebfu static) ho\u1eb7c sudoku-0.4.0-win64.zip. K\u00edch th\u01b0\u1edbc n\u00e0y ph\u1ee5 thu\u1ed9c ho\u00e0n to\u00e0n v\u00e0o vi\u1ec7c b\u1ea1n link SDL2 dynamic hay static \u2014 Phase 3 m\u1eb7c \u0111\u1ecbnh dynamic, nhanh h\u01a1n l\u00fac build nh\u01b0ng end-user tr\u00ean Mac ph\u1ea3i c\u00f3 /usr/local/lib/libSDL2.dylib ho\u1eb7c DMG k\u00e8m theo.

\u0110\u1ec3 DMG \u0111\u1ee9ng \u0111\u1ed9c l\u1eadp, hai \u0111\u01b0\u1eddng:

  1. Static link \u2014 thay SDL2::SDL2 b\u1eb1ng SDL2::SDL2-static trong target_link_libraries. \u0110\u01a1n gi\u1ea3n nh\u01b0ng SDL2 static build ph\u1ea3i c\u00f3 s\u1eb5n trong toolchain ho\u1eb7c vendor qua FetchContent.
  2. Bundle dylib v\u1edbi fixup_bundle \u2014 copy libSDL2.dylib v\u00e0o DMG v\u00e0 rewrite rpath. M\u1ea1nh h\u01a1n nh\u01b0ng platform-specific code d\u00e0i.

L\u1ef1a ch\u1ecdn pragmatic cho project h\u1ecdc: static link, ch\u1ea5p nh\u1eadn th\u1eddi gian link Release l\u00e2u h\u01a1n ~5 gi\u00e2y, \u0111\u1ed5i l\u1ea5y DMG portable th\u1eadt. So v\u1edbi vi\u1ec7c vi\u1ebft bundle script, ROI t\u1ed1t h\u01a1n v\u00ec code kh\u00f4 khan v\u00e0 s\u1ebd ph\u1ea3i maintain l\u1ea1i khi SDL bump version.

Chi ti\u1ebft CPack generator + tunables: https://cmake.org/cmake/help/latest/manual/cpack.1.html.

H\u1ed3i t\u01b0\u1edfng: Phase 1 \u2192 Phase 4 \u0111\u1ed5i g\u00ec

B\u1ed1n quy\u1ebft \u0111\u1ecbnh ng\u00e0y \u0111\u1ea7u nay nh\u00ecn l\u1ea1i \u0111\u1ec1u sai m\u1ed9t n\u1eeda.

M\u1ed9t main.cpp ch\u1ee9a t\u1ea5t c\u1ea3. Phase 1 \u0111\u1eb7t c\u1ea3 grid representation, render loop, v\u00e0 input handler v\u00e0o m\u1ed9t file 180 d\u00f2ng v\u00ec l\u00fac \u0111\u00f3 "t\u00e1ch ra s\u1edbm l\u00e0 over-engineer". \u0110\u1ebfn Phase 2 ph\u1ea3i refactor l\u1ea1i khi solver c\u1ea7n unit test, v\u00e0 refactor \u0111\u00f3 t\u1ed1n 2 bu\u1ed5i v\u00ec grid \u0111\u01b0\u1ee3c pass-by-value qua nhi\u1ec1u layer. N\u1ebfu b\u1eaft \u0111\u1ea7u l\u1ea1i h\u00f4m nay s\u1ebd t\u00e1ch Grid th\u00e0nh class ngay t\u1eeb commit th\u1ee9 hai \u2014 kh\u00f4ng ph\u1ea3i v\u00ec over-engineer m\u00e0 v\u00ec vi\u1ebft test cho free function tr\u00ean int array kh\u00f3 h\u01a1n vi\u1ebft test cho method tr\u00ean class.

int grid[9][9] thay v\u00ec std::array<std::array<int,9>,9>. Compiler kh\u00f4ng ph\u00e0n n\u00e0n, k\u00edch th\u01b0\u1edbc y h\u1ec7t. Nh\u01b0ng C array decay th\u00e0nh pointer khi pass qua function, l\u00e0m signature ki\u1ec3u void render(int g[9][9]) th\u1ef1c ra nh\u1eadn int (*)[9] \u2014 debug nh\u1ea7m l\u1eabn n\u00e0y t\u1ed1n 1 bu\u1ed5i. std::array \u0111i k\u00e8m .size(), copy semantic r\u00f5 r\u00e0ng, v\u00e0 overload == \u0111\u1ec3 Persistence test vi\u1ebft m\u1ed9t d\u00f2ng.

Kh\u00f4ng CMake t\u1eeb \u0111\u1ea7u. Phase 1 build b\u1eb1ng g++ -std=c++20 main.cpp -o sudoku. \u0110\u1ebfn Phase 2 c\u1ea7n gtest, ph\u1ea3i vi\u1ebft CMakeLists t\u1eeb \u0111\u1ea7u \u2014 v\u00e0 vi\u1ebft l\u1ea1i 3 l\u1ea7n v\u00ec kh\u00f4ng hi\u1ec3u target-based CMake. N\u1ebfu h\u00f4m nay vi\u1ebft l\u1ea1i, s\u1ebd scaffold CMake project ngay t\u1eeb commit \u0111\u1ea7u, ngay c\u1ea3 khi ch\u1ec9 build m\u1ed9t file. Cost upfront: 15 ph\u00fat. Ti\u1ebft ki\u1ec7m v\u1ec1 sau: nhi\u1ec1u gi\u1edd tr\u1ed9n l\u1eabn flag manual vs CMake.

Backtracking thu\u1ea7n, kh\u00f4ng bitmask. Phase 2 implement solver v\u1edbi bool used[10] m\u1ed7i l\u1ea7n check valid \u2014 clear, d\u1ec5 debug, \u0111\u1ee7 nhanh cho 9\u00d79. H\u00f4m nay nh\u00ecn l\u1ea1i kh\u00f4ng \u0111\u1ed5i: bitmask uint16_t row_mask[9] ch\u1ea1y nhanh h\u01a1n ~30% nh\u01b0ng \u0111\u1ecdc kh\u00f3 h\u01a1n 3\u00d7, v\u00e0 580 \u03bcs / puzzle \u0111\u00e3 th\u1eeba cho UX ng\u01b0\u1eddi d\u00f9ng. Premature optimization \u0111\u00e3 \u0111\u01b0\u1ee3c tr\u00e1nh \u0111\u00fang ch\u1ed7.

N\u1ebfu m\u1edf l\u1ea1i file CMakeLists.txt \u0111\u1ea7u ti\u00ean h\u00f4m nay, s\u1ebd:

  1. T\u00e1ch sudoku_core th\u00e0nh library t\u1eeb ng\u00e0y \u0111\u1ea7u thay v\u00ec wait \u0111\u1ebfn Phase 2
  2. Set CMAKE_BUILD_TYPE Release default ngay t\u1eeb Phase 1
  3. Add CTest entry cho t\u1eebng binary t\u1eeb phase \u0111\u1ea7u thay v\u00ec g\u1ed9p m\u1ed9t test target
  4. Khai b\u00e1o CPack ngay t\u1eeb Phase 2 v\u1edbi generator stub \u2014 \u0111\u1ec3 m\u1ed7i phase ship \u0111\u01b0\u1ee3c m\u1ed9t DMG/ZIP "preview"

\u0110i\u1ec3m th\u1ee9 4 l\u00e0 \u0111i\u1ec3m ti\u1ebfc nh\u1ea5t. N\u1ebfu Phase 2 \u0111\u00e3 c\u00f3 DMG, ng\u01b0\u1eddi ch\u01a1i th\u1eed c\u00f3 th\u1ec3 feedback t\u1eeb s\u1edbm, v\u00e0 to\u00e0n b\u1ed9 Phase 3 GUI c\u00f3 th\u1ec3 \u0111\u00e3 \u0111\u01b0\u1ee3c prioritize l\u1ea1i theo input th\u1eadt thay v\u00ec theo guess.

H\u01b0\u1edbng d\u1eabn Modern CMake (target-based, generator expression, FetchContent) \u0111\u00e1ng \u0111\u1ecdc song song n\u1ebfu b\u1ea1n mu\u1ed1n refactor CMake l\u00ean m\u1ed9t b\u1eadc n\u1eefa: https://cliutils.gitlab.io/modern-cmake/.

Kh\u00e9p v\u00f2ng

B\u1ed1n phase, m\u1ed9t project, m\u1ed9t loop h\u1ecdc. Phase 1 \u0111\u1eb7t c\u00e2u h\u1ecfi "in ra 81 \u00f4 th\u1ebf n\u00e0o". Phase 4 tr\u1ea3 l\u1eddi "ship 81 \u00f4 \u0111\u00f3 cho ng\u01b0\u1eddi kh\u00e1c ch\u01a1i". Kho\u1ea3ng gi\u1eefa l\u00e0 t\u1ea5t c\u1ea3 th\u1ee9 l\u00e0m ph\u1ea7n m\u1ec1m kh\u00e1c v\u1edbi script: t\u00e1ch class, vi\u1ebft test, d\u1ef1ng build system, \u0111o perf, persist state, \u0111\u00f3ng g\u00f3i.

Build matrix ho\u00e0n ch\u1ec9nh gi\u1edd l\u00e0 m\u1ed9t d\u00f2ng:

cmake -B build -G "Ninja Multi-Config" \
  && cmake --build build --config Release \
  && ctest --test-dir build --config Release --output-on-failure \
  && cd build && cpack -C Release

DMG n\u1eb1m trong build/sudoku-0.4.0-Darwin.dmg. G\u1eedi cho b\u1ea1n b\u00e8, nh\u1eadn feedback, m\u1edf Phase 5.

References: