├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── README.md ├── out └── ddz.pyd ├── pybind11 ├── CMakeLists.txt └── ddz.cpp ├── samples ├── CMakeLists.txt ├── demo.py └── demo_main.cpp ├── src ├── common │ ├── CMakeLists.txt │ └── utils.h └── rule │ ├── CMakeLists.txt │ ├── rule.cpp │ └── rule.h └── tests ├── CMakeLists.txt └── rule_test.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Google 4 | AccessModifierOffset: -4 5 | AllowShortFunctionsOnASingleLine: Inline 6 | ColumnLimit: 100 7 | ConstructorInitializerIndentWidth: 8 # double of IndentWidth 8 | ContinuationIndentWidth: 8 # double of IndentWidth 9 | DerivePointerAlignment: false # always use PointerAlignment 10 | IndentCaseLabels: false 11 | IndentWidth: 4 12 | PointerAlignment: Left 13 | ReflowComments: false 14 | SortUsingDeclarations: false 15 | SpacesBeforeTrailingComments: 1 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | # User-specific files 6 | *.rsuser 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Mono auto generated files 16 | mono_crash.* 17 | 18 | # Build results 19 | [Dd]ebug/ 20 | [Dd]ebugPublic/ 21 | [Rr]elease/ 22 | [Rr]eleases/ 23 | x64/ 24 | x86/ 25 | [Ww][Ii][Nn]32/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # ASP.NET Scaffolding 65 | ScaffoldingReadMe.txt 66 | 67 | # StyleCop 68 | StyleCopReport.xml 69 | 70 | # Files built by Visual Studio 71 | *_i.c 72 | *_p.c 73 | *_h.h 74 | *.ilk 75 | *.meta 76 | *.obj 77 | *.iobj 78 | *.pch 79 | *.pdb 80 | *.ipdb 81 | *.pgc 82 | *.pgd 83 | *.rsp 84 | *.sbr 85 | *.tlb 86 | *.tli 87 | *.tlh 88 | *.tmp 89 | *.tmp_proj 90 | *_wpftmp.csproj 91 | *.log 92 | *.vspscc 93 | *.vssscc 94 | .builds 95 | *.pidb 96 | *.svclog 97 | *.scc 98 | 99 | # Chutzpah Test files 100 | _Chutzpah* 101 | 102 | # Visual C++ cache files 103 | ipch/ 104 | *.aps 105 | *.ncb 106 | *.opendb 107 | *.opensdf 108 | *.sdf 109 | *.cachefile 110 | *.VC.db 111 | *.VC.VC.opendb 112 | 113 | # Visual Studio profiler 114 | *.psess 115 | *.vsp 116 | *.vspx 117 | *.sap 118 | 119 | # Visual Studio Trace Files 120 | *.e2e 121 | 122 | # TFS 2012 Local Workspace 123 | $tf/ 124 | 125 | # Guidance Automation Toolkit 126 | *.gpState 127 | 128 | # ReSharper is a .NET coding add-in 129 | _ReSharper*/ 130 | *.[Rr]e[Ss]harper 131 | *.DotSettings.user 132 | 133 | # TeamCity is a build add-in 134 | _TeamCity* 135 | 136 | # DotCover is a Code Coverage Tool 137 | *.dotCover 138 | 139 | # AxoCover is a Code Coverage Tool 140 | .axoCover/* 141 | !.axoCover/settings.json 142 | 143 | # Coverlet is a free, cross platform Code Coverage Tool 144 | coverage*.json 145 | coverage*.xml 146 | coverage*.info 147 | 148 | # Visual Studio code coverage results 149 | *.coverage 150 | *.coveragexml 151 | 152 | # NCrunch 153 | _NCrunch_* 154 | .*crunch*.local.xml 155 | nCrunchTemp_* 156 | 157 | # MightyMoose 158 | *.mm.* 159 | AutoTest.Net/ 160 | 161 | # Web workbench (sass) 162 | .sass-cache/ 163 | 164 | # Installshield output folder 165 | [Ee]xpress/ 166 | 167 | # DocProject is a documentation generator add-in 168 | DocProject/buildhelp/ 169 | DocProject/Help/*.HxT 170 | DocProject/Help/*.HxC 171 | DocProject/Help/*.hhc 172 | DocProject/Help/*.hhk 173 | DocProject/Help/*.hhp 174 | DocProject/Help/Html2 175 | DocProject/Help/html 176 | 177 | # Click-Once directory 178 | publish/ 179 | 180 | # Publish Web Output 181 | *.[Pp]ublish.xml 182 | *.azurePubxml 183 | # Note: Comment the next line if you want to checkin your web deploy settings, 184 | # but database connection strings (with potential passwords) will be unencrypted 185 | *.pubxml 186 | *.publishproj 187 | 188 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 189 | # checkin your Azure Web App publish settings, but sensitive information contained 190 | # in these scripts will be unencrypted 191 | PublishScripts/ 192 | 193 | # NuGet Packages 194 | *.nupkg 195 | # NuGet Symbol Packages 196 | *.snupkg 197 | # The packages folder can be ignored because of Package Restore 198 | **/[Pp]ackages/* 199 | # except build/, which is used as an MSBuild target. 200 | !**/[Pp]ackages/build/ 201 | # Uncomment if necessary however generally it will be regenerated when needed 202 | #!**/[Pp]ackages/repositories.config 203 | # NuGet v3's project.json files produces more ignorable files 204 | *.nuget.props 205 | *.nuget.targets 206 | 207 | # Microsoft Azure Build Output 208 | csx/ 209 | *.build.csdef 210 | 211 | # Microsoft Azure Emulator 212 | ecf/ 213 | rcf/ 214 | 215 | # Windows Store app package directories and files 216 | AppPackages/ 217 | BundleArtifacts/ 218 | Package.StoreAssociation.xml 219 | _pkginfo.txt 220 | *.appx 221 | *.appxbundle 222 | *.appxupload 223 | 224 | # Visual Studio cache files 225 | # files ending in .cache can be ignored 226 | *.[Cc]ache 227 | # but keep track of directories ending in .cache 228 | !?*.[Cc]ache/ 229 | 230 | # Others 231 | ClientBin/ 232 | ~$* 233 | *~ 234 | *.dbmdl 235 | *.dbproj.schemaview 236 | *.jfm 237 | *.pfx 238 | *.publishsettings 239 | orleans.codegen.cs 240 | 241 | # Including strong name files can present a security risk 242 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 243 | #*.snk 244 | 245 | # Since there are multiple workflows, uncomment next line to ignore bower_components 246 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 247 | #bower_components/ 248 | 249 | # RIA/Silverlight projects 250 | Generated_Code/ 251 | 252 | # Backup & report files from converting an old project file 253 | # to a newer Visual Studio version. Backup files are not needed, 254 | # because we have git ;-) 255 | _UpgradeReport_Files/ 256 | Backup*/ 257 | UpgradeLog*.XML 258 | UpgradeLog*.htm 259 | ServiceFabricBackup/ 260 | *.rptproj.bak 261 | 262 | # SQL Server files 263 | *.mdf 264 | *.ldf 265 | *.ndf 266 | 267 | # Business Intelligence projects 268 | *.rdl.data 269 | *.bim.layout 270 | *.bim_*.settings 271 | *.rptproj.rsuser 272 | *- [Bb]ackup.rdl 273 | *- [Bb]ackup ([0-9]).rdl 274 | *- [Bb]ackup ([0-9][0-9]).rdl 275 | 276 | # Microsoft Fakes 277 | FakesAssemblies/ 278 | 279 | # GhostDoc plugin setting file 280 | *.GhostDoc.xml 281 | 282 | # Node.js Tools for Visual Studio 283 | .ntvs_analysis.dat 284 | node_modules/ 285 | 286 | # Visual Studio 6 build log 287 | *.plg 288 | 289 | # Visual Studio 6 workspace options file 290 | *.opt 291 | 292 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 293 | *.vbw 294 | 295 | # Visual Studio LightSwitch build output 296 | **/*.HTMLClient/GeneratedArtifacts 297 | **/*.DesktopClient/GeneratedArtifacts 298 | **/*.DesktopClient/ModelManifest.xml 299 | **/*.Server/GeneratedArtifacts 300 | **/*.Server/ModelManifest.xml 301 | _Pvt_Extensions 302 | 303 | # Paket dependency manager 304 | .paket/paket.exe 305 | paket-files/ 306 | 307 | # FAKE - F# Make 308 | .fake/ 309 | 310 | # CodeRush personal settings 311 | .cr/personal 312 | 313 | # Python Tools for Visual Studio (PTVS) 314 | __pycache__/ 315 | *.pyc 316 | 317 | # Cake - Uncomment if you are using it 318 | # tools/** 319 | # !tools/packages.config 320 | 321 | # Tabs Studio 322 | *.tss 323 | 324 | # Telerik's JustMock configuration file 325 | *.jmconfig 326 | 327 | # BizTalk build output 328 | *.btp.cs 329 | *.btm.cs 330 | *.odx.cs 331 | *.xsd.cs 332 | 333 | # OpenCover UI analysis results 334 | OpenCover/ 335 | 336 | # Azure Stream Analytics local run output 337 | ASALocalRun/ 338 | 339 | # MSBuild Binary and Structured Log 340 | *.binlog 341 | 342 | # NVidia Nsight GPU debugger configuration file 343 | *.nvuser 344 | 345 | # MFractors (Xamarin productivity tool) working folder 346 | .mfractor/ 347 | 348 | # Local History for Visual Studio 349 | .localhistory/ 350 | 351 | # BeatPulse healthcheck temp database 352 | healthchecksdb 353 | 354 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 355 | MigrationBackup/ 356 | 357 | # Ionide (cross platform F# VS Code tools) working folder 358 | .ionide/ 359 | 360 | # Fody - auto-generated XML schema 361 | FodyWeavers.xsd 362 | 363 | 364 | out/* 365 | .idea 366 | CMakeSettings.json 367 | !out/ddz.pyd 368 | build/ 369 | .vscode/ 370 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(ddz) 3 | 4 | add_subdirectory(src/common) 5 | add_subdirectory(src/rule) 6 | 7 | add_subdirectory(samples) 8 | 9 | add_subdirectory(pybind11) 10 | 11 | enable_testing() 12 | add_subdirectory(tests) 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++实现斗地主出牌引擎 2 | 3 | 利用 pybind11 提供 python 接口。 4 | 5 | 使用方法可参考 samples/demo.py 和 tests/rule_test.cpp。 6 | -------------------------------------------------------------------------------- /out/ddz.pyd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skyduy/ddz-engine/921a907d4f92df732ee83efb3483a3ea5295e134/out/ddz.pyd -------------------------------------------------------------------------------- /pybind11/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(pybind11 CONFIG REQUIRED) 2 | add_library(ddz SHARED ddz.cpp) 3 | target_link_libraries(ddz PRIVATE ddz::rule pybind11::embed pybind11::module pybind11::pybind11) 4 | 5 | install(TARGETS ddz) 6 | -------------------------------------------------------------------------------- /pybind11/ddz.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include // for automatic conversions 3 | 4 | #include "rule/rule.h" 5 | 6 | namespace py = pybind11; 7 | 8 | PYBIND11_MODULE(ddz, m) { 9 | m.doc() = "斗地主插件"; 10 | m.def("get_actions", &getActions, "斗地主出牌规则"); 11 | } 12 | -------------------------------------------------------------------------------- /samples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # library 2 | add_executable(demo_main demo_main.cpp) 3 | target_link_libraries(demo_main PRIVATE ddz::rule ddz::common) 4 | -------------------------------------------------------------------------------- /samples/demo.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | dir_path = os.path.dirname(os.path.realpath(__file__)) 5 | 6 | # On Windows: make sure you have install the ddz.dll file and rename it to 7 | # ddz.pyd at specific location: ddz_bin_path 8 | ddz_bin_path = os.path.realpath(os.path.join(dir_path, '..', 'out')) 9 | sys.path.insert(0, ddz_bin_path) 10 | 11 | import ddz 12 | handcard = [0, 2, 1, 2, 1, 1, 1, 1, 1, 3, 0, 2, 3, 1, 0] 13 | lastcard = [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 14 | res = ddz.get_actions(handcard, lastcard) 15 | for choice in res: 16 | print(choice) 17 | -------------------------------------------------------------------------------- /samples/demo_main.cpp: -------------------------------------------------------------------------------- 1 | #include "common/utils.h" 2 | #include "rule/rule.h" 3 | 4 | int main() { 5 | CardVector handcards = {0, 2, 1, 2, 1, 1, 1, 1, 1, 3, 0, 2, 3, 1, 0}; 6 | CardVector lastcards = {2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 7 | std::vector res = getActions(handcards, lastcards); 8 | print(res); 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /src/common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(COMMON INTERFACE) 2 | target_include_directories(COMMON INTERFACE ${PROJECT_SOURCE_DIR}/src) 3 | 4 | add_library(ddz::common ALIAS COMMON) 5 | -------------------------------------------------------------------------------- /src/common/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef __CARD_UTILS_H__ 2 | #define __CARD_UTILS_H__ 3 | 4 | #include 5 | #include 6 | 7 | template 8 | void print(const std::vector& v) { 9 | for (T i : v) { 10 | std::cout << i << ' '; 11 | } 12 | std::cout << std::endl; 13 | } 14 | 15 | template 16 | void print(const std::vector>& vs) { 17 | for (auto v : vs) { 18 | print(v); 19 | } 20 | std::cout << std::endl; 21 | } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/rule/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(RULE STATIC rule.cpp) 2 | target_include_directories(RULE PUBLIC ${PROJECT_SOURCE_DIR}/src) 3 | 4 | add_library(ddz::rule ALIAS RULE) 5 | -------------------------------------------------------------------------------- /src/rule/rule.cpp: -------------------------------------------------------------------------------- 1 | #include "rule/rule.h" 2 | 3 | inline void reset(std::vector& v) { 4 | std::fill(v.begin(), v.end(), 0); 5 | } 6 | 7 | // select k-combination from options to candidates 8 | void combine(const CardList& options, size_t k, std::vector& candidates) { 9 | size_t n = options.size(); 10 | int i = 0; 11 | CardList candidate(k, 0); 12 | std::vector index(k, 0); 13 | while (i >= 0) { 14 | index[i]++; 15 | if (index[i] > n) { 16 | --i; 17 | } else if (i == k - 1) { 18 | reset(candidate); 19 | for (int j = 0; j < k; j++) { 20 | candidate[j] = options[index[j] - 1]; 21 | } 22 | candidates.push_back(candidate); 23 | } else { 24 | ++i; 25 | index[i] = index[size_t(i) - 1]; 26 | } 27 | } 28 | } 29 | 30 | std::vector getActions(CardVector& handcards, CardVector& last) { 31 | std::vector res; 32 | CardVector choice(15, 0); 33 | int totalCard = 0; // 手里的牌数目 34 | for (int num : last) totalCard += num; 35 | if (totalCard == 0) { 36 | std::vector lenDict = {5, 3, 2}; 37 | // 核弹 38 | if (handcards[13] && handcards[14]) { 39 | reset(choice); 40 | choice[13] = choice[14] = 1; 41 | 42 | res.push_back(choice); 43 | } 44 | // 单出:单、双、三、四 & 四带2单,2双 45 | for (int i = 0; i < 15; i++) { 46 | for (int j = 1; j <= handcards[i]; j++) { 47 | reset(choice); 48 | choice[i] = j; 49 | res.push_back(choice); 50 | if (j == 4) { // 四带2单,2双 51 | for (int num = 1; num <= 2; num++) { // 单双翅 52 | CardList container; 53 | for (int k = 0; k < 15; k++) { 54 | if (k != i && handcards[k] >= num) { 55 | container.push_back(k); 56 | } 57 | } 58 | if (container.size() < 2) break; 59 | std::vector candidates; 60 | combine(container, 2, candidates); 61 | for (CardList candidate : candidates) { 62 | CardVector backup(choice); 63 | for (int c : candidate) { 64 | backup[c] = num; 65 | } 66 | res.push_back(backup); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | // 三带一,三带二 73 | for (int main = 0; main < 15; main++) { 74 | if (handcards[main] >= 3) { 75 | for (int i = 0; i < 15; i++) { 76 | if (i != main && handcards[i] != 0) { // + 1 77 | reset(choice); 78 | choice[main] = 3; 79 | choice[i] = 1; 80 | res.push_back(choice); 81 | } 82 | if (i != main && handcards[i] > 1) { // + 2 83 | reset(choice); 84 | choice[main] = 3; 85 | choice[i] = 2; 86 | res.push_back(choice); 87 | } 88 | } 89 | } 90 | } 91 | // 顺子:单、双、三 & 飞机带翅膀 92 | for (int t = 0; t < 3; t++) { 93 | int start = 0, cur = 0; 94 | while (cur < 12) { 95 | // 'A' index is 11 96 | while (cur < 12 && handcards[cur] > t) cur++; 97 | if (cur - start >= lenDict[t]) { 98 | // 手牌最多有20个 99 | for (int len = lenDict[t]; len <= (cur - start) && (t + 1) * len <= 20; len++) { 100 | for (int i = start; i + len <= cur; i++) { 101 | reset(choice); 102 | for (int j = i; j < i + len; j++) { 103 | choice[j] = t + 1; 104 | } 105 | res.push_back(choice); 106 | if (t == 2) { // 飞机可以带1, 2翅膀 107 | for (int wing = 1; wing <= 2; wing++) { // 单双翅 108 | if ((t + 1) * len + wing * len <= 20) { 109 | CardList container; 110 | for (int i = 0; i < 15; i++) { 111 | if (choice[i] == 0 && handcards[i] >= wing) { 112 | container.push_back(i); 113 | } 114 | } 115 | if (int(container.size()) < len) break; 116 | std::vector candidates; 117 | combine(container, len, candidates); 118 | for (CardList candidate : candidates) { 119 | CardVector backup(choice); 120 | for (int c : candidate) { 121 | backup[c] = wing; 122 | } 123 | res.push_back(backup); 124 | } 125 | } 126 | } 127 | } 128 | } 129 | } 130 | } 131 | while (cur < 12 && handcards[cur] <= t) cur++; 132 | start = cur; 133 | } 134 | } 135 | } else { 136 | // 全局数据 137 | int diffCard = 0; // 手里不同种类的牌数 138 | int maxCard = 0; // 手里的最大的牌: 0, 1, ... 14 139 | int maxCount = 0; // 手里最多的牌,他有多少个 140 | std::vector maxCountCard; // 手里最多的牌形成的数组 141 | for (int i = 0; i < 15; i++) { 142 | if (last[i] != 0) { 143 | maxCard = i; 144 | diffCard += 1; 145 | if (last[i] > maxCount) maxCount = last[i]; 146 | } 147 | } 148 | for (int i = 0; i < 15; i++) { 149 | if (last[i] == maxCount) { 150 | maxCountCard.push_back(i); 151 | } 152 | } 153 | // 让一步,要不起 154 | CardVector none(15, 0); 155 | res.push_back(none); 156 | // --------------碾压规则------------------ 157 | // 核弹碾压一切 158 | if (handcards[13] == 1 && handcards[14] == 1) { // 核弹炸他! 159 | reset(choice); 160 | choice[13] = 1; 161 | choice[14] = 1; 162 | res.push_back(choice); 163 | } 164 | if (totalCard == 2 && last[13] == 1 && last[14] == 1) { // 核弹要不起 165 | return res; 166 | } 167 | // 炸弹碾压非核弹,非更大的炸弹以外的所有牌型 168 | if (diffCard == 1 && maxCount == 4) { 169 | for (int i = maxCard + 1; i < 15; i++) { 170 | if (handcards[i] == 4) { 171 | reset(choice); 172 | choice[i] = 4; 173 | res.push_back(choice); 174 | } 175 | } 176 | } else { 177 | for (int i = 0; i < 15; i++) { 178 | if (handcards[i] == 4) { 179 | reset(choice); 180 | choice[i] = 4; 181 | res.push_back(choice); 182 | } 183 | } 184 | } 185 | // --------------针对性规则------------------ 186 | // 单出:1,2,3 187 | if (diffCard == 1) { 188 | if (maxCount < 4) { 189 | for (int i = maxCard + 1; i < 15; i++) { 190 | if (handcards[i] >= maxCount) { 191 | reset(choice); 192 | choice[i] = maxCount; 193 | res.push_back(choice); 194 | } 195 | } 196 | } 197 | return res; 198 | } 199 | // 三带一,三带二 200 | if ((totalCard == 4 || totalCard == 5) && diffCard == 2 && maxCount == 3) { 201 | int kicker = totalCard - 3; 202 | for (int main = maxCountCard[0] + 1; main < 15; main++) { 203 | if (handcards[main] >= 3) { 204 | for (int i = 0; i < 15; i++) { 205 | if (i != main && handcards[i] >= kicker) { // 带1或2 206 | reset(choice); 207 | choice[main] = 3; 208 | choice[i] = kicker; 209 | res.push_back(choice); 210 | } 211 | } 212 | } 213 | } 214 | return res; 215 | } 216 | // 顺子:1,2,3 217 | if (diffCard == maxCountCard.size()) { 218 | int type = totalCard / diffCard; 219 | int start = maxCountCard[0] + 1; 220 | int cur = start; 221 | while (cur < 12) { 222 | // 'A' index is 11 223 | while (cur < 12 && handcards[cur] >= type) cur++; 224 | if (cur - start >= diffCard) { 225 | for (int i = start; i + diffCard <= cur; i++) { 226 | reset(choice); 227 | for (int j = i; j < i + diffCard; j++) { 228 | choice[j] = type; 229 | } 230 | res.push_back(choice); 231 | } 232 | } 233 | while (cur < 12 && handcards[cur] < type) cur++; 234 | start = cur; 235 | } 236 | return res; 237 | } 238 | // 四带俩1,四带俩2 239 | if ((diffCard == 3 && maxCount == 4 && totalCard == 6) || (totalCard == 8)) { 240 | int kicker = (totalCard - 4) / 2; 241 | for (int i = maxCountCard[0] + 1; i < 15; i++) { 242 | if (handcards[i] == 4) { 243 | reset(choice); 244 | choice[i] = 4; 245 | 246 | CardList container; 247 | for (int k = 0; k < 15; k++) { 248 | if (k != i && handcards[k] >= kicker) { 249 | container.push_back(k); 250 | } 251 | } 252 | if (container.size() < 2) continue; 253 | std::vector candidates; 254 | combine(container, 2, candidates); 255 | for (CardList candidate : candidates) { 256 | CardVector backup(choice); 257 | for (int c : candidate) { 258 | backup[c] = kicker; 259 | } 260 | res.push_back(backup); 261 | } 262 | } 263 | } 264 | return res; 265 | } 266 | // 飞机带一、二翅 267 | for (int wing = 1; wing <= 2; wing++) { 268 | if (maxCountCard.size() >= 2 && maxCount == 3 && 269 | totalCard == (3 + size_t(wing)) * maxCountCard.size() && 270 | diffCard == 2 * maxCountCard.size()) { 271 | int len = diffCard / 2; 272 | int start = maxCountCard[0]; 273 | int cur = start; 274 | while (cur < 12) { 275 | // 'A' index is 11 276 | while (cur < 12 && handcards[cur] >= 3) cur++; 277 | if (cur - start >= len) { 278 | for (int i = start; i + len <= cur; i++) { 279 | reset(choice); 280 | for (int j = i; j < i + len; j++) { 281 | choice[j] = 3; 282 | } 283 | CardList container; 284 | for (int i = 0; i < 15; i++) { 285 | if (choice[i] == 0 && handcards[i] >= wing) { 286 | container.push_back(i); 287 | } 288 | } 289 | if (container.size() < 2) continue; 290 | std::vector candidates; 291 | combine(container, len, candidates); 292 | for (CardList candidate : candidates) { 293 | CardVector backup(choice); 294 | for (int c : candidate) { 295 | backup[c] = wing; 296 | } 297 | res.push_back(backup); 298 | } 299 | } 300 | } 301 | while (cur < 12 && handcards[cur] < 3) cur++; 302 | start = cur; 303 | } 304 | return res; 305 | } 306 | } 307 | } 308 | return res; 309 | } 310 | -------------------------------------------------------------------------------- /src/rule/rule.h: -------------------------------------------------------------------------------- 1 | #ifndef __CARD_RULE_H__ 2 | #define __CARD_RULE_H__ 3 | 4 | #include 5 | 6 | typedef std::vector CardVector; 7 | typedef std::vector CardList; 8 | 9 | void combine(const CardList& options, size_t k, std::vector& candidates); 10 | std::vector getActions(CardVector& handcards, CardVector& last); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(GTest CONFIG REQUIRED) 2 | 3 | add_executable(rule_test "rule_test.cpp") 4 | target_link_libraries(rule_test GTest::gtest_main ddz::rule) 5 | 6 | add_test(test_all rule_test) 7 | -------------------------------------------------------------------------------- /tests/rule_test.cpp: -------------------------------------------------------------------------------- 1 | #include "rule/rule.h" 2 | 3 | #include 4 | 5 | std::vector res; 6 | 7 | TEST(combineTest, isOK) { 8 | CardList options{3, 4, 5, 6}; 9 | std::vector res; 10 | combine(options, 2, res); 11 | std::vector ans{{3, 4}, {3, 5}, {3, 6}, {4, 5}, {4, 6}, {5, 6}}; 12 | EXPECT_EQ(res, ans); 13 | } 14 | 15 | TEST(getActionsTest, 主动_手持54张牌_主动出牌13550种) { 16 | CardVector handcards; 17 | for (int i = 0; i < 13; i++) handcards.push_back(4); 18 | handcards.push_back(1); 19 | handcards.push_back(1); 20 | CardVector none; 21 | res = getActions(handcards, none); 22 | EXPECT_EQ(res.size(), 13550); 23 | } 24 | 25 | TEST(getActionsTest, 主动_手持20张春天牌_主动出牌368种) { 26 | CardVector handcards = {0, 0, 0, 0, 0, 1, 1, 3, 3, 3, 3, 3, 1, 1, 1}; 27 | CardVector none; 28 | res = getActions(handcards, none); 29 | EXPECT_EQ(res.size(), 368); 30 | } 31 | 32 | TEST(getActionsTest, 被动_要不起) { 33 | CardVector handcards = {0, 2, 1, 2, 1, 1, 1, 1, 1, 3, 0, 2, 3, 1, 0}; 34 | CardVector lastcards = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0}; 35 | res = getActions(handcards, lastcards); 36 | EXPECT_EQ(res.size(), 1); 37 | } 38 | 39 | TEST(getActionsTest, 被动_对方核弹_要不起) { 40 | CardVector handcards = {0, 2, 1, 2, 1, 1, 1, 1, 1, 3, 0, 2, 3, 1, 0}; 41 | CardVector lastcards = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}; 42 | res = getActions(handcards, lastcards); 43 | EXPECT_EQ(res.size(), 1); 44 | } 45 | 46 | TEST(getActionsTest, 被动_我方核弹_炸他) { 47 | CardVector handcards = {1, 1, 1, 2, 1, 1, 1, 1, 1, 3, 0, 2, 3, 1, 1}; 48 | CardVector lastcards = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0}; 49 | res = getActions(handcards, lastcards); 50 | CardVector ans = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}; 51 | EXPECT_EQ(res.size(), 2); 52 | EXPECT_EQ(res[1], ans); 53 | } 54 | 55 | TEST(getActionsTest, 被动_炸弹炸他) { 56 | CardVector handcards = {0, 4, 1, 2, 1, 1, 1, 1, 1, 3, 0, 2, 3, 1, 0}; 57 | CardVector lastcards = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0}; 58 | res = getActions(handcards, lastcards); 59 | CardVector ans = {0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 60 | EXPECT_EQ(res[1], ans); 61 | } 62 | 63 | TEST(getActionsTest, 被动_炸弹被压_要不起) { 64 | CardVector handcards = {1, 1, 1, 2, 1, 1, 1, 1, 1, 3, 0, 4, 0, 0, 0}; 65 | CardVector lastcards = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0}; 66 | res = getActions(handcards, lastcards); 67 | EXPECT_EQ(res.size(), 1); 68 | } 69 | 70 | TEST(getActionsTest, 被动_单张_压他) { 71 | CardVector handcards = {0, 2, 1, 2, 1, 1, 1, 1, 1, 3, 0, 2, 3, 1, 0}; 72 | CardVector lastcards = {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 73 | res = getActions(handcards, lastcards); 74 | EXPECT_EQ(res.size(), 11 + 1); 75 | } 76 | 77 | TEST(getActionsTest, 被动_两张_压死) { 78 | CardVector handcards = {0, 2, 1, 2, 1, 1, 1, 1, 1, 3, 0, 2, 3, 1, 0}; 79 | CardVector lastcards = {2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 80 | res = getActions(handcards, lastcards); 81 | EXPECT_EQ(res.size(), 5 + 1); 82 | } 83 | 84 | TEST(getActionsTest, 被动_三张_大你) { 85 | CardVector handcards = {0, 2, 1, 2, 1, 1, 1, 1, 1, 3, 0, 2, 3, 1, 0}; 86 | CardVector lastcards = {0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 87 | res = getActions(handcards, lastcards); 88 | EXPECT_EQ(res.size(), 2 + 1); 89 | } 90 | 91 | TEST(getActionsTest, 被动_炸弹压炸弹) { 92 | CardVector handcards = {0, 2, 1, 2, 1, 1, 1, 0, 1, 1, 0, 4, 1, 0, 0}; 93 | CardVector lastcards = {0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 94 | res = getActions(handcards, lastcards); 95 | EXPECT_EQ(res.size(), 1 + 1); 96 | } 97 | 98 | TEST(getActionsTest, 被动_三带一_压死) { 99 | CardVector handcards = {0, 2, 1, 2, 1, 1, 1, 0, 1, 1, 0, 3, 1, 0, 0}; 100 | CardVector lastcards = {0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 101 | res = getActions(handcards, lastcards); 102 | EXPECT_EQ(res.size(), 9 + 1); 103 | } 104 | 105 | TEST(getActionsTest, 被动_三带二_压死) { 106 | CardVector handcards = {0, 2, 1, 2, 1, 1, 1, 0, 1, 1, 0, 3, 1, 0, 0}; 107 | CardVector lastcards = {0, 0, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0}; 108 | res = getActions(handcards, lastcards); 109 | EXPECT_EQ(res.size(), 2 + 1); 110 | } 111 | 112 | TEST(getActionsTest, 被动_顺子_管上) { 113 | CardVector handcards = {0, 2, 1, 2, 1, 1, 0, 1, 1, 1, 1, 3, 1, 0, 0}; 114 | CardVector lastcards = {1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 115 | res = getActions(handcards, lastcards); 116 | EXPECT_EQ(res.size(), 2 + 1); 117 | } 118 | 119 | TEST(getActionsTest, 被动_连顺_大你) { 120 | CardVector handcards = {0, 2, 1, 2, 2, 2, 2, 0, 1, 1, 0, 0, 1, 0, 0}; 121 | CardVector lastcards = {0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 122 | res = getActions(handcards, lastcards); 123 | EXPECT_EQ(res.size(), 2 + 1); 124 | } 125 | 126 | TEST(getActionsTest, 被动_凸翅飞机_压死) { 127 | CardVector handcards = {0, 3, 3, 3, 3, 1, 0, 0, 0, 0, 0, 3, 0, 0, 0}; 128 | CardVector lastcards = {3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 129 | res = getActions(handcards, lastcards); 130 | EXPECT_EQ(res.size(), 3 + 1); 131 | } 132 | 133 | TEST(getActionsTest, 被动_四带俩单张_压死) { 134 | CardVector handcards = {0, 2, 1, 2, 2, 2, 4, 0, 1, 1, 0, 0, 1, 0, 0}; 135 | CardVector lastcards = {0, 1, 1, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 136 | res = getActions(handcards, lastcards); 137 | EXPECT_EQ(res.size(), 1 + 1 + 28); 138 | } 139 | 140 | TEST(getActionsTest, 被动_四带俩2) { 141 | CardVector handcards = {0, 2, 1, 2, 2, 2, 2, 0, 1, 4, 0, 0, 1, 0, 0}; 142 | CardVector lastcards = {0, 2, 2, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 143 | res = getActions(handcards, lastcards); 144 | EXPECT_EQ(res.size(), 1 + 1 + 10); 145 | } 146 | 147 | TEST(getActionsTest, 被动_单翅飞机_压死) { 148 | CardVector handcards = {0, 2, 1, 3, 3, 3, 3, 0, 1, 1, 0, 0, 1, 0, 0}; 149 | CardVector lastcards = {0, 1, 3, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}; 150 | res = getActions(handcards, lastcards); 151 | EXPECT_EQ(res.size(), 1 + 20 + 20); 152 | } 153 | 154 | TEST(getActionsTest, 被动_双翅飞机_管上) { 155 | CardVector lastcards = {0, 2, 3, 3, 3, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0}; 156 | CardVector handcards = {0, 2, 1, 3, 3, 3, 3, 0, 1, 2, 0, 0, 1, 0, 0}; 157 | res = getActions(handcards, lastcards); 158 | EXPECT_EQ(res.size(), 1 + 2); 159 | } 160 | --------------------------------------------------------------------------------