@slugbugblue/trax 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +11 -0
- package/.gitlab-ci.yml +90 -0
- package/.husky/pre-commit +1 -0
- package/CHANGELOG.md +14 -0
- package/CONTRIBUTING.md +113 -0
- package/Dockerfile +14 -5
- package/LICENSE +1 -1
- package/README.md +29 -10
- package/docs/analyst.md +13 -2
- package/docs/lru.md +151 -0
- package/package.json +15 -4
- package/src/analyst.js +68 -34
- package/src/cmds/import-export.js +9 -2
- package/src/cmds/notes.js +3 -0
- package/src/cmds/play-try.js +8 -2
- package/src/cmds/puzzles.js +26 -7
- package/src/cmds/suggest.js +5 -2
- package/src/engine.js +1 -1
- package/src/lru.js +153 -0
- package/src/point.js +1 -1
- package/src/puzzles.js +285 -16
- package/src/threats.js +3 -3
- package/src/types.d.ts +4 -2
- package/src/utils.js +22 -0
- package/src/version.js +1 -1
- package/benchmark/benchmarks.json +0 -42
- package/benchmark/puzzles.bench.js +0 -190
package/src/puzzles.js
CHANGED
|
@@ -26,6 +26,9 @@ export const sources = {
|
|
|
26
26
|
book1: {
|
|
27
27
|
name: 'Martin M. S. Pedersen',
|
|
28
28
|
url: 'https://gnutrax.com/book',
|
|
29
|
+
copyright: '2011',
|
|
30
|
+
license: 'CC BY 4.0',
|
|
31
|
+
licenseUrl: 'https://creativecommons.org/licenses/by/4.0/',
|
|
29
32
|
},
|
|
30
33
|
}
|
|
31
34
|
|
|
@@ -764,7 +767,7 @@ export const puzzles = [
|
|
|
764
767
|
level: 3,
|
|
765
768
|
player: 2,
|
|
766
769
|
max: 16,
|
|
767
|
-
|
|
770
|
+
hint: 'A0+',
|
|
768
771
|
},
|
|
769
772
|
{
|
|
770
773
|
id: 'hb4.trx',
|
|
@@ -811,7 +814,7 @@ export const puzzles = [
|
|
|
811
814
|
hint: 'B1+',
|
|
812
815
|
},
|
|
813
816
|
{
|
|
814
|
-
id: '
|
|
817
|
+
id: 'book.01',
|
|
815
818
|
src: 'book1',
|
|
816
819
|
game: 'traxloop',
|
|
817
820
|
notation: '@0/ @1+ B0/ C1+ D2/ C3/',
|
|
@@ -822,18 +825,18 @@ export const puzzles = [
|
|
|
822
825
|
hint: '@2/',
|
|
823
826
|
},
|
|
824
827
|
{
|
|
825
|
-
id: '
|
|
828
|
+
id: 'book.02',
|
|
826
829
|
src: 'book1',
|
|
827
830
|
game: 'traxloop',
|
|
828
831
|
notation: '@0+ B1+ C1/ B0/ B0/ B0/ B0/ D5/ E5/ E6+ C4/ C3+ B6+',
|
|
829
|
-
icon: '0df:0ca:
|
|
832
|
+
icon: '0df:0ca:0de:0cd:eecdc:0efae',
|
|
830
833
|
level: 2,
|
|
831
834
|
player: 2,
|
|
832
835
|
max: 18,
|
|
833
836
|
hints: ['D2\\', 'D3/'],
|
|
834
837
|
},
|
|
835
838
|
{
|
|
836
|
-
id: '
|
|
839
|
+
id: 'book.03',
|
|
837
840
|
src: 'book1',
|
|
838
841
|
game: 'traxloop',
|
|
839
842
|
notation: '@0/ @1/ @1+ @1/ @1+ C0/ D1/ E1+ D0+ E1+ F1\\ E0+',
|
|
@@ -844,18 +847,18 @@ export const puzzles = [
|
|
|
844
847
|
hint: 'C1\\',
|
|
845
848
|
},
|
|
846
849
|
{
|
|
847
|
-
id: '
|
|
850
|
+
id: 'book.04',
|
|
848
851
|
src: 'book1',
|
|
849
852
|
game: 'traxloop',
|
|
850
|
-
notation: '@0+ B1/ @1/ A0/ C3/ A4+ @4+ @3/
|
|
853
|
+
notation: '@0+ B1/ @1/ A0/ C3/ A4+ @4+ @3/ A5\\',
|
|
851
854
|
icon: '1caf:1dec:dcafd:aeec:fd',
|
|
852
855
|
level: 3,
|
|
853
856
|
player: 2,
|
|
854
857
|
max: 16,
|
|
855
|
-
|
|
858
|
+
hints: ['C0/', 'D0\\'],
|
|
856
859
|
},
|
|
857
860
|
{
|
|
858
|
-
id: '
|
|
861
|
+
id: 'book.05',
|
|
859
862
|
src: 'book1',
|
|
860
863
|
game: 'traxloop',
|
|
861
864
|
notation: '@0/ @1/ B0+ @2+ A0/ @2+',
|
|
@@ -866,7 +869,7 @@ export const puzzles = [
|
|
|
866
869
|
hints: ['B4\\', 'C4/'],
|
|
867
870
|
},
|
|
868
871
|
{
|
|
869
|
-
id: '
|
|
872
|
+
id: 'book.07',
|
|
870
873
|
src: 'book1',
|
|
871
874
|
game: 'traxloop',
|
|
872
875
|
notation: '@0/ A2+ A0+ @2\\ B0+ C1+ B0/ B0/ A2+',
|
|
@@ -877,7 +880,7 @@ export const puzzles = [
|
|
|
877
880
|
hint: 'C6+',
|
|
878
881
|
},
|
|
879
882
|
{
|
|
880
|
-
id: '
|
|
883
|
+
id: 'book.08',
|
|
881
884
|
src: 'book1',
|
|
882
885
|
game: 'traxloop',
|
|
883
886
|
notation: '@0/ @1/ A2/ A0/ B1/ @1+ @1/ A0/ @1/',
|
|
@@ -888,7 +891,7 @@ export const puzzles = [
|
|
|
888
891
|
hint: 'A0\\',
|
|
889
892
|
},
|
|
890
893
|
{
|
|
891
|
-
id: '
|
|
894
|
+
id: 'book.09',
|
|
892
895
|
src: 'book1',
|
|
893
896
|
game: 'traxloop',
|
|
894
897
|
notation: '@0/ @1/ B0/ B0/ A0/ A5/ @3\\ @4/',
|
|
@@ -899,7 +902,7 @@ export const puzzles = [
|
|
|
899
902
|
hint: 'A2\\',
|
|
900
903
|
},
|
|
901
904
|
{
|
|
902
|
-
id: '
|
|
905
|
+
id: 'book.10',
|
|
903
906
|
src: 'book1',
|
|
904
907
|
game: 'traxloop',
|
|
905
908
|
notation: '@0/ A0+ A0/ A4+ A0/ A0/ B4+ C4/ @4+ @4/ @4/',
|
|
@@ -907,10 +910,10 @@ export const puzzles = [
|
|
|
907
910
|
level: 2,
|
|
908
911
|
player: 2,
|
|
909
912
|
max: 18,
|
|
910
|
-
|
|
913
|
+
hints: ['D7\\', 'E6+'],
|
|
911
914
|
},
|
|
912
915
|
{
|
|
913
|
-
id: '
|
|
916
|
+
id: 'book.11',
|
|
914
917
|
src: 'book1',
|
|
915
918
|
game: 'traxloop',
|
|
916
919
|
notation: '@0+ B1+ C1/ C0/ B0/ @3+',
|
|
@@ -921,7 +924,7 @@ export const puzzles = [
|
|
|
921
924
|
hint: 'B1\\',
|
|
922
925
|
},
|
|
923
926
|
{
|
|
924
|
-
id: '
|
|
927
|
+
id: 'book.12',
|
|
925
928
|
src: 'book1',
|
|
926
929
|
game: 'traxloop',
|
|
927
930
|
notation: '@0+ B1+ C1/ C0/ B0/ D2+',
|
|
@@ -929,8 +932,274 @@ export const puzzles = [
|
|
|
929
932
|
level: 3,
|
|
930
933
|
player: 1,
|
|
931
934
|
max: 15,
|
|
935
|
+
hints: ['B4\\', 'C4/'],
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
id: 'book.13',
|
|
939
|
+
src: 'book1',
|
|
940
|
+
game: 'traxloop',
|
|
941
|
+
notation: '@0/ @1/ B2+ A0\\ @1+ @1\\ C4/ B4/',
|
|
942
|
+
icon: 'aefa:1cd:1fb:0dc',
|
|
943
|
+
level: 3,
|
|
944
|
+
player: 1,
|
|
945
|
+
max: 15,
|
|
946
|
+
hint: 'C5\\',
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
id: 'book.15',
|
|
950
|
+
src: 'book1',
|
|
951
|
+
game: 'traxloop',
|
|
952
|
+
notation: '@0/ A0/ @1+ A0\\ C1+ D1+ B0+ A0/ @5+ A2/ A0+ @1\\ A4+ @4+',
|
|
953
|
+
icon: 'caef:dfdca:cacdef:eefafbb:0faec:0aefd',
|
|
954
|
+
level: 3,
|
|
955
|
+
player: 1,
|
|
956
|
+
max: 21,
|
|
957
|
+
hints: ['B7\\', 'C7/'],
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
id: 'book.17',
|
|
961
|
+
src: 'book1',
|
|
962
|
+
game: 'traxloop',
|
|
963
|
+
notation: '@0/ @1/ B0+ C1/ @1\\ B0/ C0/ D0+ A2+ @2+',
|
|
964
|
+
icon: '2de:eecaf:0fdec:0acd',
|
|
965
|
+
level: 3,
|
|
966
|
+
player: 1,
|
|
967
|
+
max: 19,
|
|
968
|
+
hints: ['A1/', 'B1\\'],
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
id: 'book.18',
|
|
972
|
+
src: 'book1',
|
|
973
|
+
game: 'traxloop',
|
|
974
|
+
notation: '@0+ B1+ C1/ B2\\ B3\\ B4\\ B5\\',
|
|
975
|
+
icon: 'eec:0fd:0ac:0fd:0ac',
|
|
976
|
+
level: 2,
|
|
977
|
+
player: 2,
|
|
978
|
+
max: 14,
|
|
979
|
+
hint: 'D3+',
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
id: 'book.19',
|
|
983
|
+
src: 'book1',
|
|
984
|
+
game: 'traxloop',
|
|
985
|
+
notation: '@0+ B1+ @1/ @1/ B2\\',
|
|
986
|
+
icon: 'cdee:fa',
|
|
987
|
+
level: 2,
|
|
988
|
+
player: 2,
|
|
989
|
+
max: 12,
|
|
990
|
+
hint: 'E1\\',
|
|
991
|
+
},
|
|
992
|
+
{
|
|
993
|
+
id: 'book.20',
|
|
994
|
+
src: 'book1',
|
|
995
|
+
game: 'traxloop',
|
|
996
|
+
notation: '@0/ @1/ B0+ B0/ C3+ D3+ E3\\',
|
|
997
|
+
icon: '0c:de:cdeef',
|
|
998
|
+
level: 2,
|
|
999
|
+
player: 2,
|
|
1000
|
+
max: 14,
|
|
1001
|
+
hints: ['C4+', 'B4\\'],
|
|
1002
|
+
},
|
|
1003
|
+
{
|
|
1004
|
+
id: 'book.23',
|
|
1005
|
+
src: 'book1',
|
|
1006
|
+
game: 'traxloop',
|
|
1007
|
+
notation: '@0+ @1\\ @1\\ @1\\ @1+ A0+ @1/ B3\\ E3/',
|
|
1008
|
+
icon: 'cb:fbafae:0a1d',
|
|
1009
|
+
level: 2,
|
|
1010
|
+
player: 2,
|
|
1011
|
+
max: 12,
|
|
1012
|
+
hint: 'F1+',
|
|
1013
|
+
},
|
|
1014
|
+
{
|
|
1015
|
+
id: 'book.25',
|
|
1016
|
+
src: 'book1',
|
|
1017
|
+
game: 'traxloop',
|
|
1018
|
+
notation: '@0/ @1/ B0/ B0+ C1/ C0+ B0/ A2+ @1/ @3\\',
|
|
1019
|
+
icon: '0cdca:0fbde:facbd:afdc:facd',
|
|
1020
|
+
level: 3,
|
|
1021
|
+
player: 1,
|
|
1022
|
+
max: 17,
|
|
1023
|
+
hint: 'B0+',
|
|
1024
|
+
},
|
|
1025
|
+
{
|
|
1026
|
+
id: 'book.26',
|
|
1027
|
+
src: 'book1',
|
|
1028
|
+
game: 'traxloop',
|
|
1029
|
+
notation: '@0/ @1/ @1/ @1+ A0\\ B0+ @3/ @3/',
|
|
1030
|
+
icon: '1cbaf:cdfafa:dcbdcd',
|
|
1031
|
+
level: 4,
|
|
1032
|
+
player: 1,
|
|
1033
|
+
max: 21,
|
|
932
1034
|
hint: 'B4\\',
|
|
933
1035
|
},
|
|
1036
|
+
{
|
|
1037
|
+
id: 'book.28',
|
|
1038
|
+
src: 'book1',
|
|
1039
|
+
game: 'traxloop',
|
|
1040
|
+
notation: '@0/ @1/ B0/ C1/ C0/ @2+ A0+ D0+ @2\\ @3+ A4/ @4\\ A2/',
|
|
1041
|
+
icon: '3cde:dcaefaf:aefdcdc:fdcbdcd:0afacdc',
|
|
1042
|
+
level: 2,
|
|
1043
|
+
player: 2,
|
|
1044
|
+
max: 20,
|
|
1045
|
+
hint: 'G6+',
|
|
1046
|
+
},
|
|
1047
|
+
{
|
|
1048
|
+
id: 'book.29',
|
|
1049
|
+
src: 'book1',
|
|
1050
|
+
game: 'traxloop',
|
|
1051
|
+
notation: '@0/ B1+ B2\\ B3/',
|
|
1052
|
+
icon: 'de:af:0c',
|
|
1053
|
+
level: 3,
|
|
1054
|
+
player: 1,
|
|
1055
|
+
max: 13,
|
|
1056
|
+
hint: '@2/',
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
id: 'book.30',
|
|
1060
|
+
src: 'book1',
|
|
1061
|
+
game: 'traxloop',
|
|
1062
|
+
notation:
|
|
1063
|
+
'@0/ @1/ @1/ D1+ A2+ E1+ E2+ E0+ F1\\ G1+ G2\\ F3+ E4\\ G3+ H2\\ C5\\ D5+ D6\\ D7\\',
|
|
1064
|
+
icon: '3efba:dcdeecaf:bdcdeeec:0afafd:0faec:2fd:2ac',
|
|
1065
|
+
level: 4,
|
|
1066
|
+
player: 2,
|
|
1067
|
+
max: 30,
|
|
1068
|
+
hints: ['H4/', 'G4\\'],
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
id: 'book.31',
|
|
1072
|
+
src: 'book1',
|
|
1073
|
+
game: 'traxloop',
|
|
1074
|
+
notation: '@0/ @1/ C1/ A2+ B3+ A3/ D3\\ B4\\ D4+ C5+ E2\\ E3+',
|
|
1075
|
+
icon: 'cdcaf:ecdfa:decae:afde:1bd',
|
|
1076
|
+
level: 3,
|
|
1077
|
+
player: 1,
|
|
1078
|
+
max: 23,
|
|
1079
|
+
hints: ['E0/', '@4/'],
|
|
1080
|
+
},
|
|
1081
|
+
{
|
|
1082
|
+
id: 'book.32',
|
|
1083
|
+
src: 'book1',
|
|
1084
|
+
game: 'traxloop',
|
|
1085
|
+
notation: '@0/ @1+ C1/ B2+ A2/ B3+ D2/ E2\\ D3+ F2+ G2\\ G3\\ C4\\ C5+ A4+',
|
|
1086
|
+
icon: 'bdc:cbdcaef:fbaeefa:bafd:afb',
|
|
1087
|
+
level: 3,
|
|
1088
|
+
player: 2,
|
|
1089
|
+
max: 24,
|
|
1090
|
+
hint: 'G4+',
|
|
1091
|
+
},
|
|
1092
|
+
{
|
|
1093
|
+
id: 'book.35',
|
|
1094
|
+
src: 'book1',
|
|
1095
|
+
game: 'traxloop',
|
|
1096
|
+
notation: '@0+ B1+ A0+ @2+ @2+ @2+ A1+ @1/ E3/',
|
|
1097
|
+
icon: 'de1e:aeeeee:2fd',
|
|
1098
|
+
level: 4,
|
|
1099
|
+
player: 2,
|
|
1100
|
+
max: 20,
|
|
1101
|
+
hint: 'E4\\',
|
|
1102
|
+
},
|
|
1103
|
+
{
|
|
1104
|
+
id: 'book.38',
|
|
1105
|
+
src: 'book1',
|
|
1106
|
+
game: 'traxloop',
|
|
1107
|
+
notation: '@0/ B1/ C1/ D1/ A2+ E1+ D3\\ E3+ F3+ F4\\ G4\\ F5\\ G5\\',
|
|
1108
|
+
icon: 'dcdcb:bdcdca:0afaeef:4fa:4af',
|
|
1109
|
+
level: 3,
|
|
1110
|
+
player: 2,
|
|
1111
|
+
max: 22,
|
|
1112
|
+
hint: 'B4+',
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
id: 'book.40',
|
|
1116
|
+
src: 'book1',
|
|
1117
|
+
game: 'traxloop',
|
|
1118
|
+
notation: '@0/ @1/ A2+ @2/ @2/ B1+ B3\\',
|
|
1119
|
+
icon: 'decd:cdec:fa',
|
|
1120
|
+
level: 3,
|
|
1121
|
+
player: 2,
|
|
1122
|
+
max: 14,
|
|
1123
|
+
hint: 'E2+',
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
id: 'book.42',
|
|
1127
|
+
src: 'book1',
|
|
1128
|
+
game: 'traxloop',
|
|
1129
|
+
notation: '@0/ B1+ @1+ A2/ A3+ B0+ D2+ E2\\ D1/',
|
|
1130
|
+
icon: 'deeca:bdeef:c:d',
|
|
1131
|
+
level: 2,
|
|
1132
|
+
player: 2,
|
|
1133
|
+
max: 16,
|
|
1134
|
+
hint: 'F1/',
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
id: 'book.43',
|
|
1138
|
+
src: 'book1',
|
|
1139
|
+
game: 'traxloop',
|
|
1140
|
+
notation:
|
|
1141
|
+
'@0/ A0/ A0/ A0+ @1\\ C1+ D1/ E1+ F1/ D0+ B1+ A0/ @1+ @1/ D0+ D0\\',
|
|
1142
|
+
icon: '2f:2ba:decaf:afdecb:1aeecbd:1fd:1ac:1fd',
|
|
1143
|
+
level: 3,
|
|
1144
|
+
player: 1,
|
|
1145
|
+
max: 27,
|
|
1146
|
+
hints: ['E6\\', 'F6/'],
|
|
1147
|
+
},
|
|
1148
|
+
{
|
|
1149
|
+
id: 'book.44',
|
|
1150
|
+
src: 'book1',
|
|
1151
|
+
game: 'traxloop',
|
|
1152
|
+
notation: '@0/ @1/ C1/ D1+ B2/ D3\\ E1\\ D0/ F2+ E4+ F3\\ F4\\',
|
|
1153
|
+
icon: '2dfa:cdcbae:0cdcdf:0fafba',
|
|
1154
|
+
level: 3,
|
|
1155
|
+
player: 1,
|
|
1156
|
+
max: 19,
|
|
1157
|
+
hint: 'D5+',
|
|
1158
|
+
},
|
|
1159
|
+
{
|
|
1160
|
+
id: 'book.45',
|
|
1161
|
+
src: 'book1',
|
|
1162
|
+
game: 'traxloop',
|
|
1163
|
+
notation: 'A0+ B1+ C1/ C0/ B0/ D2\\ A4\\ A5\\ C4+ @3+',
|
|
1164
|
+
icon: '1d:1cdf:eeec:0fde:0acd',
|
|
1165
|
+
level: 3,
|
|
1166
|
+
player: 1,
|
|
1167
|
+
max: 17,
|
|
1168
|
+
hint: 'E1/',
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
id: 'book.47',
|
|
1172
|
+
src: 'book1',
|
|
1173
|
+
game: 'traxloop',
|
|
1174
|
+
notation: '@0/ A0/ @2+ B0+ B0+ @3\\ @3\\ @3\\ A2/',
|
|
1175
|
+
icon: '3b:c1cb:fafdc:2bd',
|
|
1176
|
+
level: 2,
|
|
1177
|
+
player: 2,
|
|
1178
|
+
max: 14,
|
|
1179
|
+
hint: 'A1\\',
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
id: 'book.49',
|
|
1183
|
+
src: 'book1',
|
|
1184
|
+
game: 'traxloop',
|
|
1185
|
+
notation: '@0/ @1/ @1/ @1/ D0+ @2+ @2/ @2/ @2/ F3\\ G3\\',
|
|
1186
|
+
icon: '2cdcde:dcdecdcd:3faf',
|
|
1187
|
+
level: 3,
|
|
1188
|
+
player: 2,
|
|
1189
|
+
max: 16,
|
|
1190
|
+
hints: ['A1+', 'A3+'],
|
|
1191
|
+
},
|
|
1192
|
+
{
|
|
1193
|
+
id: 'book.50',
|
|
1194
|
+
src: 'book1',
|
|
1195
|
+
game: 'traxloop',
|
|
1196
|
+
notation: '@0/ @1/ @1/ @1+ @1/ @1/ A0/ A3/ D3+',
|
|
1197
|
+
icon: 'c:dcbdcd:cdcbdc',
|
|
1198
|
+
level: 3,
|
|
1199
|
+
player: 2,
|
|
1200
|
+
max: 16,
|
|
1201
|
+
hint: 'D1+',
|
|
1202
|
+
},
|
|
934
1203
|
]
|
|
935
1204
|
|
|
936
1205
|
puzzles.sort(
|
package/src/threats.js
CHANGED
|
@@ -21,7 +21,7 @@ const REGEX = {
|
|
|
21
21
|
'?': 'b?', // Zero or one black piece
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export
|
|
24
|
+
export class Threat {
|
|
25
25
|
#depth = 0
|
|
26
26
|
#pattern = ''
|
|
27
27
|
#rx
|
|
@@ -72,7 +72,7 @@ export const Threat = class {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
export
|
|
75
|
+
export class ThreatsDB {
|
|
76
76
|
#db = {}
|
|
77
77
|
#depth = 0
|
|
78
78
|
|
|
@@ -83,7 +83,7 @@ export const ThreatsDB = class {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
compose(depth, pattern) {
|
|
86
|
-
pattern = pattern.
|
|
86
|
+
pattern = pattern.replace(/(?<=.)A/g, 'a').replace(/W(?=.)/g, 'w')
|
|
87
87
|
return this.add(depth, pattern)
|
|
88
88
|
}
|
|
89
89
|
|
package/src/types.d.ts
CHANGED
|
@@ -71,7 +71,7 @@ type FaultyThreats = {
|
|
|
71
71
|
|
|
72
72
|
/** An object with keys representing each level, with arrays for each threat of
|
|
73
73
|
* that level. */
|
|
74
|
-
type
|
|
74
|
+
type ColorThreats = Record<string, FoundThreat[]>
|
|
75
75
|
|
|
76
76
|
/** All the threats found for both the white and black player. */
|
|
77
77
|
type FoundThreatsCollection = { b: ColorThreats; w: ColorThreats }
|
|
@@ -116,8 +116,10 @@ type Puzzle = {
|
|
|
116
116
|
|
|
117
117
|
type PuzzleSource = {
|
|
118
118
|
name: string
|
|
119
|
-
copyright?: string
|
|
120
119
|
url?: string
|
|
120
|
+
copyright?: string
|
|
121
|
+
license?: string
|
|
122
|
+
licenseUrl?: string
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
/** Array of objects representing the spaces surrounding the perimeter of a
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Shared utilities
|
|
2
|
+
* @copyright 2023
|
|
3
|
+
* @author Chad Transtrum <chad@transtrum.net>
|
|
4
|
+
* @license Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Convert milliseconds to a human readable string in ms, sec, or min units.
|
|
8
|
+
* @arg {number} ms - the number of milliseconds
|
|
9
|
+
* @returns {string} a short human-readable representation of the time
|
|
10
|
+
*/
|
|
11
|
+
export const timeString = (ms) => {
|
|
12
|
+
if (ms < 1000) {
|
|
13
|
+
return (ms === Math.floor(ms) ? String(ms) : ms.toFixed(2)) + ' ms'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
ms /= 1000
|
|
17
|
+
if (ms < 60) return ms.toFixed(3) + ' sec'
|
|
18
|
+
|
|
19
|
+
const min = Math.floor(ms / 60)
|
|
20
|
+
ms -= min * 60
|
|
21
|
+
return String(min) + ' min ' + ms.toFixed(3) + ' sec'
|
|
22
|
+
}
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by genversion.
|
|
2
|
-
export const version = '0.
|
|
2
|
+
export const version = '0.20.0'
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"0.17.0": {
|
|
3
|
-
"puzzles.firstmove.tutorial": {
|
|
4
|
-
"count": 16,
|
|
5
|
-
"ms": 2007
|
|
6
|
-
},
|
|
7
|
-
"puzzles.firstmove.beginner": {
|
|
8
|
-
"count": 16,
|
|
9
|
-
"ms": 2664
|
|
10
|
-
},
|
|
11
|
-
"puzzles.firstmove.tough": {
|
|
12
|
-
"count": 1,
|
|
13
|
-
"ms": 76
|
|
14
|
-
},
|
|
15
|
-
"puzzles.firstmove.total": {
|
|
16
|
-
"count": 33,
|
|
17
|
-
"ms": 4838
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"0.18.0": {
|
|
21
|
-
"puzzles.firstmove.tutorial": {
|
|
22
|
-
"count": 16,
|
|
23
|
-
"ms": 10164
|
|
24
|
-
},
|
|
25
|
-
"puzzles.firstmove.beginner": {
|
|
26
|
-
"count": 15,
|
|
27
|
-
"ms": 17864
|
|
28
|
-
},
|
|
29
|
-
"puzzles.firstmove.tough": {
|
|
30
|
-
"count": 26,
|
|
31
|
-
"ms": 30349
|
|
32
|
-
},
|
|
33
|
-
"puzzles.firstmove.advanced": {
|
|
34
|
-
"count": 1,
|
|
35
|
-
"ms": 427
|
|
36
|
-
},
|
|
37
|
-
"puzzles.firstmove.total": {
|
|
38
|
-
"count": 58,
|
|
39
|
-
"ms": 59031
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/** Let's try to solve the puzzles
|
|
4
|
-
* @copyright 2023
|
|
5
|
-
* @author Chad Transtrum <chad@transtrum.net>
|
|
6
|
-
* @license Apache-2.0
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import fs from 'node:fs'
|
|
10
|
-
import process from 'node:process'
|
|
11
|
-
import { fileURLToPath } from 'node:url'
|
|
12
|
-
|
|
13
|
-
import { Trax } from '@slugbugblue/trax'
|
|
14
|
-
import { suggest } from '@slugbugblue/trax/analyst'
|
|
15
|
-
import { puzzles } from '@slugbugblue/trax/puzzles'
|
|
16
|
-
import { version } from '@slugbugblue/trax/version'
|
|
17
|
-
|
|
18
|
-
import * as tty from '@slugbugblue/trax/tty'
|
|
19
|
-
|
|
20
|
-
let STUMPED = false
|
|
21
|
-
const FINAL = {}
|
|
22
|
-
|
|
23
|
-
const record = (name, data) => {
|
|
24
|
-
FINAL['puzzles.firstmove.' + name] = data
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const timeString = (ms) => {
|
|
28
|
-
if (ms < 1000) {
|
|
29
|
-
return (ms === Math.floor(ms) ? String(ms) : ms.toFixed(2)) + ' ms'
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
ms /= 1000
|
|
33
|
-
if (ms < 60) return ms.toFixed(3) + ' sec'
|
|
34
|
-
|
|
35
|
-
const min = Math.floor(ms / 60)
|
|
36
|
-
ms -= min * 60
|
|
37
|
-
return String(min) + ' min ' + ms.toFixed(3) + ' sec'
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const time = (ms) => timeString(Date.now() - ms)
|
|
41
|
-
|
|
42
|
-
const out = (messages) => {
|
|
43
|
-
tty.outty(messages.join(' '))
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const section = (title) => {
|
|
47
|
-
out([tty.color('===', 250), tty.color(title, 39), tty.color('===', 250)])
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const success = (puzzle, ms) => {
|
|
51
|
-
out([tty.color('✓', 35), tty.color(puzzle, 250), tty.color(ms, 240)])
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const puzzled = (puzzle, move) => {
|
|
55
|
-
tty.display(new Trax(puzzle.game, puzzle.notation))
|
|
56
|
-
tty.outty('Puzzled by ' + puzzle.id, 205)
|
|
57
|
-
out([
|
|
58
|
-
tty.color(' played', 250),
|
|
59
|
-
tty.color(move, 127),
|
|
60
|
-
tty.color('instead of', 250),
|
|
61
|
-
tty.color(puzzle.hint || puzzle.hints.join(' '), 39),
|
|
62
|
-
])
|
|
63
|
-
STUMPED = true
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const finalize = (name, ms, count) => {
|
|
67
|
-
out([
|
|
68
|
-
tty.color('Finished', 195),
|
|
69
|
-
tty.color(String(count) + ' ' + name + ' puzzles', 123),
|
|
70
|
-
tty.color('in ' + time(ms), 195),
|
|
71
|
-
])
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const solvePuzzleLevel = (name, level) => {
|
|
75
|
-
if (STUMPED) return 0
|
|
76
|
-
section(name + ' puzzles')
|
|
77
|
-
name = name.toLowerCase()
|
|
78
|
-
let count = 0
|
|
79
|
-
const begin = Date.now()
|
|
80
|
-
for (const puzzle of puzzles) {
|
|
81
|
-
if (puzzle.level !== level) continue
|
|
82
|
-
const start = Date.now()
|
|
83
|
-
const game = new Trax(puzzle.game, puzzle.notation)
|
|
84
|
-
const { move } = suggest(game).pick
|
|
85
|
-
const solved = puzzle.hint
|
|
86
|
-
? move === puzzle.hint
|
|
87
|
-
: puzzle.hints
|
|
88
|
-
? puzzle.hints.includes(move)
|
|
89
|
-
: false
|
|
90
|
-
if (solved) {
|
|
91
|
-
success(puzzle.id, time(start))
|
|
92
|
-
count++
|
|
93
|
-
record(name, { count, ms: Date.now() - begin })
|
|
94
|
-
} else {
|
|
95
|
-
puzzled(puzzle, move)
|
|
96
|
-
process.exitCode = level + 1
|
|
97
|
-
return count
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
finalize(name, begin, count)
|
|
102
|
-
return count
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Gather the benchmark data
|
|
106
|
-
let total = 0
|
|
107
|
-
const timer = Date.now()
|
|
108
|
-
total += solvePuzzleLevel('Tutorial', 0)
|
|
109
|
-
total += solvePuzzleLevel('Beginner', 1)
|
|
110
|
-
total += solvePuzzleLevel('Tough', 2)
|
|
111
|
-
total += solvePuzzleLevel('Advanced', 3)
|
|
112
|
-
total += solvePuzzleLevel('Genius', 4)
|
|
113
|
-
|
|
114
|
-
record('total', { count: total, ms: Date.now() - timer })
|
|
115
|
-
tty.outty(
|
|
116
|
-
'Benchmark: ' + total + ' puzzles in ' + time(timer),
|
|
117
|
-
STUMPED ? 205 : 35,
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
tty.outty()
|
|
121
|
-
|
|
122
|
-
// Load the previous benchmarks
|
|
123
|
-
const FILE = new URL('benchmarks.json', import.meta.url)
|
|
124
|
-
let CONTENTS
|
|
125
|
-
try {
|
|
126
|
-
CONTENTS = fs.readFileSync(FILE, 'utf8')
|
|
127
|
-
} catch {
|
|
128
|
-
CONTENTS = '{}'
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const BENCHMARK = JSON.parse(CONTENTS)
|
|
132
|
-
|
|
133
|
-
const PREV = Object.values(BENCHMARK).pop() || {}
|
|
134
|
-
|
|
135
|
-
// Report the benchmark data
|
|
136
|
-
const size = [0, 0, 0, 0, 0]
|
|
137
|
-
const REPORT = []
|
|
138
|
-
for (const name of Object.keys(FINAL)) {
|
|
139
|
-
const report = [
|
|
140
|
-
name,
|
|
141
|
-
String(FINAL[name].count) + ' puzzles',
|
|
142
|
-
'in ' + timeString(FINAL[name].ms),
|
|
143
|
-
'avg ' + timeString(FINAL[name].ms / FINAL[name].count),
|
|
144
|
-
]
|
|
145
|
-
if (PREV[name]) {
|
|
146
|
-
const diff =
|
|
147
|
-
FINAL[name].ms / FINAL[name].count - PREV[name].ms / PREV[name].count
|
|
148
|
-
if (diff <= 0) {
|
|
149
|
-
report.push('-' + timeString(Math.abs(diff)), '35')
|
|
150
|
-
} else {
|
|
151
|
-
report.push('+' + timeString(diff), '208')
|
|
152
|
-
}
|
|
153
|
-
} else {
|
|
154
|
-
report.push('new', '250')
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
size[0] = Math.max(size[0], report[0].length)
|
|
158
|
-
size[1] = Math.max(size[1], report[1].length)
|
|
159
|
-
size[2] = Math.max(size[2], report[2].length)
|
|
160
|
-
size[3] = Math.max(size[3], report[3].length)
|
|
161
|
-
size[4] = Math.max(size[4], report[4].length)
|
|
162
|
-
REPORT.push(report)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
for (const line of REPORT) {
|
|
166
|
-
out([
|
|
167
|
-
tty.color(line[0].padEnd(size[0]), 39),
|
|
168
|
-
tty.color(line[1].padStart(size[1]), 123),
|
|
169
|
-
tty.color(line[2].padStart(size[2]), 195),
|
|
170
|
-
tty.color(line[3].padEnd(size[3]), 245),
|
|
171
|
-
tty.color(line[4].padStart(size[4]), Number(line[5])),
|
|
172
|
-
])
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
tty.outty()
|
|
176
|
-
|
|
177
|
-
// Save the benchmark data to a file
|
|
178
|
-
if (BENCHMARK[version]) {
|
|
179
|
-
tty.outty('Not overwriting existing benchmark data')
|
|
180
|
-
} else {
|
|
181
|
-
BENCHMARK[version] = FINAL
|
|
182
|
-
try {
|
|
183
|
-
fs.writeFileSync(FILE, JSON.stringify(BENCHMARK, null, 2), 'utf8')
|
|
184
|
-
const filename = fileURLToPath(FILE)
|
|
185
|
-
tty.outty('Saved benchmark data to ' + tty.color(filename, 74))
|
|
186
|
-
} catch (error) {
|
|
187
|
-
tty.outty('Error saving benchmark data', 205)
|
|
188
|
-
tty.outty(String(error.code) + ' ' + error.message, 127)
|
|
189
|
-
}
|
|
190
|
-
}
|