@xom11/whiteboard 0.31.0 → 0.32.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/dist/ai.d.mts +148 -27
- package/dist/ai.d.ts +148 -27
- package/dist/ai.js +2652 -430
- package/dist/ai.js.map +1 -1
- package/dist/ai.mjs +2482 -426
- package/dist/ai.mjs.map +1 -1
- package/dist/catalog.json +4 -4
- package/dist/{chunk-JJ4FPCBE.mjs → chunk-4EXRNZRX.mjs} +641 -75
- package/dist/chunk-4EXRNZRX.mjs.map +1 -0
- package/dist/{chunk-BMYC2ILT.mjs → chunk-DBG6DZ2V.mjs} +4 -4
- package/dist/{chunk-BMYC2ILT.mjs.map → chunk-DBG6DZ2V.mjs.map} +1 -1
- package/dist/{chunk-T3N4BSJV.mjs → chunk-DCFZTKFE.mjs} +13 -5
- package/dist/chunk-DCFZTKFE.mjs.map +1 -0
- package/dist/{chunk-DWIEVCGK.mjs → chunk-DJM5EWTT.mjs} +53 -3
- package/dist/chunk-DJM5EWTT.mjs.map +1 -0
- package/dist/{chunk-44JY2AKC.mjs → chunk-F42MZ745.mjs} +3 -3
- package/dist/{chunk-44JY2AKC.mjs.map → chunk-F42MZ745.mjs.map} +1 -1
- package/dist/{chunk-TMRFSOM7.mjs → chunk-FI2MGX5H.mjs} +4 -4
- package/dist/{chunk-TMRFSOM7.mjs.map → chunk-FI2MGX5H.mjs.map} +1 -1
- package/dist/{chunk-KOXOC2FI.mjs → chunk-KLGPAMJS.mjs} +10 -7
- package/dist/chunk-KLGPAMJS.mjs.map +1 -0
- package/dist/{chunk-73Q7ADVL.mjs → chunk-MNHOFOM5.mjs} +2 -2
- package/dist/{chunk-73Q7ADVL.mjs.map → chunk-MNHOFOM5.mjs.map} +1 -1
- package/dist/{chunk-K5BS2H56.mjs → chunk-MOAXHVKI.mjs} +4 -4
- package/dist/{chunk-K5BS2H56.mjs.map → chunk-MOAXHVKI.mjs.map} +1 -1
- package/dist/chunk-SUXIMLYN.mjs +238 -0
- package/dist/chunk-SUXIMLYN.mjs.map +1 -0
- package/dist/{chunk-LTLLQUMN.mjs → chunk-XSTGTEGP.mjs} +4 -4
- package/dist/{chunk-LTLLQUMN.mjs.map → chunk-XSTGTEGP.mjs.map} +1 -1
- package/dist/geometry-2d.js +883 -119
- package/dist/geometry-2d.js.map +1 -1
- package/dist/geometry-2d.mjs +4 -4
- package/dist/geometry-3d.js +3995 -471
- package/dist/geometry-3d.js.map +1 -1
- package/dist/geometry-3d.mjs +4 -4
- package/dist/graph-2d.js +706 -111
- package/dist/graph-2d.js.map +1 -1
- package/dist/graph-2d.mjs +4 -4
- package/dist/{handleExtractProblem-BrDY9ifM.d.ts → handleExtractProblem-mtJyhEw3.d.mts} +2 -0
- package/dist/{handleExtractProblem-BrDY9ifM.d.mts → handleExtractProblem-mtJyhEw3.d.ts} +2 -0
- package/dist/{host-KMWP7KBT.mjs → host-JNLJ5BVR.mjs} +8 -8
- package/dist/{host-KMWP7KBT.mjs.map → host-JNLJ5BVR.mjs.map} +1 -1
- package/dist/{host-4FIUNIDQ.mjs → host-VRWLQSMH.mjs} +7 -7
- package/dist/{host-4FIUNIDQ.mjs.map → host-VRWLQSMH.mjs.map} +1 -1
- package/dist/host-W4U6YCSX.mjs +4973 -0
- package/dist/host-W4U6YCSX.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4090 -392
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +27 -21
- package/dist/index.mjs.map +1 -1
- package/dist/serialize-7UZWQQ4I.mjs +9 -0
- package/dist/{serialize-PGHQZEPV.mjs.map → serialize-7UZWQQ4I.mjs.map} +1 -1
- package/package.json +4 -1
- package/dist/chunk-DWIEVCGK.mjs.map +0 -1
- package/dist/chunk-JJ4FPCBE.mjs.map +0 -1
- package/dist/chunk-KOXOC2FI.mjs.map +0 -1
- package/dist/chunk-QK6OVDLC.mjs +0 -103
- package/dist/chunk-QK6OVDLC.mjs.map +0 -1
- package/dist/chunk-T3N4BSJV.mjs.map +0 -1
- package/dist/host-4ZB4XD4S.mjs +0 -2378
- package/dist/host-4ZB4XD4S.mjs.map +0 -1
- package/dist/serialize-PGHQZEPV.mjs +0 -9
package/dist/ai.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { KIND_REGISTRY, POINT_KINDS, LINE_LIKE_SHAPE_KINDS, CIRCLE_KINDS } from './chunk-
|
|
2
|
-
export { fileToImagePart, inferMediaType, validateFile } from './chunk-
|
|
3
|
-
export { extractProblemFromImage, handleExtractProblem, runTesseractOcr } from './chunk-
|
|
4
|
-
import { createEmptyState } from './chunk-
|
|
1
|
+
import { KIND_REGISTRY, POINT_KINDS, LINE_LIKE_SHAPE_KINDS, CIRCLE_KINDS } from './chunk-DJM5EWTT.mjs';
|
|
2
|
+
export { fileToImagePart, inferMediaType, validateFile } from './chunk-DJM5EWTT.mjs';
|
|
3
|
+
export { extractProblemFromImage, handleExtractProblem, runTesseractOcr } from './chunk-SUXIMLYN.mjs';
|
|
4
|
+
import { createEmptyState } from './chunk-MNHOFOM5.mjs';
|
|
5
5
|
import './chunk-B4NJJZFR.mjs';
|
|
6
6
|
import './chunk-J5LGTIGS.mjs';
|
|
7
7
|
import { z } from 'zod';
|
|
@@ -19,6 +19,11 @@ var GEOMETRY_KEYWORDS = [
|
|
|
19
19
|
"h\xECnh thoi",
|
|
20
20
|
"h\xECnh thang",
|
|
21
21
|
"t\u1EE9 gi\xE1c",
|
|
22
|
+
// Đa giác ≥5 đỉnh: "Cho lục giác ABCDEF …" — thiếu các từ này thì clause chỉ
|
|
23
|
+
// có tên đa giác (không "ngoại/nội tiếp") bị coi văn xuôi → hexagonRule miss.
|
|
24
|
+
"ng\u0169 gi\xE1c",
|
|
25
|
+
"l\u1EE5c gi\xE1c",
|
|
26
|
+
"\u0111a gi\xE1c",
|
|
22
27
|
// Triangle variants
|
|
23
28
|
"vu\xF4ng t\u1EA1i",
|
|
24
29
|
"c\xE2n t\u1EA1i",
|
|
@@ -100,6 +105,13 @@ var GEOMETRY_KEYWORDS = [
|
|
|
100
105
|
"c\u1EAFt",
|
|
101
106
|
"\u0111\u01B0\u1EDDng th\u1EB3ng",
|
|
102
107
|
"\u0111\u01B0\u01A1ng th\u1EB3ng",
|
|
108
|
+
// "thẳng hàng" — "Cho 3 điểm A, B, C thẳng hàng theo thứ tự" (C41): clause GIỚI
|
|
109
|
+
// THIỆU bộ điểm thẳng hàng (collinearPointsRule dựng A,B,C) NHƯNG không có keyword
|
|
110
|
+
// nào khác → hasGeometry=false → clause bị lọc khỏi rule engine → A,B,C KHÔNG dựng
|
|
111
|
+
// → cascade làm sụp tiếp tuyến/trung điểm phụ thuộc chúng. "thẳng hàng" (≥8 ký tự,
|
|
112
|
+
// phân biệt) đánh dấu geo-clause. Clause CHỨNG MINH ("Chứng minh A,B,C thẳng hàng")
|
|
113
|
+
// vẫn non-geo nhờ guard proofOnly (startsProofSection) — không bị ảnh hưởng.
|
|
114
|
+
"th\u1EB3ng h\xE0ng",
|
|
103
115
|
// Ký hiệu (symbol-only phrasing tương đương từ chữ): "⊥" ≡ "vuông góc".
|
|
104
116
|
// perpFoot rule đã nhận "⊥" ở tầng construct (PERP_DRAW), nhưng gate
|
|
105
117
|
// hasGeometry trước đây CHỈ nhận chữ "vuông góc" → clause mà "⊥" là tín hiệu
|
|
@@ -215,17 +227,18 @@ function countGeometryKeywords(text) {
|
|
|
215
227
|
var MASK_SEMI = "";
|
|
216
228
|
var MASK_DOT = "";
|
|
217
229
|
var MASK_COMMA = "";
|
|
230
|
+
var MASK_SEMI_LIST = "";
|
|
218
231
|
function maskNonBoundaryPunct(s) {
|
|
219
232
|
return s.replace(
|
|
220
233
|
/\(([^()\n]{1,40})\)/g,
|
|
221
234
|
(m) => m.replace(/;/g, MASK_SEMI).replace(/\./g, MASK_DOT).replace(/,/g, MASK_COMMA)
|
|
222
235
|
).replace(
|
|
223
|
-
/;(?=\s*[A-Z][A-Z]?['′]?\d?\s*(?:[;,]|cắt nhau|đồng quy))/gu,
|
|
224
|
-
|
|
225
|
-
);
|
|
236
|
+
/;(?=\s*[A-Z][A-Z]?['′]?\d?\s*(?:[;,]|lần lượt|cắt nhau|đồng quy|đôi một))/gu,
|
|
237
|
+
MASK_SEMI_LIST
|
|
238
|
+
).replace(/;(?=\s*[A-Z][A-Z]?['′]?\d?\s*(?:[;,.]|$))/gu, MASK_SEMI_LIST);
|
|
226
239
|
}
|
|
227
240
|
function unmask(s) {
|
|
228
|
-
return s.replace(/\u0001/g, ";").replace(/\u0002/g, ".").replace(/\u0003/g, ",");
|
|
241
|
+
return s.replace(/\u0001/g, ";").replace(/\u0002/g, ".").replace(/\u0003/g, ",").replace(/\u0004/g, ",");
|
|
229
242
|
}
|
|
230
243
|
function segmentClauses(problem) {
|
|
231
244
|
let proofMode = false;
|
|
@@ -234,7 +247,7 @@ function segmentClauses(problem) {
|
|
|
234
247
|
// giờ khớp trước space → split chết im lặng (bug class \b+tiếng Việt).
|
|
235
248
|
/[.;\n]+|,\s*(?=(?:Gọi|Vẽ|Kẻ|Cho|Lấy|Dựng|trên|với|Let|Draw|Mark|Take|Construct|Join)(?!\p{L}))/u
|
|
236
249
|
).map((s) => unmask(s).trim()).filter((s) => s.length > 0).map((text, id) => {
|
|
237
|
-
const hasGeometryKeyword = countGeometryKeywords(text) > 0;
|
|
250
|
+
const hasGeometryKeyword = countGeometryKeywords(text) > 0 || NAMED_LINE_PICK.test(text) || BARE_SEG_PICK.test(text) && !/tia\s+đối/u.test(text);
|
|
238
251
|
const proofOnly = isProofOnlyClause(text, proofMode);
|
|
239
252
|
if (startsProofSection(text)) proofMode = true;
|
|
240
253
|
const locusOnly = LOCUS_CLAUSE.test(text) && !CONSTRUCTION_LEAD.test(text);
|
|
@@ -243,6 +256,8 @@ function segmentClauses(problem) {
|
|
|
243
256
|
}
|
|
244
257
|
var ENUM_PREFIX = "(?:[0-9]+\\s*[.)]?\\s*|[a-zA-Z]\\s*[.)]\\s*)*";
|
|
245
258
|
var LOCUS_CLAUSE = /(?:di\s*chuyển|di\s*động)\s+trên\s+(?:\(|đường\s*tròn|đương\s*tròn|cung|nửa)/u;
|
|
259
|
+
var NAMED_LINE_PICK = /(?:[Tt]rên|[Tt]huộc)\s+(?:đường\s*thẳng\s+)?[a-z]{1,2}[0-9]?(?![\p{L}])\s+(?:[Ll]ấy|có)|[Ll]ấy\s+điểm\s+[A-Z]['′]?[0-9]?\s+(?:trên|thuộc)\s+(?:đường\s*thẳng\s+)?[a-z]{1,2}[0-9]?(?![\p{L}])/u;
|
|
260
|
+
var BARE_SEG_PICK = /[Ll]ấy\s+(?:điểm\s+)?[A-Z]['′]?(?:\s*,\s*[A-Z]['′]?)?\s+(?:bất\s*k[iìyỳ]\s+)?(?:trên|thuộc)\s+(?:cạnh\s+|đoạn\s+|tia\s+)?[A-Z]{2}(?![A-Z])|[Tt]rên\s+(?:cạnh\s+|đoạn\s+|tia\s+)?[A-Z]{2}(?:\s*,\s*[A-Z]{2})*\s+(?:(?:theo\s+)?thứ\s+tự\s+|lần\s*lượt\s+)?lấy\s+(?:các\s+)?(?:điểm\s+)?[A-Z]/u;
|
|
246
261
|
var PROOF_SECTION_START = new RegExp(
|
|
247
262
|
`^${ENUM_PREFIX}(?:[Cc]h\u1EE9ng\\s*minh|C/m|CMR|[Tt]\xEDnh|[Tt]\xECm|[Xx]\xE1c\\s*\u0111\u1ECBnh|[Hh]\xE3y\\s+x\xE1c\\s*\u0111\u1ECBnh)(?!\\p{L})`,
|
|
248
263
|
"u"
|
|
@@ -311,6 +326,9 @@ function pairFromToken(token) {
|
|
|
311
326
|
return m ? [m[1], m[2]] : [];
|
|
312
327
|
}
|
|
313
328
|
var SIDE_PREFIX = "(?:c\u1EA1nh(?:\\s+huy\u1EC1n)?\\s+|\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+|d\xE2y(?:\\s+cung)?\\s+)?";
|
|
329
|
+
function escapeRe(s) {
|
|
330
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
331
|
+
}
|
|
314
332
|
var DUONG_KW = "[\u0110\u0111]\u01B0[\u1EDD\u01A1]ng";
|
|
315
333
|
var CIRCLE_KW = DUONG_KW + "\\s*tr\xF2n";
|
|
316
334
|
|
|
@@ -479,44 +497,44 @@ var QUAD = "([A-Z])([A-Z])([A-Z])([A-Z])(?![A-Z])";
|
|
|
479
497
|
var KINDS = [
|
|
480
498
|
// hình thang cân / vuông (modifier TRƯỚC đỉnh) — phải match trước thang chung
|
|
481
499
|
{
|
|
482
|
-
name: new RegExp("
|
|
500
|
+
name: new RegExp("[Hh]\xECnh\\s+thang\\s+c\xE2n\\s+" + QUAD, "gu"),
|
|
483
501
|
shape: "trapezoid",
|
|
484
502
|
variant: "isoceles"
|
|
485
503
|
},
|
|
486
504
|
{
|
|
487
|
-
name: new RegExp("
|
|
505
|
+
name: new RegExp("[Hh]\xECnh\\s+thang\\s+vu\xF4ng\\s+" + QUAD, "gu"),
|
|
488
506
|
shape: "trapezoid",
|
|
489
507
|
variant: "right"
|
|
490
508
|
},
|
|
491
509
|
// hình thang chung — sau đó dò modifier 'vuông'/'cân' đứng SAU đỉnh
|
|
492
510
|
// ("hình thang ABCD vuông tại A" → right; "...cân" → isoceles).
|
|
493
511
|
{
|
|
494
|
-
name: new RegExp("
|
|
512
|
+
name: new RegExp("[Hh]\xECnh\\s+thang\\s+" + QUAD, "gu"),
|
|
495
513
|
shape: "trapezoid",
|
|
496
514
|
variant: "general"
|
|
497
515
|
},
|
|
498
516
|
{
|
|
499
|
-
name: new RegExp("
|
|
517
|
+
name: new RegExp("[Hh]\xECnh\\s+vu\xF4ng\\s+" + QUAD, "gu"),
|
|
500
518
|
shape: "square",
|
|
501
519
|
variant: "standard"
|
|
502
520
|
},
|
|
503
521
|
{
|
|
504
|
-
name: new RegExp("
|
|
522
|
+
name: new RegExp("[Hh]\xECnh\\s+ch\u1EEF\\s+nh\u1EADt\\s+" + QUAD, "gu"),
|
|
505
523
|
shape: "rectangle",
|
|
506
524
|
variant: "wide"
|
|
507
525
|
},
|
|
508
526
|
{
|
|
509
|
-
name: new RegExp("
|
|
527
|
+
name: new RegExp("[Hh]\xECnh\\s+b\xECnh\\s+h\xE0nh\\s+" + QUAD, "gu"),
|
|
510
528
|
shape: "parallelogram",
|
|
511
529
|
variant: "standard"
|
|
512
530
|
},
|
|
513
531
|
{
|
|
514
|
-
name: new RegExp("
|
|
532
|
+
name: new RegExp("[Hh]\xECnh\\s+thoi\\s+" + QUAD, "gu"),
|
|
515
533
|
shape: "rhombus",
|
|
516
534
|
variant: "standard"
|
|
517
535
|
},
|
|
518
536
|
{
|
|
519
|
-
name: new RegExp("
|
|
537
|
+
name: new RegExp("[Tt]\u1EE9\\s+gi\xE1c(?:\\s+l\u1ED3i)?\\s+" + QUAD, "gu"),
|
|
520
538
|
shape: "quadrilateral",
|
|
521
539
|
variant: "any"
|
|
522
540
|
},
|
|
@@ -566,10 +584,10 @@ var KINDS = [
|
|
|
566
584
|
variant: "any"
|
|
567
585
|
}
|
|
568
586
|
];
|
|
569
|
-
var PREFILTER = /
|
|
587
|
+
var PREFILTER = /[Hh]ình\s+(?:vuông|chữ\s+nhật|bình\s+hành|thoi|thang)|[Tt]ứ\s+giác|[Ss]quare|[Rr]ectangle|[Pp]arallelogram|[Rr]hombus|[Tt]rapezoid|[Tt]rapezium|[Qq]uadrilateral/u;
|
|
570
588
|
var RIGHT_AFTER = /vuông(?!\p{L})/u;
|
|
571
589
|
var ISO_AFTER = /(?<!\p{L})cân(?!\p{L})/u;
|
|
572
|
-
var NEXT_SHAPE = /
|
|
590
|
+
var NEXT_SHAPE = /[Hh]ình\s|[Tt]ứ\s+giác/u;
|
|
573
591
|
function scanClause(text) {
|
|
574
592
|
const claimed = [];
|
|
575
593
|
const taken = [];
|
|
@@ -613,13 +631,17 @@ var CYCLIC_QUAD_THETAS = CYCLIC_QUAD_COORDS.map(
|
|
|
613
631
|
);
|
|
614
632
|
var CENTER = "(?:\\(\\s*([A-Z])\\s*\\)|t\xE2m\\s+([A-Z]))?";
|
|
615
633
|
var QUAD_INSCRIBED_IN_CIRCLE = new RegExp(
|
|
616
|
-
"^[\\s,]*(?:n\u1ED9i\\s*ti\u1EBFp\\s+(?:trong\\s+)?(?:m\u1ED9t\\s+)?\u0111\u01B0\u1EDDng\\s*tr\xF2n|(?:is\\s+|are\\s+)?inscribed\\s+in\\s+(?:a\\s+|the\\s+)?circle
|
|
634
|
+
"^[\\s,]*(?:n\u1ED9i\\s*ti\u1EBFp\\s+(?:trong\\s+)?(?:m\u1ED9t\\s+)?\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s*" + CENTER + "|n\u1ED9i\\s*ti\u1EBFp\\s+(?:trong\\s+)?\\(\\s*([A-Z])\\s*\\)|(?:is\\s+|are\\s+)?inscribed\\s+in\\s+(?:a\\s+|the\\s+)?circle\\s*" + CENTER + ")",
|
|
617
635
|
"iu"
|
|
618
636
|
);
|
|
619
637
|
var CIRCLE_CIRCUMSCRIBES_QUAD = new RegExp(
|
|
620
638
|
"(?:\u0111\u01B0\u1EDDng\\s*tr\xF2n|circle)\\s*" + CENTER + "\\s*(?:ngo\u1EA1i\\s*ti\u1EBFp|(?:is\\s+|are\\s+)?circumscrib(?:es|ed)(?:\\s+(?:about|around))?)\\s+$",
|
|
621
639
|
"iu"
|
|
622
640
|
);
|
|
641
|
+
var TUGIAC_NOITIEP = new RegExp(
|
|
642
|
+
"[Tt]\u1EE9\\s+gi\xE1c(?:\\s+l\u1ED3i)?\\s+n\u1ED9i\\s*ti\u1EBFp\\s+(?:(?:trong\\s+)?(?:m\u1ED9t\\s+)?\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s+)?(?:\\(\\s*([A-Z])\\s*\\)\\s+)?" + QUAD,
|
|
643
|
+
"gu"
|
|
644
|
+
);
|
|
623
645
|
var TRIANGLE_DECL = /(?:tam\s*giác|[Tt]riangle)\s+([A-Z])([A-Z])([A-Z])(?![A-Z])/gu;
|
|
624
646
|
function ownedByOthers(ctx, selfLabels) {
|
|
625
647
|
const owned = /* @__PURE__ */ new Set();
|
|
@@ -638,9 +660,26 @@ function ownedByOthers(ctx, selfLabels) {
|
|
|
638
660
|
}
|
|
639
661
|
return owned;
|
|
640
662
|
}
|
|
663
|
+
function conflictingOwners(ctx, quadLabels) {
|
|
664
|
+
const quadSet = new Set(quadLabels);
|
|
665
|
+
const owned = /* @__PURE__ */ new Set();
|
|
666
|
+
const addIfOutside = (verts) => {
|
|
667
|
+
if (verts.some((v) => !quadSet.has(v))) for (const v of verts) owned.add(v);
|
|
668
|
+
};
|
|
669
|
+
TRIANGLE_DECL.lastIndex = 0;
|
|
670
|
+
for (const m of ctx.problem.matchAll(TRIANGLE_DECL)) addIfOutside([m[1], m[2], m[3]]);
|
|
671
|
+
const selfKey = quadLabels.join("");
|
|
672
|
+
for (const c of ctx.clauses) {
|
|
673
|
+
for (const h of scanClause(c.text)) {
|
|
674
|
+
if (h.labels.join("") === selfKey) continue;
|
|
675
|
+
addIfOutside(h.labels);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return owned;
|
|
679
|
+
}
|
|
641
680
|
function detectCyclic(text, hit) {
|
|
642
681
|
const a = QUAD_INSCRIBED_IN_CIRCLE.exec(text.slice(hit.afterEnd));
|
|
643
|
-
if (a) return a[1] ?? a[2] ?? "";
|
|
682
|
+
if (a) return a[1] ?? a[2] ?? a[3] ?? a[4] ?? a[5] ?? "";
|
|
644
683
|
const b = CIRCLE_CIRCUMSCRIBES_QUAD.exec(text.slice(0, hit.index));
|
|
645
684
|
if (b) return b[1] ?? b[2] ?? "";
|
|
646
685
|
return void 0;
|
|
@@ -653,12 +692,30 @@ var quadRule = {
|
|
|
653
692
|
match(ctx) {
|
|
654
693
|
const out = [];
|
|
655
694
|
for (const c of ctx.clauses) {
|
|
695
|
+
TUGIAC_NOITIEP.lastIndex = 0;
|
|
696
|
+
for (const m of c.text.matchAll(TUGIAC_NOITIEP)) {
|
|
697
|
+
const labels = [m[2], m[3], m[4], m[5]];
|
|
698
|
+
const owned = ownedByOthers(ctx, labels);
|
|
699
|
+
if (labels.some((lbl) => owned.has(lbl))) continue;
|
|
700
|
+
const centerName = m[1] || "O";
|
|
701
|
+
out.push({
|
|
702
|
+
ruleId: "quad",
|
|
703
|
+
clauseIds: [c.id],
|
|
704
|
+
intents: [
|
|
705
|
+
drawCircle(centerName, "centerRadius", { center: centerName, radius: CYCLIC_RADIUS }),
|
|
706
|
+
...labels.map(
|
|
707
|
+
(lbl, i) => addPoint(lbl, { kind: "onCircle", circle: centerName, theta: CYCLIC_QUAD_THETAS[i] })
|
|
708
|
+
),
|
|
709
|
+
markShape("quadrilateral", labels)
|
|
710
|
+
]
|
|
711
|
+
});
|
|
712
|
+
}
|
|
656
713
|
for (const hit of scanClause(c.text)) {
|
|
657
714
|
let intents = [drawShape(hit.shape, hit.labels, hit.variant)];
|
|
658
715
|
if (hit.shape === "quadrilateral") {
|
|
659
716
|
const center = detectCyclic(c.text, hit);
|
|
660
717
|
if (center !== void 0) {
|
|
661
|
-
const owned =
|
|
718
|
+
const owned = conflictingOwners(ctx, hit.labels);
|
|
662
719
|
const shared = hit.labels.some((lbl) => owned.has(lbl));
|
|
663
720
|
if (!shared) {
|
|
664
721
|
const centerName = center || "O";
|
|
@@ -768,11 +825,11 @@ var NAME_AFTER_G = new RegExp(
|
|
|
768
825
|
"gu"
|
|
769
826
|
);
|
|
770
827
|
var DISTRIB = new RegExp(
|
|
771
|
-
`((?:[A-Z](?:['\u2032]?)\\s*(?:,|v\xE0)\\s*)+[A-Z](?:['\u2032]?))\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?(?:l\xE0\\s+)?(?:\u0111i\u1EC3m\\s+)?trung\\s*\u0111i\u1EC3m\\s+(?:c\u1EE7a\\s+)?(?:c\xE1c\\s+)?${SIDE_PREFIX}((?:[A-Z][A-Z]\\s*,\\s*)*(?:[A-Z][A-Z]\\s*,\\s*)?[A-Z][A-Z](?:\\s*v\xE0\\s*[A-Z][A-Z])?)`,
|
|
828
|
+
`((?:[A-Z](?:['\u2032]?)\\s*(?:,|v\xE0)\\s*)+[A-Z](?:['\u2032]?))\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|(?:theo\\s+)?th\u1EE9\\s+t\u1EF1\\s+)?(?:l\xE0\\s+)?(?:\u0111i\u1EC3m\\s+)?trung\\s*\u0111i\u1EC3m\\s+(?:c\u1EE7a\\s+)?(?:c\xE1c\\s+)?${SIDE_PREFIX}((?:[A-Z][A-Z]\\s*,\\s*)*(?:[A-Z][A-Z]\\s*,\\s*)?[A-Z][A-Z](?:\\s*v\xE0\\s*[A-Z][A-Z])?)`,
|
|
772
829
|
"u"
|
|
773
830
|
);
|
|
774
831
|
var DISTRIB_AFTER = new RegExp(
|
|
775
|
-
`trung\\s*\u0111i\u1EC3m\\s+(?:c\u1EE7a\\s+)?(?:c\xE1c\\s+)?${SIDE_PREFIX}((?:[A-Z][A-Z]\\s*(?:,|v\xE0)\\s*)+[A-Z][A-Z])(?![A-Z])\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?l\xE0\\s+((?:[A-Z](?:['\u2032]?)\\s*(?:,|v\xE0)\\s*)+[A-Z](?:['\u2032]?))(?!\\p{L})`,
|
|
832
|
+
`trung\\s*\u0111i\u1EC3m\\s+(?:c\u1EE7a\\s+)?(?:c\xE1c\\s+)?${SIDE_PREFIX}((?:[A-Z][A-Z]\\s*(?:,|v\xE0)\\s*)+[A-Z][A-Z])(?![A-Z])\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|(?:theo\\s+)?th\u1EE9\\s+t\u1EF1\\s+)?l\xE0\\s+((?:[A-Z](?:['\u2032]?)\\s*(?:,|v\xE0)\\s*)+[A-Z](?:['\u2032]?))(?!\\p{L})`,
|
|
776
833
|
"u"
|
|
777
834
|
);
|
|
778
835
|
function nameToken(raw) {
|
|
@@ -1138,8 +1195,8 @@ var PREFILTER4 = [
|
|
|
1138
1195
|
/[Bb]isector\s+of\s+(?:the\s+)?(?:[Aa]ngle|∠)/u
|
|
1139
1196
|
// Form A EN
|
|
1140
1197
|
];
|
|
1141
|
-
var ANGLE3 = /phân\s*giác\s+(?:trong\s+)?(?:(?:của|cho)\s+)?góc\s
|
|
1142
|
-
var ANGLE1 = /phân\s*giác\s+(?:trong\s+)?(?:(?:của|cho)\s+)?góc\s
|
|
1198
|
+
var ANGLE3 = /phân\s*giác\s+(?:trong\s+)?(?:(?:của|cho)\s+)?(?:góc\s+|∠\s*)([A-Z])([A-Z])([A-Z])(?![A-Z])/giu;
|
|
1199
|
+
var ANGLE1 = /phân\s*giác\s+(?:trong\s+)?(?:(?:của|cho)\s+)?(?:góc\s+|∠\s*)([A-Z])(?![A-Z])/giu;
|
|
1143
1200
|
var CHAN_BEFORE = /chân\s+(?:của\s+)?(?:đường\s*|tia\s+)?$/u;
|
|
1144
1201
|
var ANGLE3_EN_A = /[Bb]isector\s+of\s+(?:the\s+)?(?:[Aa]ngle|∠)\s*([A-Z])([A-Z])([A-Z])(?![A-Z])/gu;
|
|
1145
1202
|
var ANGLE3_EN_B = /[Aa]ngle\s+bisector\s+of\s+([A-Z])([A-Z])([A-Z])(?![A-Z])/gu;
|
|
@@ -1317,6 +1374,13 @@ function resolveTriangle(clauseText, kwIndex, uniqueProblemTri) {
|
|
|
1317
1374
|
}
|
|
1318
1375
|
return uniqueProblemTri;
|
|
1319
1376
|
}
|
|
1377
|
+
function dropDegenerate(intents) {
|
|
1378
|
+
return intents.filter((i) => {
|
|
1379
|
+
if (i.op !== "add-point") return true;
|
|
1380
|
+
const of = i.constraint.of;
|
|
1381
|
+
return !(Array.isArray(of) && of.includes(i.name));
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1320
1384
|
var centersRule = {
|
|
1321
1385
|
id: "centers",
|
|
1322
1386
|
priority: 70,
|
|
@@ -1349,7 +1413,8 @@ var centersRule = {
|
|
|
1349
1413
|
if (of) {
|
|
1350
1414
|
intents.push(addPoint(dc[1], { kind: centerTypeToKind(dc[3]), of }));
|
|
1351
1415
|
intents.push(addPoint(dc[2], { kind: centerTypeToKind(dc[4]), of }));
|
|
1352
|
-
|
|
1416
|
+
const kept2 = dropDegenerate(intents);
|
|
1417
|
+
if (kept2.length) out.push({ ruleId: "centers", clauseIds: [c.id], intents: kept2 });
|
|
1353
1418
|
continue;
|
|
1354
1419
|
}
|
|
1355
1420
|
}
|
|
@@ -1398,8 +1463,9 @@ var centersRule = {
|
|
|
1398
1463
|
const of = ofFor(c.text, INCENTER_KW_EN);
|
|
1399
1464
|
if (name && of) intents.push(addPoint(name, { kind: "incenter", of }));
|
|
1400
1465
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1466
|
+
const kept = dropDegenerate(intents);
|
|
1467
|
+
if (kept.length > 0) {
|
|
1468
|
+
out.push({ ruleId: "centers", clauseIds: [c.id], intents: kept });
|
|
1403
1469
|
}
|
|
1404
1470
|
}
|
|
1405
1471
|
return out;
|
|
@@ -1412,9 +1478,11 @@ var PREFILTER5 = new RegExp(
|
|
|
1412
1478
|
"u"
|
|
1413
1479
|
);
|
|
1414
1480
|
var PREFILTER_EN = /projection|perpendicular|foot\s+of/i;
|
|
1415
|
-
var
|
|
1481
|
+
var LINE_PREFIX = "(?:" + DUONG_KW + "\\s*th\u1EB3ng\\s+|ti\u1EBFp\\s*tuy\u1EBFn\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+)?";
|
|
1482
|
+
var LINE_TOK = "([A-Z]{1,2}(?![A-Za-z])|[A-Z]?[a-z][a-z]?[0-9]?(?![A-Za-z]))";
|
|
1483
|
+
var LINE = LINE_PREFIX + LINE_TOK;
|
|
1416
1484
|
var PREP = "(?:tr\xEAn|l\xEAn|xu\u1ED1ng|\u0111\u1EBFn|t\u1EDBi)";
|
|
1417
|
-
var LANLUOT_OPT = "(?:(?:l\u1EA7n\\s*l\u01B0\u1EE3t|theo\\s+th\u1EE9\\s+t\u1EF1|th\u1EE9\\s+t\u1EF1)\\s+)?";
|
|
1485
|
+
var LANLUOT_OPT = "(?:(?:l\u1EA7n\\s*l\u01B0\u1EE3t|theo\\s+th\u1EE9\\s+t\u1EF1|th\u1EE9\\s+t\u1EF1|t\u01B0\u01A1ng\\s+\u1EE9ng)\\s+)?";
|
|
1418
1486
|
var PROJ_CORE = `h\xECnh\\s*chi\u1EBFu\\s+(?:vu\xF4ng\\s*g\xF3c\\s+)?(?:c\u1EE7a\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z])\\s+${PREP}\\s+${LINE}`;
|
|
1419
1487
|
var FOOT_CORE = `ch\xE2n\\s+(?:c\u1EE7a\\s+)?${DUONG_KW}\\s+(?:vu\xF4ng\\s*g\xF3c|cao)\\s*(?:h\u1EA1\\s+|k\u1EBB\\s+|v\u1EBD\\s+|d\u1EF1ng\\s+)?(?:t\u1EEB\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z])\\s+${PREP}\\s+${LINE}`;
|
|
1420
1488
|
var LANLUOT = new RegExp(
|
|
@@ -1425,14 +1493,27 @@ var SINGLE = new RegExp(`(?:${PROJ_CORE})|(?:${FOOT_CORE})`, "gu");
|
|
|
1425
1493
|
var NAMES_BLOB = "((?:[A-Z](?:['\u2032]?)\\s*(?:,|v\xE0)\\s*)+[A-Z](?:['\u2032]?))";
|
|
1426
1494
|
var LINES_BLOB = "((?:[A-Z]{1,2}\\s*(?:,|v\xE0)\\s*)+[A-Z]{1,2})(?!\\p{L})";
|
|
1427
1495
|
var LINES_PREFIX = "(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?";
|
|
1496
|
+
var NO_HOA_BEFORE = "(?<![A-Z])";
|
|
1428
1497
|
var DISTRIB_PROJ = new RegExp(
|
|
1429
|
-
NAMES_BLOB + "\\s+" + LANLUOT_OPT + "(?:l\xE0\\s+)?(?:c\xE1c\\s+)?h\xECnh\\s*chi\u1EBFu\\s+(?:vu\xF4ng\\s*g\xF3c\\s+)?(?:c\u1EE7a\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})\\s+(?:tr\xEAn|l\xEAn|xu\u1ED1ng|\u0111\u1EBFn)\\s+" + LINES_PREFIX + LINES_BLOB,
|
|
1498
|
+
NO_HOA_BEFORE + NAMES_BLOB + "\\s+" + LANLUOT_OPT + "(?:l\xE0\\s+)?(?:c\xE1c\\s+)?h\xECnh\\s*chi\u1EBFu\\s+(?:vu\xF4ng\\s*g\xF3c\\s+)?(?:c\u1EE7a\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})\\s+(?:tr\xEAn|l\xEAn|xu\u1ED1ng|\u0111\u1EBFn)\\s+" + LINES_PREFIX + LINES_BLOB,
|
|
1430
1499
|
"gu"
|
|
1431
1500
|
);
|
|
1432
1501
|
var DISTRIB_FOOT = new RegExp(
|
|
1433
|
-
NAMES_BLOB + "\\s+" + LANLUOT_OPT + "(?:l\xE0\\s+)?(?:c\xE1c\\s+)?ch\xE2n\\s+(?:c\u1EE7a\\s+)?(?:c\xE1c\\s+)?\u0111\u01B0\u1EDDng\\s+(?:vu\xF4ng\\s*g\xF3c|cao)\\s*(?:h\u1EA1\\s+|k\u1EBB\\s+|v\u1EBD\\s+|d\u1EF1ng\\s+)?(?:t\u1EEB\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})\\s+(?:\u0111\u1EBFn|xu\u1ED1ng|tr\xEAn|t\u1EDBi)\\s+" + LINES_PREFIX + LINES_BLOB,
|
|
1502
|
+
NO_HOA_BEFORE + NAMES_BLOB + "\\s+" + LANLUOT_OPT + "(?:l\xE0\\s+)?(?:c\xE1c\\s+)?ch\xE2n\\s+(?:c\u1EE7a\\s+)?(?:c\xE1c\\s+)?\u0111\u01B0\u1EDDng\\s+(?:vu\xF4ng\\s*g\xF3c|cao)\\s*(?:h\u1EA1\\s+|k\u1EBB\\s+|v\u1EBD\\s+|d\u1EF1ng\\s+)?(?:t\u1EEB\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})\\s+(?:\u0111\u1EBFn|xu\u1ED1ng|tr\xEAn|t\u1EDBi)\\s+" + LINES_PREFIX + LINES_BLOB,
|
|
1503
|
+
"gu"
|
|
1504
|
+
);
|
|
1505
|
+
var NAMES_BLOB_IDX = "((?:[A-Z][0-9]?(?:['\u2032]?)\\s*(?:,|v\xE0)\\s*)+[A-Z][0-9]?(?:['\u2032]?))";
|
|
1506
|
+
var FROMS_BLOB_IDX = "((?:[A-Z][0-9]?(?:['\u2032]?)\\s*(?:,|v\xE0)\\s*)+[A-Z][0-9]?(?:['\u2032]?))(?!\\p{L})";
|
|
1507
|
+
var ZIP_FROM_PROJ = new RegExp(
|
|
1508
|
+
NO_HOA_BEFORE + NAMES_BLOB_IDX + "\\s+" + LANLUOT_OPT + "(?:l\xE0\\s+)?(?:c\xE1c\\s+)?h\xECnh\\s*chi\u1EBFu\\s+(?:vu\xF4ng\\s*g\xF3c\\s+)?(?:c\u1EE7a\\s+)?(?:\u0111i\u1EC3m\\s+)?" + FROMS_BLOB_IDX + "\\s+(?:tr\xEAn|l\xEAn|xu\u1ED1ng|\u0111\u1EBFn)\\s+" + LINES_PREFIX + LINES_BLOB,
|
|
1434
1509
|
"gu"
|
|
1435
1510
|
);
|
|
1511
|
+
function splitNamesIdx(blob) {
|
|
1512
|
+
return blob.split(/,|và/).map((s) => {
|
|
1513
|
+
const mm = /^([A-Z][0-9]?)(['′]?)/u.exec(s.trim());
|
|
1514
|
+
return mm ? mm[2] ? `${mm[1]}'` : mm[1] : "";
|
|
1515
|
+
}).filter(Boolean);
|
|
1516
|
+
}
|
|
1436
1517
|
var TWO_LINES_PREP = "(?:tr\xEAn|l\xEAn|xu\u1ED1ng|\u0111\u1EBFn|t\u1EDBi)";
|
|
1437
1518
|
var TWO_LINES_LINE = "(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?([A-Z]{1,2})(?![A-Z])";
|
|
1438
1519
|
var SHARED_FROM_TWO = new RegExp(
|
|
@@ -1440,21 +1521,44 @@ var SHARED_FROM_TWO = new RegExp(
|
|
|
1440
1521
|
"gu"
|
|
1441
1522
|
);
|
|
1442
1523
|
var FEET_FROM_SHARED_LINE = new RegExp(
|
|
1443
|
-
"(?:[Hh]ai\\s+\u0111i\u1EC3m\\s+|[Cc]\xE1c\\s+\u0111i\u1EC3m\\s+)?([A-Z])(?:['\u2032])?\\s*(?:,|v\xE0)\\s*([A-Z])(?:['\u2032])?\\s+
|
|
1524
|
+
"(?:[Hh]ai\\s+\u0111i\u1EC3m\\s+|[Cc]\xE1c\\s+\u0111i\u1EC3m\\s+)?([A-Z])(?:['\u2032])?\\s*(?:,|v\xE0)\\s*([A-Z])(?:['\u2032])?\\s+" + // "lần lượt"/"theo thứ tự"/"thứ tự" = phân phối 1-1, OPTIONAL (LANLUOT_OPT).
|
|
1525
|
+
LANLUOT_OPT + "l\xE0\\s+ch\xE2n\\s+(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s+)?(?:vu\xF4ng\\s*g\xF3c|cao)\\s+(?:k\u1EBB\\s+|h\u1EA1\\s+|v\u1EBD\\s+|d\u1EF1ng\\s+)?(?:t\u1EEB\\s+)?([A-Z](?:['\u2032])?)\\s*(?:,|v\xE0)\\s*([A-Z](?:['\u2032])?)\\s+(?:\u0111\u1EBFn|xu\u1ED1ng|tr\xEAn|t\u1EDBi)\\s+(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|\u0111\u01B0\u1EDDng\\s*k\xEDnh\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?([A-Z]{1,2}(?:['\u2032])?)(?![A-Z])",
|
|
1444
1526
|
"gu"
|
|
1445
1527
|
);
|
|
1528
|
+
var PERP_LINE_PREFIX = "(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|ti\u1EBFp\\s*tuy\u1EBFn\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?";
|
|
1529
|
+
var PERP_LINE_TOK = "([A-Z]{1,2}(?![A-Za-z])|[A-Z]?[a-z][a-z]?[0-9]?(?![A-Za-z]))";
|
|
1446
1530
|
var PERP_DRAW_DISTRIB = new RegExp(
|
|
1447
1531
|
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng|[Hh]\u1EA1)\\s+([A-Z])([A-Z])\\s*,\\s*([A-Z])([A-Z])(?![A-Z])\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:\u22A5|vu\xF4ng\\s*g\xF3c(?:\\s+v\u1EDBi)?)\\s+(?:v\u1EDBi\\s+)?(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?([A-Z]{2})\\s*,\\s*([A-Z]{2})(?![A-Z])",
|
|
1448
1532
|
"gu"
|
|
1449
1533
|
);
|
|
1534
|
+
var PAIRS_BLOB = "((?:[A-Z]{2}\\s*,\\s*)+[A-Z]{2})(?![A-Z])";
|
|
1535
|
+
var PERP_DRAW_ARE = new RegExp(
|
|
1536
|
+
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng|[Hh]\u1EA1)\\s+" + PAIRS_BLOB + "\\s+l\xE0\\s+(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|\u0111\u01B0\u1EDDng\\s+)?vu\xF4ng\\s*g\xF3c\\s+(?:t\u1EDBi|\u0111\u1EBFn|xu\u1ED1ng|v\u1EDBi)\\s+(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?((?:[A-Z]{2}\\s*(?:,|v\xE0)\\s*)+[A-Z]{2})(?![A-Z])",
|
|
1537
|
+
"gu"
|
|
1538
|
+
);
|
|
1450
1539
|
var PERP_SHARED_CUNG = new RegExp(
|
|
1451
|
-
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng|[Hh]\u1EA1)\\s+([A-Z])([A-Z])(?![A-Z])\\s+v\xE0\\s+([A-Z])([A-Z])(?![A-Z])\\s+c\xF9ng\\s+vu\xF4ng\\s*g\xF3c\\s+(?:v\u1EDBi\\s+)?
|
|
1540
|
+
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng|[Hh]\u1EA1)\\s+([A-Z])([A-Z])(?![A-Z])\\s+v\xE0\\s+([A-Z])([A-Z])(?![A-Z])\\s+(?:c\xF9ng\\s+)?vu\xF4ng\\s*g\xF3c\\s+(?:v\u1EDBi\\s+)?" + PERP_LINE_PREFIX + PERP_LINE_TOK,
|
|
1452
1541
|
"gu"
|
|
1453
1542
|
);
|
|
1454
1543
|
var FROM_DRAW_DISTRIB = new RegExp(
|
|
1455
1544
|
"t\u1EEB\\s+([A-Z])(?!\\p{L})\\s+k\u1EBB\\s+([A-Z]{2})\\s*,\\s*([A-Z]{2})\\s+vu\xF4ng\\s*g\xF3c\\s+v\u1EDBi\\s+c\xE1c\\s+c\u1EA1nh\\s+([A-Z]{2})\\s*,\\s*([A-Z]{2})(?![A-Z])",
|
|
1456
1545
|
"gu"
|
|
1457
1546
|
);
|
|
1547
|
+
var DECLARE_LINE = new RegExp(
|
|
1548
|
+
"(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng|ti\u1EBFp\\s*tuy\u1EBFn|c\xE1t\\s*tuy\u1EBFn|tia)\\s+([A-Z]?[a-z][a-z]?[0-9]?)(?![A-Za-z])",
|
|
1549
|
+
"gu"
|
|
1550
|
+
);
|
|
1551
|
+
function declaredNamedLines(problem) {
|
|
1552
|
+
const out = /* @__PURE__ */ new Set();
|
|
1553
|
+
DECLARE_LINE.lastIndex = 0;
|
|
1554
|
+
let m;
|
|
1555
|
+
while ((m = DECLARE_LINE.exec(problem)) !== null) out.add(m[1]);
|
|
1556
|
+
return out;
|
|
1557
|
+
}
|
|
1558
|
+
function isAllowedLine(line, declared) {
|
|
1559
|
+
if (/^[A-Z]{1,2}$/u.test(line)) return true;
|
|
1560
|
+
return declared.has(line);
|
|
1561
|
+
}
|
|
1458
1562
|
function splitNames(blob) {
|
|
1459
1563
|
return blob.split(/,|và/).map((s) => {
|
|
1460
1564
|
const mm = /^([A-Z])(['′]?)/u.exec(s.trim());
|
|
@@ -1462,11 +1566,11 @@ function splitNames(blob) {
|
|
|
1462
1566
|
}).filter(Boolean);
|
|
1463
1567
|
}
|
|
1464
1568
|
var PERP_DRAW = new RegExp(
|
|
1465
|
-
`(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng|[Hh]\u1EA1)\\s+([A-Z])([A-Z])(?![A-Z])\\s+(?:\u22A5\\s*|vu\xF4ng\\s*g\xF3c(?:\\s+v\u1EDBi)?\\s+)(?:v\u1EDBi\\s+)
|
|
1569
|
+
`(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng|[Hh]\u1EA1)\\s+([A-Z])([A-Z])(?![A-Z])\\s+(?:\u22A5\\s*|vu\xF4ng\\s*g\xF3c(?:\\s+v\u1EDBi)?\\s+)(?:v\u1EDBi\\s+)?${PERP_LINE_PREFIX}${PERP_LINE_TOK}(?:\\s+t\u1EA1i\\s+([A-Z]))?`,
|
|
1466
1570
|
"gu"
|
|
1467
1571
|
);
|
|
1468
1572
|
var PERP_BARE = new RegExp(
|
|
1469
|
-
`([A-Z])([A-Z])(?![A-Z])\\s+(?:\u22A5\\s*|vu\xF4ng\\s*g\xF3c(?:\\s+v\u1EDBi)?\\s+)(?:v\u1EDBi\\s+)
|
|
1573
|
+
`([A-Z])([A-Z])(?![A-Z])\\s+(?:\u22A5\\s*|vu\xF4ng\\s*g\xF3c(?:\\s+v\u1EDBi)?\\s+)(?:v\u1EDBi\\s+)?${PERP_LINE_PREFIX}${PERP_LINE_TOK}(?:\\s+t\u1EA1i\\s+([A-Z]))?`,
|
|
1470
1574
|
"gu"
|
|
1471
1575
|
);
|
|
1472
1576
|
var DRAW_VERB = /[Kk]ẻ|[Vv]ẽ|[Dd]ựng/u;
|
|
@@ -1535,7 +1639,7 @@ function parseAltitudeBundle(text, fallbackTri) {
|
|
|
1535
1639
|
}
|
|
1536
1640
|
return out;
|
|
1537
1641
|
}
|
|
1538
|
-
function parseFeet(text, fallbackTri) {
|
|
1642
|
+
function parseFeet(text, fallbackTri, declared = /* @__PURE__ */ new Set()) {
|
|
1539
1643
|
const out = [];
|
|
1540
1644
|
const consumed = [];
|
|
1541
1645
|
const bundled = parseAltitudeBundle(text, fallbackTri);
|
|
@@ -1577,6 +1681,24 @@ function parseFeet(text, fallbackTri) {
|
|
|
1577
1681
|
}
|
|
1578
1682
|
if (ok) return local;
|
|
1579
1683
|
}
|
|
1684
|
+
ZIP_FROM_PROJ.lastIndex = 0;
|
|
1685
|
+
for (const zm of text.matchAll(ZIP_FROM_PROJ)) {
|
|
1686
|
+
const names = splitNamesIdx(zm[1]);
|
|
1687
|
+
const froms = splitNamesIdx(zm[2]);
|
|
1688
|
+
const lines = zm[3].split(/,|và/).map((s) => s.trim()).filter(Boolean);
|
|
1689
|
+
if (names.length < 2) continue;
|
|
1690
|
+
if (names.length !== froms.length || names.length !== lines.length) continue;
|
|
1691
|
+
const local = [];
|
|
1692
|
+
let ok = true;
|
|
1693
|
+
for (let i = 0; i < names.length; i++) {
|
|
1694
|
+
if (lines[i].includes(names[i]) || names[i] === froms[i]) {
|
|
1695
|
+
ok = false;
|
|
1696
|
+
break;
|
|
1697
|
+
}
|
|
1698
|
+
local.push({ name: names[i], from: froms[i], onLine: lines[i], withSegment: true });
|
|
1699
|
+
}
|
|
1700
|
+
if (ok && local.length > 0) return local;
|
|
1701
|
+
}
|
|
1580
1702
|
for (const re of [DISTRIB_PROJ, DISTRIB_FOOT]) {
|
|
1581
1703
|
re.lastIndex = 0;
|
|
1582
1704
|
for (const dm2 of text.matchAll(re)) {
|
|
@@ -1617,8 +1739,8 @@ function parseFeet(text, fallbackTri) {
|
|
|
1617
1739
|
for (const fm of text.matchAll(FEET_FROM_SHARED_LINE)) {
|
|
1618
1740
|
const start = fm.index ?? 0;
|
|
1619
1741
|
if (consumed.some(([a, b]) => start >= a && start < b)) continue;
|
|
1620
|
-
const
|
|
1621
|
-
const [n1, n2, f1, f2, line] = [fm[1], fm[2],
|
|
1742
|
+
const norm5 = (s) => s.replace(/′/g, "'");
|
|
1743
|
+
const [n1, n2, f1, f2, line] = [fm[1], fm[2], norm5(fm[3]), norm5(fm[4]), norm5(fm[5])];
|
|
1622
1744
|
if (n1 === n2 || f1 === f2 || line.includes(n1) || line.includes(n2)) continue;
|
|
1623
1745
|
out.push({ name: n1, from: f1, onLine: line, withSegment: true });
|
|
1624
1746
|
out.push({ name: n2, from: f2, onLine: line, withSegment: true });
|
|
@@ -1643,7 +1765,9 @@ function parseFeet(text, fallbackTri) {
|
|
|
1643
1765
|
if (MID_BEFORE.test(before)) continue;
|
|
1644
1766
|
const nm = NAME_BEFORE.exec(before);
|
|
1645
1767
|
if (!nm) continue;
|
|
1646
|
-
|
|
1768
|
+
const onLine = m[2] ?? m[4];
|
|
1769
|
+
if (!isAllowedLine(onLine, declared)) continue;
|
|
1770
|
+
out.push({ name: nm[1], from: m[1] ?? m[3], onLine });
|
|
1647
1771
|
}
|
|
1648
1772
|
PERP_DRAW_DISTRIB.lastIndex = 0;
|
|
1649
1773
|
for (const ddm of text.matchAll(PERP_DRAW_DISTRIB)) {
|
|
@@ -1654,10 +1778,36 @@ function parseFeet(text, fallbackTri) {
|
|
|
1654
1778
|
out.push({ name: foot2, from: from2, onLine: line2, withSegment: true });
|
|
1655
1779
|
consumed.push([ddm.index ?? 0, (ddm.index ?? 0) + ddm[0].length]);
|
|
1656
1780
|
}
|
|
1781
|
+
PERP_DRAW_ARE.lastIndex = 0;
|
|
1782
|
+
for (const am of text.matchAll(PERP_DRAW_ARE)) {
|
|
1783
|
+
const start = am.index ?? 0;
|
|
1784
|
+
if (consumed.some(([a, b]) => start >= a && start < b)) continue;
|
|
1785
|
+
const pairs = am[1].split(",").map((s) => s.trim()).filter((s) => /^[A-Z]{2}$/.test(s));
|
|
1786
|
+
const lines = am[2].split(/,|và/).map((s) => s.trim()).filter((s) => /^[A-Z]{2}$/.test(s));
|
|
1787
|
+
if (pairs.length < 2 || pairs.length !== lines.length) continue;
|
|
1788
|
+
const from = pairs[0][0];
|
|
1789
|
+
if (!pairs.every((p) => p[0] === from)) continue;
|
|
1790
|
+
const local = [];
|
|
1791
|
+
let ok = true;
|
|
1792
|
+
for (let i = 0; i < pairs.length; i++) {
|
|
1793
|
+
const foot = pairs[i][1];
|
|
1794
|
+
const onLine = lines[i];
|
|
1795
|
+
if (onLine.includes(foot)) {
|
|
1796
|
+
ok = false;
|
|
1797
|
+
break;
|
|
1798
|
+
}
|
|
1799
|
+
local.push({ name: foot, from, onLine, withSegment: true });
|
|
1800
|
+
}
|
|
1801
|
+
if (ok && local.length > 0) {
|
|
1802
|
+
out.push(...local);
|
|
1803
|
+
consumed.push([start, start + am[0].length]);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1657
1806
|
PERP_SHARED_CUNG.lastIndex = 0;
|
|
1658
1807
|
for (const sm of text.matchAll(PERP_SHARED_CUNG)) {
|
|
1659
1808
|
const [f1, t1, f2, t2, line] = [sm[1], sm[2], sm[3], sm[4], sm[5]];
|
|
1660
1809
|
if (t1 === t2 || line.includes(t1) || line.includes(t2)) continue;
|
|
1810
|
+
if (!isAllowedLine(line, declared)) continue;
|
|
1661
1811
|
out.push({ name: t1, from: f1, onLine: line, withSegment: true });
|
|
1662
1812
|
out.push({ name: t2, from: f2, onLine: line, withSegment: true });
|
|
1663
1813
|
consumed.push([sm.index ?? 0, (sm.index ?? 0) + sm[0].length]);
|
|
@@ -1671,6 +1821,7 @@ function parseFeet(text, fallbackTri) {
|
|
|
1671
1821
|
const at = pm[4];
|
|
1672
1822
|
if (at && at !== foot) continue;
|
|
1673
1823
|
if (onLine.includes(foot)) continue;
|
|
1824
|
+
if (!isAllowedLine(onLine, declared)) continue;
|
|
1674
1825
|
out.push({ name: foot, from, onLine });
|
|
1675
1826
|
}
|
|
1676
1827
|
if (DRAW_VERB.test(text)) {
|
|
@@ -1683,6 +1834,7 @@ function parseFeet(text, fallbackTri) {
|
|
|
1683
1834
|
const at = bm[4];
|
|
1684
1835
|
if (at && at !== foot) continue;
|
|
1685
1836
|
if (onLine.includes(foot)) continue;
|
|
1837
|
+
if (!isAllowedLine(onLine, declared)) continue;
|
|
1686
1838
|
if (out.some((o) => o.name === foot)) continue;
|
|
1687
1839
|
out.push({ name: foot, from, onLine });
|
|
1688
1840
|
}
|
|
@@ -1718,13 +1870,14 @@ var perpFootRule = {
|
|
|
1718
1870
|
match(ctx) {
|
|
1719
1871
|
const out = [];
|
|
1720
1872
|
const fallbackTri = uniqueTriangle(ctx.problem);
|
|
1873
|
+
const declared = declaredNamedLines(ctx.problem);
|
|
1721
1874
|
for (const c of ctx.clauses) {
|
|
1722
|
-
const feet = parseFeet(c.text, fallbackTri);
|
|
1875
|
+
const feet = parseFeet(c.text, fallbackTri, declared);
|
|
1723
1876
|
if (feet.length === 0) continue;
|
|
1724
1877
|
const intents = feet.flatMap((f) => {
|
|
1725
1878
|
if (f.from === "") return [addPoint(f.name, { kind: "orthocenter", of: f.onLine.split("") })];
|
|
1726
|
-
const
|
|
1727
|
-
return f.withSegment ? [
|
|
1879
|
+
const add2 = addPoint(f.name, { kind: "perpFoot", from: f.from, onLine: f.onLine });
|
|
1880
|
+
return f.withSegment ? [add2, connect(f.from, f.name, "segment")] : [add2];
|
|
1728
1881
|
});
|
|
1729
1882
|
out.push({ ruleId: "perpFoot", clauseIds: [c.id], intents });
|
|
1730
1883
|
}
|
|
@@ -1771,28 +1924,28 @@ function parseNum(raw) {
|
|
|
1771
1924
|
function isInscribedCircumscribed(problem, center) {
|
|
1772
1925
|
const REL = "(?:(?:n\u1ED9i|ngo\u1EA1i)\\s*ti\u1EBFp|[Ii]nscribed|[Cc]ircumscrib(?:es|ed))";
|
|
1773
1926
|
const after = new RegExp(
|
|
1774
|
-
`${REL}[^.]{0,30}?(?:\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s*|[Cc]ircle\\s*)?\\(?\\s*${center}(?![A-Z])`,
|
|
1927
|
+
`${REL}[^.]{0,30}?(?:\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s*|[Cc]ircle\\s*)?\\(?\\s*${escapeRe(center)}(?![A-Z])`,
|
|
1775
1928
|
"u"
|
|
1776
1929
|
);
|
|
1777
1930
|
const before = new RegExp(
|
|
1778
|
-
`\\(\\s*${center}\\s*[;,][^()]*\\)[^.]{0,30}?${REL}`,
|
|
1931
|
+
`\\(\\s*${escapeRe(center)}\\s*[;,][^()]*\\)[^.]{0,30}?${REL}`,
|
|
1779
1932
|
"u"
|
|
1780
1933
|
);
|
|
1781
1934
|
return after.test(problem) || before.test(problem);
|
|
1782
1935
|
}
|
|
1783
1936
|
function isDiameterCircle(problem, center) {
|
|
1784
1937
|
const parenThenDiameter = new RegExp(
|
|
1785
|
-
`\\(\\s*${center}\\s*[;,][^()]*\\)[^.]{0,40}?${DUONG_KW}\\s*k\xEDnh\\s+[A-Z]{2}(?![A-Z])`,
|
|
1938
|
+
`\\(\\s*${escapeRe(center)}\\s*[;,][^()]*\\)[^.]{0,40}?${DUONG_KW}\\s*k\xEDnh\\s+[A-Z]{2}(?![A-Z])`,
|
|
1786
1939
|
"u"
|
|
1787
1940
|
);
|
|
1788
1941
|
const namedThenDiameter = new RegExp(
|
|
1789
|
-
`${CIRCLE_KW}\\s*\\(\\s*${center}\\s*\\)[^.]{0,40}?${DUONG_KW}\\s*k\xEDnh\\s+[A-Z]{2}(?![A-Z])`,
|
|
1942
|
+
`${CIRCLE_KW}\\s*\\(\\s*${escapeRe(center)}\\s*\\)[^.]{0,40}?${DUONG_KW}\\s*k\xEDnh\\s+[A-Z]{2}(?![A-Z])`,
|
|
1790
1943
|
"u"
|
|
1791
1944
|
);
|
|
1792
1945
|
return parenThenDiameter.test(problem) || namedThenDiameter.test(problem);
|
|
1793
1946
|
}
|
|
1794
1947
|
function findParenClauseId(clauses, center) {
|
|
1795
|
-
const frag = new RegExp(`\\(\\s*${center}(?![A-Z])`, "u");
|
|
1948
|
+
const frag = new RegExp(`\\(\\s*${escapeRe(center)}(?![A-Z])`, "u");
|
|
1796
1949
|
for (const c of clauses) if (frag.test(c.text)) return c.id;
|
|
1797
1950
|
return void 0;
|
|
1798
1951
|
}
|
|
@@ -2330,8 +2483,52 @@ var externalPointRule = {
|
|
|
2330
2483
|
}
|
|
2331
2484
|
};
|
|
2332
2485
|
|
|
2486
|
+
// src/stamps/geometry-2d/ai/rules/externalPointAtRadius.ts
|
|
2487
|
+
var METRIC = /([A-Z])(?:['′]?)([A-Z])(?:['′]?)\s*=\s*([2-9])\s*[Rr](?![A-Za-z])/gu;
|
|
2488
|
+
function isCenter(problem, c) {
|
|
2489
|
+
const e = escapeRe(c);
|
|
2490
|
+
return new RegExp(
|
|
2491
|
+
String.raw`\(\s*${e}[\s);,]|tâm\s*${e}(?![A-Za-z])|đường\s*tròn\s*\(?\s*${e}(?![A-Za-z])`,
|
|
2492
|
+
"u"
|
|
2493
|
+
).test(problem);
|
|
2494
|
+
}
|
|
2495
|
+
var externalPointAtRadiusRule = {
|
|
2496
|
+
id: "externalPointAtRadius",
|
|
2497
|
+
priority: 67,
|
|
2498
|
+
languages: ["vi"],
|
|
2499
|
+
patterns: [/[A-Z](?:['′]?)[A-Z](?:['′]?)\s*=\s*[2-9]\s*[Rr](?![A-Za-z])/u],
|
|
2500
|
+
match(ctx) {
|
|
2501
|
+
const out = [];
|
|
2502
|
+
for (const c of ctx.clauses) {
|
|
2503
|
+
METRIC.lastIndex = 0;
|
|
2504
|
+
for (const m of c.text.matchAll(METRIC)) {
|
|
2505
|
+
const a = m[1];
|
|
2506
|
+
const b = m[2];
|
|
2507
|
+
let center;
|
|
2508
|
+
let point;
|
|
2509
|
+
const aC = isCenter(ctx.problem, a);
|
|
2510
|
+
const bC = isCenter(ctx.problem, b);
|
|
2511
|
+
if (aC && !bC) {
|
|
2512
|
+
center = a;
|
|
2513
|
+
point = b;
|
|
2514
|
+
} else if (bC && !aC) {
|
|
2515
|
+
center = b;
|
|
2516
|
+
point = a;
|
|
2517
|
+
}
|
|
2518
|
+
if (!center || !point || center === point) continue;
|
|
2519
|
+
out.push({
|
|
2520
|
+
ruleId: "externalPointAtRadius",
|
|
2521
|
+
clauseIds: [c.id],
|
|
2522
|
+
intents: [addPoint(point, { kind: "externalToCircle", circle: center })]
|
|
2523
|
+
});
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
return out;
|
|
2527
|
+
}
|
|
2528
|
+
};
|
|
2529
|
+
|
|
2333
2530
|
// src/stamps/geometry-2d/ai/rules/arcMidpoint.ts
|
|
2334
|
-
var ARC_MID = /(?:chính\s+giữa|trung\s*điểm)\s+(?:của\s+)
|
|
2531
|
+
var ARC_MID = /(?:chính\s+giữa|trung\s*điểm)\s+(?:của\s+|các\s+)*cung/u;
|
|
2335
2532
|
var ARC_PAIR = /(?:chính\s+giữa|trung\s*điểm)\s+(?:của\s+)?cung\s+(nhỏ\s+|lớn\s+)?([A-Z])([A-Z])/u;
|
|
2336
2533
|
var NAME_BEFORE2 = /([A-Z])(?:['′]?)\s+(?:là\s+)?(?:điểm\s+)?(?:chính\s+giữa|trung\s*điểm)\s+(?:của\s+)?cung/u;
|
|
2337
2534
|
var TRI3 = /tam giác\s+([A-Z])([A-Z])([A-Z])/u;
|
|
@@ -2367,6 +2564,10 @@ var DISTRIB_TWO_ARCS = new RegExp(
|
|
|
2367
2564
|
"([A-Z])(?:['\u2032]?)\\s*(?:,|v\xE0)\\s*([A-Z])(?:['\u2032]?)\\s+l\u1EA7n\\s*l\u01B0\u1EE3t\\s+(?:l\xE0\\s+)?(?:\u0111i\u1EC3m\\s+)?(?:ch\xEDnh\\s+gi\u1EEFa|trung\\s*\u0111i\u1EC3m)\\s+(?:c\u1EE7a\\s+)?cung\\s+(?:nh\u1ECF\\s+|l\u1EDBn\\s+)?([A-Z])([A-Z])(?![A-Z])\\s+v\xE0\\s+(?:\u0111i\u1EC3m\\s+)?(?:(?:ch\xEDnh\\s+gi\u1EEFa|trung\\s*\u0111i\u1EC3m)\\s+)?(?:c\u1EE7a\\s+)?cung\\s+(?:nh\u1ECF\\s+|l\u1EDBn\\s+)?([A-Z])([A-Z])(?![A-Z])",
|
|
2368
2565
|
"u"
|
|
2369
2566
|
);
|
|
2567
|
+
var DISTRIB_TWO_ARCS3 = new RegExp(
|
|
2568
|
+
"([A-Z])(?:['\u2032]?)\\s*(?:,|v\xE0)\\s*([A-Z])(?:['\u2032]?)\\s+l\u1EA7n\\s*l\u01B0\u1EE3t\\s+(?:l\xE0\\s+)?(?:\u0111i\u1EC3m\\s+)?(?:ch\xEDnh\\s+gi\u1EEFa|trung\\s*\u0111i\u1EC3m)\\s+(?:c\u1EE7a\\s+|c\xE1c\\s+)*cung\\s+([A-Z])([A-Z])([A-Z])(?![A-Z])\\s+v\xE0\\s+(?:\u0111i\u1EC3m\\s+)?(?:(?:ch\xEDnh\\s+gi\u1EEFa|trung\\s*\u0111i\u1EC3m)\\s+)?(?:c\u1EE7a\\s+)?(?:cung\\s+)?([A-Z])([A-Z])([A-Z])(?![A-Z])",
|
|
2569
|
+
"u"
|
|
2570
|
+
);
|
|
2370
2571
|
function resolveCircle(problem) {
|
|
2371
2572
|
const w = CIRCLE_WORDS.exec(problem);
|
|
2372
2573
|
if (w) return w[1];
|
|
@@ -2414,6 +2615,27 @@ var arcMidpointRule = {
|
|
|
2414
2615
|
};
|
|
2415
2616
|
for (const c of ctx.clauses) {
|
|
2416
2617
|
if (!ARC_MID.test(c.text)) continue;
|
|
2618
|
+
const twoArcs3 = DISTRIB_TWO_ARCS3.exec(c.text);
|
|
2619
|
+
if (twoArcs3) {
|
|
2620
|
+
const [, n1, n2, x1, y1, z1, x2, y2, z22] = twoArcs3;
|
|
2621
|
+
const triples = [
|
|
2622
|
+
[n1, x1, z1, y1],
|
|
2623
|
+
[n2, x2, z22, y2]
|
|
2624
|
+
];
|
|
2625
|
+
const ok = triples.every(
|
|
2626
|
+
([n, a2, b2, ct]) => arcOnCircum(a2, b2) && (/* @__PURE__ */ new Set([a2, b2, ct])).size === 3 && n !== a2 && n !== b2 && n !== ct
|
|
2627
|
+
);
|
|
2628
|
+
if (ok) {
|
|
2629
|
+
const mark = out.length;
|
|
2630
|
+
let good = true;
|
|
2631
|
+
for (const [n, a2, b2, ct] of triples) {
|
|
2632
|
+
good = pushArc(c, n, a2, b2, { rel: "in", point: ct });
|
|
2633
|
+
if (!good) break;
|
|
2634
|
+
}
|
|
2635
|
+
if (!good) out.length = mark;
|
|
2636
|
+
}
|
|
2637
|
+
continue;
|
|
2638
|
+
}
|
|
2417
2639
|
const twoArcs = DISTRIB_TWO_ARCS.exec(c.text);
|
|
2418
2640
|
if (twoArcs) {
|
|
2419
2641
|
const [, n1, n2, a1, b1, a2, b2] = twoArcs;
|
|
@@ -2437,7 +2659,7 @@ var arcMidpointRule = {
|
|
|
2437
2659
|
}
|
|
2438
2660
|
const pairM = ARC_PAIR.exec(c.text);
|
|
2439
2661
|
if (!pairM) continue;
|
|
2440
|
-
|
|
2662
|
+
const isMajor = !!pairM[1] && /lớn/u.test(pairM[1]);
|
|
2441
2663
|
const pair = pairFromToken(pairM[2] + pairM[3]);
|
|
2442
2664
|
if (pair.length !== 2) continue;
|
|
2443
2665
|
const [a, b] = pair;
|
|
@@ -2462,7 +2684,9 @@ var arcMidpointRule = {
|
|
|
2462
2684
|
pushArc(c, name, a, b, conts[0]);
|
|
2463
2685
|
} else if (tri) {
|
|
2464
2686
|
const third = [tri[1], tri[2], tri[3]].find((v) => v !== a && v !== b);
|
|
2465
|
-
if (third) pushArc(c, name, a, b, { rel: "not", point: third });
|
|
2687
|
+
if (third) pushArc(c, name, a, b, isMajor ? { rel: "in", point: third } : { rel: "not", point: third });
|
|
2688
|
+
} else if (isMajor && explicitCircle) {
|
|
2689
|
+
pushArc(c, name, a, b, { rel: "in", point: explicitCircle });
|
|
2466
2690
|
} else {
|
|
2467
2691
|
out.push({
|
|
2468
2692
|
ruleId: "arcMidpoint",
|
|
@@ -2558,6 +2782,14 @@ var RE_CUT = new RegExp(
|
|
|
2558
2782
|
THROUGH_LINE + "\\s+c\u1EAFt\\s+(?:\u0111\u01B0\u1EDDng\\s*(?:th\u1EB3ng\\s*)?)?([A-Z])([A-Z])(?!\\p{L})\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
2559
2783
|
"gu"
|
|
2560
2784
|
);
|
|
2785
|
+
var FOOT2 = "([A-Z])([A-Z])\\s*,\\s*([A-Z])([A-Z])";
|
|
2786
|
+
var FOOT3 = "([A-Z])([A-Z])\\s*,\\s*([A-Z])([A-Z])\\s*,\\s*([A-Z])([A-Z])";
|
|
2787
|
+
var TARGET2 = "([A-Z]{2})\\s*,\\s*([A-Z]{2})(?![A-Z])";
|
|
2788
|
+
var TARGET3 = "([A-Z]{2})\\s*,\\s*([A-Z]{2})\\s*,\\s*([A-Z]{2})(?![A-Z])";
|
|
2789
|
+
var PERP_KW = "(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?(?:\u22A5|vu\xF4ng\\s*g\xF3c)\\s+(?:v\u1EDBi\\s+)?(?:c\xE1c\\s+)?(?:c\u1EA1nh\\s+|\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+|\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+)?";
|
|
2790
|
+
var FROM = "(?:Qua|qua|T\u1EEB|t\u1EEB)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?:['\u2032]?)(?!\\p{L})[^.]{0,16}?(?:k\u1EBB|v\u1EBD|d\u1EF1ng)\\s+";
|
|
2791
|
+
var PERP_NAMED_DISTRIB3 = new RegExp(FROM + FOOT3 + "\\s+" + PERP_KW + TARGET3, "gu");
|
|
2792
|
+
var PERP_NAMED_DISTRIB2 = new RegExp(FROM + FOOT2 + "\\s+" + PERP_KW + TARGET2, "gu");
|
|
2561
2793
|
var PREFILTER6 = /(?:Qua|qua|Từ|từ)\s+(?:điểm\s+)?[A-Z][^.]{0,24}?(?:song\s*song|vuông\s*góc)|đường\s*thẳng\s+qua\s+[A-Z][^.]{0,24}?(?:song\s*song|vuông\s*góc)/u;
|
|
2562
2794
|
function lineNameOf(isParallel, through) {
|
|
2563
2795
|
return (isParallel ? "par" : "prp") + through;
|
|
@@ -2587,6 +2819,43 @@ var parallelPerpRule = {
|
|
|
2587
2819
|
});
|
|
2588
2820
|
cutSpans.push([start, end]);
|
|
2589
2821
|
};
|
|
2822
|
+
const emitNamedPerp = (from, foot, target, start, end) => {
|
|
2823
|
+
if (foot === from || target.includes(foot)) return;
|
|
2824
|
+
const lineName = lineNameOf(false, from) + foot;
|
|
2825
|
+
out.push({
|
|
2826
|
+
ruleId: "parallelPerp",
|
|
2827
|
+
clauseIds: [c.id],
|
|
2828
|
+
intents: [
|
|
2829
|
+
drawLine(lineName, "perpThrough", { through: from, to: target }),
|
|
2830
|
+
addPoint(foot, { kind: "intersection", of: [lineName, target] })
|
|
2831
|
+
]
|
|
2832
|
+
});
|
|
2833
|
+
cutSpans.push([start, end]);
|
|
2834
|
+
};
|
|
2835
|
+
PERP_NAMED_DISTRIB3.lastIndex = 0;
|
|
2836
|
+
for (const m of c.text.matchAll(PERP_NAMED_DISTRIB3)) {
|
|
2837
|
+
const from = m[1];
|
|
2838
|
+
const triples = [
|
|
2839
|
+
[m[3], m[8]],
|
|
2840
|
+
[m[5], m[9]],
|
|
2841
|
+
[m[7], m[10]]
|
|
2842
|
+
];
|
|
2843
|
+
if ([m[2], m[4], m[6]].every((f) => f === from)) {
|
|
2844
|
+
const s = m.index ?? 0;
|
|
2845
|
+
const e = s + m[0].length;
|
|
2846
|
+
for (const [foot, target] of triples) emitNamedPerp(from, foot, target, s, e);
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
PERP_NAMED_DISTRIB2.lastIndex = 0;
|
|
2850
|
+
for (const m of c.text.matchAll(PERP_NAMED_DISTRIB2)) {
|
|
2851
|
+
const s = m.index ?? 0;
|
|
2852
|
+
if (cutSpans.some(([a, b]) => s >= a && s < b)) continue;
|
|
2853
|
+
const from = m[1];
|
|
2854
|
+
if (m[2] !== from || m[4] !== from) continue;
|
|
2855
|
+
const pairs = [[m[3], m[6]], [m[5], m[7]]];
|
|
2856
|
+
const e = s + m[0].length;
|
|
2857
|
+
for (const [foot, target] of pairs) emitNamedPerp(from, foot, target, s, e);
|
|
2858
|
+
}
|
|
2590
2859
|
RE_GIAO.lastIndex = 0;
|
|
2591
2860
|
for (const m of c.text.matchAll(RE_GIAO)) {
|
|
2592
2861
|
emitCut(m[2], m[3], m[4], m[5], m[6] + m[7], m[1], m.index ?? 0, (m.index ?? 0) + m[0].length);
|
|
@@ -2620,12 +2889,23 @@ var REFLECT = /đối\s*xứng/u;
|
|
|
2620
2889
|
var PT = `[A-Z](?:['\u2032])?`;
|
|
2621
2890
|
var NAME_BEFORE4 = new RegExp(
|
|
2622
2891
|
`(${PT})\\s+(?:l\xE0\\s+)?(?:\u0111i\u1EC3m\\s+)?\u0111\u1ED1i\\s*x\u1EE9ng\\s+(?:c\u1EE7a\\s+|v\u1EDBi\\s+)?(?:\u0111i\u1EC3m\\s+)?(${PT})\\s+qua\\s+(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+|tr\u1EE5c\\s+|trung\\s*\u0111i\u1EC3m\\s+|t\xE2m\\s+|\u0111i\u1EC3m\\s+)?([A-Za-z][A-Za-z\u2032']?)`,
|
|
2623
|
-
"
|
|
2892
|
+
"gu"
|
|
2624
2893
|
);
|
|
2625
2894
|
var DISTRIB2 = new RegExp(
|
|
2626
2895
|
`(${PT})\\s*,\\s*(${PT})\\s+l\u1EA7n\\s*l\u01B0\u1EE3t\\s+(?:l\xE0\\s+)?(?:\u0111i\u1EC3m\\s+)?\u0111\u1ED1i\\s*x\u1EE9ng\\s+(?:c\u1EE7a\\s+|v\u1EDBi\\s+)?(?:\u0111i\u1EC3m\\s+)?(${PT})\\s+qua\\s+([A-Za-z]{1,2})(?:['\u2032]?)\\s*(?:,|v\xE0)\\s*([A-Za-z]{1,2})(?:['\u2032]?)`,
|
|
2627
2896
|
"u"
|
|
2628
2897
|
);
|
|
2898
|
+
var LL = `(?:l\u1EA7n\\s*l\u01B0\u1EE3t|t\u01B0\u01A1ng\\s*\u1EE9ng|(?:theo\\s+)?th\u1EE9\\s+t\u1EF1)`;
|
|
2899
|
+
var NAMES_BLOB2 = `(${PT}(?:\\s*(?:,|v\xE0)\\s*${PT})+)`;
|
|
2900
|
+
var SRCS_BLOB = `(${PT}(?:\\s*(?:,|v\xE0)\\s*${PT})+)`;
|
|
2901
|
+
var MIRRORS_BLOB = `([A-Za-z]{1,2}(?:['\u2032]?)(?:\\s*(?:,|v\xE0)\\s*[A-Za-z]{1,2}(?:['\u2032]?))+)`;
|
|
2902
|
+
var DISTRIB_MULTI = new RegExp(
|
|
2903
|
+
`${NAMES_BLOB2}\\s+(?:${LL}\\s+)?(?:l\xE0\\s+)?(?:c\xE1c\\s+)?(?:\u0111i\u1EC3m\\s+)?\u0111\u1ED1i\\s*x\u1EE9ng\\s+(?:c\u1EE7a\\s+|v\u1EDBi\\s+)?(?:\u0111i\u1EC3m\\s+)?${SRCS_BLOB}\\s+(?:${LL}\\s+)?qua\\s+${MIRRORS_BLOB}`,
|
|
2904
|
+
"u"
|
|
2905
|
+
);
|
|
2906
|
+
function splitList(blob) {
|
|
2907
|
+
return blob.split(/\s*,\s*|\s+và\s+/u).map((s) => s.trim()).filter(Boolean);
|
|
2908
|
+
}
|
|
2629
2909
|
var NAME_AFTER = new RegExp(
|
|
2630
2910
|
`(?:\u0111i\u1EC3m\\s+)?\u0111\u1ED1i\\s*x\u1EE9ng\\s+(?:c\u1EE7a\\s+|v\u1EDBi\\s+)?(?:\u0111i\u1EC3m\\s+)?(${PT})\\s+qua\\s+(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+|tr\u1EE5c\\s+|trung\\s*\u0111i\u1EC3m\\s+|t\xE2m\\s+|\u0111i\u1EC3m\\s+)?([A-Za-z][A-Za-z\u2032']?)`,
|
|
2631
2911
|
"u"
|
|
@@ -2651,6 +2931,28 @@ var reflectionRule = {
|
|
|
2651
2931
|
const out = [];
|
|
2652
2932
|
for (const c of ctx.clauses) {
|
|
2653
2933
|
if (!REFLECT.test(c.text) && !REFLECT_EN_PRE.test(c.text)) continue;
|
|
2934
|
+
const dmm = DISTRIB_MULTI.exec(c.text);
|
|
2935
|
+
if (dmm) {
|
|
2936
|
+
const names = splitList(dmm[1]).map(normalizePointName);
|
|
2937
|
+
const srcs = splitList(dmm[2]).map(normalizePointName);
|
|
2938
|
+
const mirrors = splitList(dmm[3]);
|
|
2939
|
+
if (names.length >= 2 && names.length === srcs.length && names.length === mirrors.length) {
|
|
2940
|
+
const through2 = mirrors.map(classifyThrough);
|
|
2941
|
+
if (through2.every((t) => !!t)) {
|
|
2942
|
+
out.push({
|
|
2943
|
+
ruleId: "reflection",
|
|
2944
|
+
clauseIds: [c.id],
|
|
2945
|
+
intents: names.map(
|
|
2946
|
+
(nm, i) => addPoint(
|
|
2947
|
+
nm,
|
|
2948
|
+
through2[i].kind === "point" ? { kind: "reflectPoint", of: srcs[i], through: through2[i].value } : { kind: "reflectLine", of: srcs[i], through: through2[i].value }
|
|
2949
|
+
)
|
|
2950
|
+
)
|
|
2951
|
+
});
|
|
2952
|
+
continue;
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2654
2956
|
const dm = DISTRIB2.exec(c.text);
|
|
2655
2957
|
if (dm) {
|
|
2656
2958
|
const [n1, n2, ofPt, raw1, raw2] = [
|
|
@@ -2672,15 +2974,31 @@ var reflectionRule = {
|
|
|
2672
2974
|
continue;
|
|
2673
2975
|
}
|
|
2674
2976
|
}
|
|
2977
|
+
NAME_BEFORE4.lastIndex = 0;
|
|
2978
|
+
const befores = [...c.text.matchAll(NAME_BEFORE4)];
|
|
2979
|
+
if (befores.length) {
|
|
2980
|
+
for (const b of befores) {
|
|
2981
|
+
const nm = normalizePointName(b[1]);
|
|
2982
|
+
const ofP = normalizePointName(b[2]);
|
|
2983
|
+
const thr = classifyThrough(b[3]);
|
|
2984
|
+
if (!thr) continue;
|
|
2985
|
+
out.push({
|
|
2986
|
+
ruleId: "reflection",
|
|
2987
|
+
clauseIds: [c.id],
|
|
2988
|
+
intents: [
|
|
2989
|
+
addPoint(
|
|
2990
|
+
nm,
|
|
2991
|
+
thr.kind === "point" ? { kind: "reflectPoint", of: ofP, through: thr.value } : { kind: "reflectLine", of: ofP, through: thr.value }
|
|
2992
|
+
)
|
|
2993
|
+
]
|
|
2994
|
+
});
|
|
2995
|
+
}
|
|
2996
|
+
continue;
|
|
2997
|
+
}
|
|
2675
2998
|
let name;
|
|
2676
2999
|
let of;
|
|
2677
3000
|
let throughRaw;
|
|
2678
|
-
|
|
2679
|
-
if (before) {
|
|
2680
|
-
name = normalizePointName(before[1]);
|
|
2681
|
-
of = normalizePointName(before[2]);
|
|
2682
|
-
throughRaw = before[3];
|
|
2683
|
-
} else {
|
|
3001
|
+
{
|
|
2684
3002
|
const after = NAME_AFTER.exec(c.text);
|
|
2685
3003
|
if (after) {
|
|
2686
3004
|
name = extractPointName(c.text);
|
|
@@ -3183,7 +3501,7 @@ var DISTRIB_TWO = new RegExp(
|
|
|
3183
3501
|
"gu"
|
|
3184
3502
|
);
|
|
3185
3503
|
var CAT_ONE_TWO = new RegExp(
|
|
3186
|
-
`${REF}\\s+c\u1EAFt\\s+${REF}\\s*(?:,|v\xE0)\\s*${REF}\\s+(?:theo\\s+th\u1EE9\\s+t\u1EF1\\s+|l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?t\u1EA1i\\s+([A-Z])\\s*(?:,|v\xE0)\\s*(?:t\u1EA1i\\s+)?([A-Z])(?![A-Z])`,
|
|
3504
|
+
`${REF}\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?c\u1EAFt\\s+${REF}\\s*(?:,|v\xE0)\\s*${REF}\\s+(?:theo\\s+th\u1EE9\\s+t\u1EF1\\s+|l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?t\u1EA1i\\s+([A-Z])\\s*(?:,|v\xE0)\\s*(?:t\u1EA1i\\s+)?([A-Z])(?![A-Z])`,
|
|
3187
3505
|
"gu"
|
|
3188
3506
|
);
|
|
3189
3507
|
var CAT_TWO_ONE = new RegExp(
|
|
@@ -3198,12 +3516,22 @@ var CAT_ONE_RAY = new RegExp(
|
|
|
3198
3516
|
`${REF}\\s+c\u1EAFt\\s+(?:tia\\s+)?(?:ti\u1EBFp\\s*tuy\u1EBFn\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+)?([A-Z][a-z]|[a-z])(?![\\p{L}\\d])[^.;,]{0,40}?(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])`,
|
|
3199
3517
|
"gu"
|
|
3200
3518
|
);
|
|
3519
|
+
var NAMED_CAT_TAI = new RegExp(
|
|
3520
|
+
`(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|tia\\s+)?\\b([a-z]{1,2}[0-9]?)(?![\\p{L}\\d])\\s+(?:k\xE9o\\s+d\xE0i\\s+)?c\u1EAFt\\s+${REF}\\s+(?:k\xE9o\\s+d\xE0i\\s+)?(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])`,
|
|
3521
|
+
"gu"
|
|
3522
|
+
);
|
|
3201
3523
|
var GIAO_TWO_ONE_LA = new RegExp(
|
|
3202
3524
|
`giao\\s*\u0111i\u1EC3m\\s+(?:c\u1EE7a\\s+)?${REF}\\s*(?:,|v\xE0)\\s*${REF}\\s+v\u1EDBi\\s+${REF}\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?l\xE0\\s+([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])`,
|
|
3203
3525
|
"gu"
|
|
3204
3526
|
);
|
|
3527
|
+
var ZIP_MK = `(?:l\u1EA7n\\s*l\u01B0\u1EE3t|theo\\s+th\u1EE9\\s+t\u1EF1|t\u01B0\u01A1ng\\s+\u1EE9ng)`;
|
|
3528
|
+
var ZIP_SIDE = `(?:\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+|c\u1EA1nh\\s+|tia\\s+|${DUONG_KW}\\s*th\u1EB3ng\\s+)?`;
|
|
3205
3529
|
var CAT_ZIP = new RegExp(
|
|
3206
|
-
`${REF}\\s*,\\s*${REF}\\s
|
|
3530
|
+
`${REF}\\s*,\\s*${REF}\\s+${ZIP_MK}\\s+c\u1EAFt\\s+(?:c\xE1c\\s+)?${ZIP_SIDE}${REF}\\s*,\\s*${REF}\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])`,
|
|
3531
|
+
"gu"
|
|
3532
|
+
);
|
|
3533
|
+
var CAT_ZIP_POST = new RegExp(
|
|
3534
|
+
`${REF}\\s*,\\s*${REF}\\s+c\u1EAFt\\s+(?:c\xE1c\\s+)?${ZIP_SIDE}${REF}\\s*,\\s*${REF}\\s+${ZIP_MK}\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])`,
|
|
3207
3535
|
"gu"
|
|
3208
3536
|
);
|
|
3209
3537
|
var NAME_BEFORE6 = /([A-Z])(?:['′]?)\s+là\s+$/u;
|
|
@@ -3218,6 +3546,10 @@ var CAP_SYMBOL = new RegExp(
|
|
|
3218
3546
|
`(${PT2})\\s*=\\s*(${PT2})\\s*(${PT2})\\s*\u2229\\s*(${PT2})\\s*(${PT2})(?!${PT2})`,
|
|
3219
3547
|
"gu"
|
|
3220
3548
|
);
|
|
3549
|
+
var CAP_SYMBOL_POST = new RegExp(
|
|
3550
|
+
`(${PT2})\\s*(${PT2})\\s*\u2229\\s*(${PT2})\\s*(${PT2})\\s*=\\s*\\{?\\s*(${PT2})\\s*\\}?`,
|
|
3551
|
+
"gu"
|
|
3552
|
+
);
|
|
3221
3553
|
var PTP = "[A-Z](?:['\u2032])?";
|
|
3222
3554
|
var REFP = `(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+|tia\\s+|c\u1EA1nh\\s+)?(${PTP}\\s*${PTP})(?![A-Za-z])`;
|
|
3223
3555
|
var GIAO_PRIMED = new RegExp(
|
|
@@ -3242,6 +3574,12 @@ function makeIntent(name, ref1, ref2) {
|
|
|
3242
3574
|
if (ends.includes(name)) return null;
|
|
3243
3575
|
return addPoint(name, { kind: "intersection", of: [ref1, ref2] });
|
|
3244
3576
|
}
|
|
3577
|
+
function declaredNamedLines2(problem) {
|
|
3578
|
+
const out = /* @__PURE__ */ new Set();
|
|
3579
|
+
const RE31 = /(?:đường\s*thẳng|tiếp\s*tuyến|cát\s*tuyến)\s+([a-z]{1,2}[0-9]?)(?![\p{L}\d])/gu;
|
|
3580
|
+
for (const m of problem.matchAll(RE31)) out.add(m[1]);
|
|
3581
|
+
return out;
|
|
3582
|
+
}
|
|
3245
3583
|
function makeIntentEnds(name, e1, e2, e3, e4) {
|
|
3246
3584
|
const ends = [e1, e2, e3, e4];
|
|
3247
3585
|
if (new Set(ends).size !== 4) return null;
|
|
@@ -3259,6 +3597,7 @@ var intersectionRule = {
|
|
|
3259
3597
|
patterns: [PREFILTER8],
|
|
3260
3598
|
match(ctx) {
|
|
3261
3599
|
const out = [];
|
|
3600
|
+
const namedLines = declaredNamedLines2(ctx.problem);
|
|
3262
3601
|
for (const c of ctx.clauses) {
|
|
3263
3602
|
const seen = /* @__PURE__ */ new Set();
|
|
3264
3603
|
const emit = (name, ref1, ref2) => {
|
|
@@ -3282,6 +3621,15 @@ var intersectionRule = {
|
|
|
3282
3621
|
seen.add(name);
|
|
3283
3622
|
out.push({ ruleId: "intersection", clauseIds: [c.id], intents: [intent] });
|
|
3284
3623
|
}
|
|
3624
|
+
CAP_SYMBOL_POST.lastIndex = 0;
|
|
3625
|
+
for (const m of c.text.matchAll(CAP_SYMBOL_POST)) {
|
|
3626
|
+
const name = m[5];
|
|
3627
|
+
if (seen.has(name)) continue;
|
|
3628
|
+
const intent = makeIntentEnds(name, m[1], m[2], m[3], m[4]);
|
|
3629
|
+
if (!intent) continue;
|
|
3630
|
+
seen.add(name);
|
|
3631
|
+
out.push({ ruleId: "intersection", clauseIds: [c.id], intents: [intent] });
|
|
3632
|
+
}
|
|
3285
3633
|
CAT_ONE_TWO.lastIndex = 0;
|
|
3286
3634
|
for (const m of c.text.matchAll(CAT_ONE_TWO)) {
|
|
3287
3635
|
emit(m[4], m[1], m[2]);
|
|
@@ -3313,6 +3661,12 @@ var intersectionRule = {
|
|
|
3313
3661
|
for (const m of c.text.matchAll(CAT_ONE_RAY)) {
|
|
3314
3662
|
emitShape(m[3], m[1], m[2]);
|
|
3315
3663
|
}
|
|
3664
|
+
if (namedLines.size > 0) {
|
|
3665
|
+
NAMED_CAT_TAI.lastIndex = 0;
|
|
3666
|
+
for (const m of c.text.matchAll(NAMED_CAT_TAI)) {
|
|
3667
|
+
if (namedLines.has(m[1])) emitShape(m[3], m[2], m[1]);
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3316
3670
|
GIAO_TWO_ONE_LA.lastIndex = 0;
|
|
3317
3671
|
for (const m of c.text.matchAll(GIAO_TWO_ONE_LA)) {
|
|
3318
3672
|
emit(m[4], m[1], m[3]);
|
|
@@ -3323,6 +3677,11 @@ var intersectionRule = {
|
|
|
3323
3677
|
emit(m[5], m[1], m[3]);
|
|
3324
3678
|
emit(m[6], m[2], m[4]);
|
|
3325
3679
|
}
|
|
3680
|
+
CAT_ZIP_POST.lastIndex = 0;
|
|
3681
|
+
for (const m of c.text.matchAll(CAT_ZIP_POST)) {
|
|
3682
|
+
emit(m[5], m[1], m[3]);
|
|
3683
|
+
emit(m[6], m[2], m[4]);
|
|
3684
|
+
}
|
|
3326
3685
|
GIAO_LA.lastIndex = 0;
|
|
3327
3686
|
for (const m of c.text.matchAll(GIAO_LA)) emit(m[3], m[1], m[2]);
|
|
3328
3687
|
GIAO_DIEM.lastIndex = 0;
|
|
@@ -3406,9 +3765,6 @@ var THETA_A = 2.3;
|
|
|
3406
3765
|
var THETA_B = 0.7;
|
|
3407
3766
|
var THETA_STEP = 0.9;
|
|
3408
3767
|
var PREFILTER9 = /[Dd]ây/u;
|
|
3409
|
-
function escapeRe(s) {
|
|
3410
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3411
|
-
}
|
|
3412
3768
|
function findCircleClauseId(clauses, circle) {
|
|
3413
3769
|
const frag = new RegExp(CIRCLE_KW + `|\\(\\s*${escapeRe(circle)}\\s*\\)`, "u");
|
|
3414
3770
|
for (const c of clauses) if (frag.test(c.text)) return c.id;
|
|
@@ -3421,6 +3777,29 @@ function resolveCircle2(problem) {
|
|
|
3421
3777
|
if (p) return p[1];
|
|
3422
3778
|
return void 0;
|
|
3423
3779
|
}
|
|
3780
|
+
function inscribedTriangleVertices(problem, circle) {
|
|
3781
|
+
const out = /* @__PURE__ */ new Set();
|
|
3782
|
+
const NAMED_O = "(?:\\(\\s*([^\\s;,).:]+)\\s*\\)|t\xE2m\\s+([A-Z]))?";
|
|
3783
|
+
const TRI7 = "tam\\s*gi\xE1c\\s+(?:(?:nh\u1ECDn|c\xE2n|\u0111\u1EC1u|vu\xF4ng|t\xF9)\\s+)?([A-Z])([A-Z])([A-Z])(?![A-Z])";
|
|
3784
|
+
const RE_A = new RegExp(TRI7 + "[^.]{0,40}?n\u1ED9i\\s*ti\u1EBFp\\s+(?:trong\\s+)?(?:" + CIRCLE_KW + ")?\\s*" + NAMED_O, "gu");
|
|
3785
|
+
for (const m of problem.matchAll(RE_A)) {
|
|
3786
|
+
const named = m[4] ?? m[5];
|
|
3787
|
+
if (named && named !== circle) continue;
|
|
3788
|
+
out.add(m[1]);
|
|
3789
|
+
out.add(m[2]);
|
|
3790
|
+
out.add(m[3]);
|
|
3791
|
+
}
|
|
3792
|
+
const NAMED_O_B = "(?:\\(\\s*([^\\s;,).:]+)\\s*\\)|t\xE2m\\s+([A-Z])|([A-Z]))?";
|
|
3793
|
+
const RE_B = new RegExp(CIRCLE_KW + "\\s*" + NAMED_O_B + "\\s*ngo\u1EA1i\\s*ti\u1EBFp\\s+" + TRI7, "gu");
|
|
3794
|
+
for (const m of problem.matchAll(RE_B)) {
|
|
3795
|
+
const named = m[1] ?? m[2] ?? m[3];
|
|
3796
|
+
if (named && named !== circle) continue;
|
|
3797
|
+
out.add(m[4]);
|
|
3798
|
+
out.add(m[5]);
|
|
3799
|
+
out.add(m[6]);
|
|
3800
|
+
}
|
|
3801
|
+
return out;
|
|
3802
|
+
}
|
|
3424
3803
|
var chordRule = {
|
|
3425
3804
|
id: "chord",
|
|
3426
3805
|
// Dưới circleRadius (75) — nếu circle O có bán kính cụ thể, định nghĩa đó thắng.
|
|
@@ -3434,6 +3813,7 @@ var chordRule = {
|
|
|
3434
3813
|
match(ctx) {
|
|
3435
3814
|
const circle = resolveCircle2(ctx.problem);
|
|
3436
3815
|
if (!circle) return [];
|
|
3816
|
+
const inscribedVerts = inscribedTriangleVertices(ctx.problem, circle);
|
|
3437
3817
|
const chords = [];
|
|
3438
3818
|
const seen = /* @__PURE__ */ new Set();
|
|
3439
3819
|
for (const c of ctx.clauses) {
|
|
@@ -3468,15 +3848,11 @@ var chordRule = {
|
|
|
3468
3848
|
});
|
|
3469
3849
|
chords.forEach((ch, k) => {
|
|
3470
3850
|
const offset = k * THETA_STEP;
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
addPoint(ch.b, { kind: "onCircle", circle, theta: THETA_B + offset }),
|
|
3477
|
-
connect(ch.a, ch.b, "segment")
|
|
3478
|
-
]
|
|
3479
|
-
});
|
|
3851
|
+
const intents = [];
|
|
3852
|
+
if (!inscribedVerts.has(ch.a)) intents.push(addPoint(ch.a, { kind: "onCircle", circle, theta: THETA_A + offset }));
|
|
3853
|
+
if (!inscribedVerts.has(ch.b)) intents.push(addPoint(ch.b, { kind: "onCircle", circle, theta: THETA_B + offset }));
|
|
3854
|
+
intents.push(connect(ch.a, ch.b, "segment"));
|
|
3855
|
+
out.push({ ruleId: "chord", clauseIds: [ch.clauseId], intents });
|
|
3480
3856
|
});
|
|
3481
3857
|
return out;
|
|
3482
3858
|
}
|
|
@@ -3486,9 +3862,13 @@ var chordRule = {
|
|
|
3486
3862
|
var PREFILTER10 = /tiếp\s*xúc|tiếp\s*điểm/u;
|
|
3487
3863
|
var TANGENT_NAMED_FWD = /tiếp\s*điểm\s+(?:của\s+)?(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)\s+với\s+(?:các\s+)?(?:(?:cạnh|đoạn)\s+)?([A-Z]{2}(?:\s*(?:,|và)\s*[A-Z]{2})*)\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+)?là\s+(?:các\s+)?(?:điểm\s+)?([A-Z](?:\s*(?:,|và)\s*[A-Z])*)(?![A-Za-z])/iu;
|
|
3488
3864
|
var TANGENT_NAMED_REV = /tiếp\s*điểm\s+(?:của\s+)?(?:các\s+)?(?:(?:cạnh|đoạn)\s+)?([A-Z]{2}(?:\s*(?:,|và)\s*[A-Z]{2})*)\s+với\s+(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+)?là\s+(?:các\s+)?(?:điểm\s+)?([A-Z](?:\s*(?:,|và)\s*[A-Z])*)(?![A-Za-z])/iu;
|
|
3865
|
+
var TANGENT_NAMED_BEFORE = /([A-Z](?:\s*(?:,|và)\s*[A-Z])*)\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+)?là\s+(?:các\s+)?(?:điểm\s+)?tiếp\s*điểm\s+(?:của\s+)?(?:các\s+)?(?:(?:cạnh|đoạn)\s+)?([A-Z]{2}(?:\s*(?:,|và)\s*[A-Z]{2})*)\s+với\s+(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)/iu;
|
|
3489
3866
|
var INCIRCLE_IN_CLAUSE = /đường\s*tròn\s*(?:\(\s*([A-Z])\s*\)|tâm\s+([A-Z]))?\s*nội\s*tiếp\s+tam\s*giác(?:\s+([A-Z])([A-Z])([A-Z])(?![A-Z]))?/iu;
|
|
3490
3867
|
var SIDE_POINT_LIST = /tiếp\s*xúc\s+(?:với\s+)?(?:các\s+|ba\s+|hai\s+|bốn\s+)?(?:(?:cạnh|đoạn)\s+)?([A-Z]{2}(?:\s*(?:,|và)\s*[A-Z]{2})*)\s+(?:lần\s*lượt\s+|tương\s*ứng\s+)?tại\s+(?:các\s+|ba\s+|hai\s+|bốn\s+)?(?:điểm\s+)?([A-Z]'?(?:\s*(?:,|và)\s*[A-Z]'?)*)(?![A-Za-z])/iu;
|
|
3491
3868
|
var REVERSED_SIDE_POINT = /(?:các\s+)?(?:cạnh|đoạn)\s+([A-Z]{2}(?:\s*(?:,|và)\s*[A-Z]{2})*)\s+tiếp\s*xúc\s+(?:với\s+)?(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)\s+(?:lần\s*lượt\s+|tương\s*ứng\s+)?tại\s+(?:các\s+)?(?:điểm\s+)?([A-Z]'?(?:\s*(?:,|và)\s*[A-Z]'?)*)(?![A-Za-z])/iu;
|
|
3869
|
+
var INTERLEAVED_CIRCLE = /(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*[,;]?\s*[Rr]?\s*\)\s*tiếp\s*xúc\s+(?:với\s+)?(?:(?:cạnh|đoạn)\s+)?([A-Z]{2}\s+tại\s+[A-Z]'?(?:\s*,\s*[A-Z]{2}\s+tại\s+[A-Z]'?)+)(?![A-Za-z])/iu;
|
|
3870
|
+
var PAIR_RE = /([A-Z]{2})\s+tại\s+([A-Z]'?)/giu;
|
|
3871
|
+
var REVERSED_BARE = /(?<![A-Za-z])([A-Z]{2}(?:\s*(?:,|và)\s*[A-Z]{2})*)\s+tiếp\s*xúc\s+(?:với\s+)?(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)\s+(?:lần\s*lượt\s+|tương\s*ứng\s+)?tại\s+(?:các\s+)?(?:điểm\s+)?([A-Z]'?(?:\s*(?:,|và)\s*[A-Z]'?)*)(?![A-Za-z])/iu;
|
|
3492
3872
|
function splitCsv(blob) {
|
|
3493
3873
|
return blob.split(/,|và/iu).map((s) => s.trim()).filter(Boolean);
|
|
3494
3874
|
}
|
|
@@ -3526,6 +3906,22 @@ var incircleTangencyRule = {
|
|
|
3526
3906
|
match(ctx) {
|
|
3527
3907
|
const out = [];
|
|
3528
3908
|
for (const chunk of logicalChunks(ctx.problem, ctx.clauses)) {
|
|
3909
|
+
const tb = TANGENT_NAMED_BEFORE.exec(chunk.text);
|
|
3910
|
+
if (tb) {
|
|
3911
|
+
const names = splitCsv(tb[1]);
|
|
3912
|
+
const sides2 = splitCsv(tb[2]);
|
|
3913
|
+
const circle2 = tb[3];
|
|
3914
|
+
if (sides2.length >= 1 && sides2.length === names.length) {
|
|
3915
|
+
const verts2 = [...new Set(sides2.join("").split(""))];
|
|
3916
|
+
const intents2 = [];
|
|
3917
|
+
if (verts2.length === 3) intents2.push(drawCircle(circle2, "inscribedIn", { triangle: verts2 }));
|
|
3918
|
+
for (let i = 0; i < names.length; i++) {
|
|
3919
|
+
intents2.push(addPoint(names[i], { kind: "tangencyPoint", circle: circle2, onLine: sides2[i] }));
|
|
3920
|
+
}
|
|
3921
|
+
out.push({ ruleId: "incircleTangency", clauseIds: chunk.clauseIds, intents: intents2 });
|
|
3922
|
+
continue;
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3529
3925
|
const tn = TANGENT_NAMED_FWD.exec(chunk.text) ?? TANGENT_NAMED_REV.exec(chunk.text);
|
|
3530
3926
|
if (tn) {
|
|
3531
3927
|
const fwd = TANGENT_NAMED_FWD.test(chunk.text);
|
|
@@ -3543,6 +3939,20 @@ var incircleTangencyRule = {
|
|
|
3543
3939
|
continue;
|
|
3544
3940
|
}
|
|
3545
3941
|
}
|
|
3942
|
+
const il = INTERLEAVED_CIRCLE.exec(chunk.text);
|
|
3943
|
+
if (il) {
|
|
3944
|
+
const circle2 = il[1];
|
|
3945
|
+
const pairs = [];
|
|
3946
|
+
PAIR_RE.lastIndex = 0;
|
|
3947
|
+
for (const pm of il[2].matchAll(PAIR_RE)) pairs.push([pm[1], pm[2]]);
|
|
3948
|
+
if (pairs.length >= 1) {
|
|
3949
|
+
const intents2 = pairs.map(
|
|
3950
|
+
([side, name]) => addPoint(name, { kind: "tangencyPoint", circle: circle2, onLine: side })
|
|
3951
|
+
);
|
|
3952
|
+
out.push({ ruleId: "incircleTangency", clauseIds: chunk.clauseIds, intents: intents2 });
|
|
3953
|
+
continue;
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3546
3956
|
const rev = REVERSED_SIDE_POINT.exec(chunk.text);
|
|
3547
3957
|
if (rev) {
|
|
3548
3958
|
const sides2 = splitCsv(rev[1]);
|
|
@@ -3561,6 +3971,19 @@ var incircleTangencyRule = {
|
|
|
3561
3971
|
continue;
|
|
3562
3972
|
}
|
|
3563
3973
|
}
|
|
3974
|
+
const rb = REVERSED_BARE.exec(chunk.text);
|
|
3975
|
+
if (rb) {
|
|
3976
|
+
const sides2 = splitCsv(rb[1]);
|
|
3977
|
+
const circle2 = rb[2];
|
|
3978
|
+
const names = splitCsv(rb[3]);
|
|
3979
|
+
if (sides2.length >= 1 && sides2.length === names.length) {
|
|
3980
|
+
const intents2 = sides2.map(
|
|
3981
|
+
(side, i) => addPoint(names[i], { kind: "tangencyPoint", circle: circle2, onLine: side })
|
|
3982
|
+
);
|
|
3983
|
+
out.push({ ruleId: "incircleTangency", clauseIds: chunk.clauseIds, intents: intents2 });
|
|
3984
|
+
continue;
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3564
3987
|
let circle = parseIncircleName(chunk.text);
|
|
3565
3988
|
if (!circle) {
|
|
3566
3989
|
const fm = FORWARD_CIRCLE.exec(chunk.text);
|
|
@@ -3675,16 +4098,17 @@ var altitudeDiameterCirclesRule = {
|
|
|
3675
4098
|
|
|
3676
4099
|
// src/stamps/geometry-2d/ai/rules/diameterCircleCutsSides.ts
|
|
3677
4100
|
var PATTERN = new RegExp(
|
|
3678
|
-
// Chỉ-báo đường tròn: "đường tròn (tâm X|
|
|
3679
|
-
// đường kính BC"
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
//
|
|
3683
|
-
"
|
|
4101
|
+
// Chỉ-báo đường tròn: "đường tròn ((X)|tâm X|X-trần)?" HOẶC "(X)" trần (httcd:2
|
|
4102
|
+
// "(O) đường kính BC"). "X-trần" = tâm 1-HOA không ngoặc ("đường tròn O đường
|
|
4103
|
+
// kính BC" — vxhung:14). Bắt buộc có 1 trong 2 nhánh.
|
|
4104
|
+
"(?:" + CIRCLE_KW + "\\s+(?:(?:\\(\\s*[A-Z]\\s*\\)|t\xE2m\\s+[A-Z]|[A-Z])\\s+)?|\\(\\s*[A-Z]\\s*\\)\\s+)" + DUONG_KW + // optional phẩy + đại từ "nó" giữa diameter và "cắt" (vao10:81 "AH, cắt…").
|
|
4105
|
+
// "theo thứ tự" = "lần lượt". Anchor điểm = "tại"; "(các)? (điểm)?" trước danh
|
|
4106
|
+
// sách tên ("tại các điểm D và E" — vxhung:6).
|
|
4107
|
+
"\\s*k\xEDnh\\s+([A-Z])([A-Z])(?![A-Z])\\s*,?\\s*(?:n\xF3\\s+)?\\s*c\u1EAFt\\s+([^.;\\n]*?)\\s+(?:(?:l\u1EA7n\\s*l\u01B0\u1EE3t|theo\\s+th\u1EE9\\s+t\u1EF1)\\s+)?t\u1EA1i\\s+(?:c\xE1c\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z](?!\\p{L})(?:\\s*(?:,|v\xE0)\\s*[A-Z](?!\\p{L}))*)",
|
|
3684
4108
|
"gu"
|
|
3685
4109
|
);
|
|
3686
4110
|
var PATTERN_O = new RegExp(
|
|
3687
|
-
"(?:" + CIRCLE_KW + "\\s+(?:(?:\\(\\s*[A-Z]\\s*\\)|t\xE2m\\s+[A-Z])\\s+)?|\\(\\s*[A-Z]\\s*\\)\\s+)" + DUONG_KW + "\\s*k\xEDnh\\s+([A-Z])([A-Z])(?![A-Z])\\s*,?\\s*(?:n\xF3\\s+)?\\s*c\u1EAFt\\s+([^.;\\n]*?)\\s+(?:(?:l\u1EA7n\\s*l\u01B0\u1EE3t|theo\\s+th\u1EE9\\s+t\u1EF1)\\s+)?\u1EDF\\s+([A-Z](?!\\p{L})(?:\\s*(?:,|v\xE0)\\s*[A-Z](?!\\p{L}))+)",
|
|
4111
|
+
"(?:" + CIRCLE_KW + "\\s+(?:(?:\\(\\s*[A-Z]\\s*\\)|t\xE2m\\s+[A-Z]|[A-Z])\\s+)?|\\(\\s*[A-Z]\\s*\\)\\s+)" + DUONG_KW + "\\s*k\xEDnh\\s+([A-Z])([A-Z])(?![A-Z])\\s*,?\\s*(?:n\xF3\\s+)?\\s*c\u1EAFt\\s+([^.;\\n]*?)\\s+(?:(?:l\u1EA7n\\s*l\u01B0\u1EE3t|theo\\s+th\u1EE9\\s+t\u1EF1)\\s+)?\u1EDF\\s+(?:c\xE1c\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z](?!\\p{L})(?:\\s*(?:,|v\xE0)\\s*[A-Z](?!\\p{L}))+)",
|
|
3688
4112
|
"gu"
|
|
3689
4113
|
);
|
|
3690
4114
|
var PATTERN_INTERLEAVED = new RegExp(
|
|
@@ -3783,6 +4207,10 @@ var GIVEN_BARE = new RegExp(
|
|
|
3783
4207
|
String.raw`[Cc]ho\s+\(\s*(${CTR})\s*\)\s*,?\s*(?:có\s+)?` + DUONG_KW + String.raw`\s*kính\s+([A-Z])([A-Z])(?![A-Z])`,
|
|
3784
4208
|
"gu"
|
|
3785
4209
|
);
|
|
4210
|
+
var CHO_BARE = new RegExp(
|
|
4211
|
+
String.raw`[Cc]ho\s+(${CTR})(?![A-Z\p{L}])\s+` + DUONG_KW + String.raw`\s*kính\s+([A-Z])([A-Z])(?![A-Z])`,
|
|
4212
|
+
"gu"
|
|
4213
|
+
);
|
|
3786
4214
|
var BARE_PAREN = new RegExp(
|
|
3787
4215
|
String.raw`\(\s*(${CTR})\s*\)\s*(?:có\s+)?` + DUONG_KW + String.raw`\s*kính\s+([A-Z])([A-Z])(?![A-Z])`,
|
|
3788
4216
|
"gu"
|
|
@@ -3873,6 +4301,22 @@ var circleDiameterRule = {
|
|
|
3873
4301
|
intents: intentsFor(p)
|
|
3874
4302
|
});
|
|
3875
4303
|
}
|
|
4304
|
+
CHO_BARE.lastIndex = 0;
|
|
4305
|
+
let cm;
|
|
4306
|
+
while ((cm = CHO_BARE.exec(ctx.problem)) !== null) {
|
|
4307
|
+
const p = { center: cm[1], a: cm[2], b: cm[3] };
|
|
4308
|
+
if (!p.center || p.center === p.a || p.center === p.b || p.a === p.b) continue;
|
|
4309
|
+
const key = `${p.center}|${p.a}${p.b}`;
|
|
4310
|
+
if (emitted.has(key)) continue;
|
|
4311
|
+
emitted.add(key);
|
|
4312
|
+
const matched = cm[0];
|
|
4313
|
+
const claim = ctx.clauses.filter((c) => c.text.includes(matched.replace(/^[Cc]ho\s+/u, ""))).map((c) => c.id);
|
|
4314
|
+
out.push({
|
|
4315
|
+
ruleId: "circle-diameter",
|
|
4316
|
+
clauseIds: claim.length > 0 ? claim : [ctx.clauses[0]?.id ?? 0],
|
|
4317
|
+
intents: intentsFor(p)
|
|
4318
|
+
});
|
|
4319
|
+
}
|
|
3876
4320
|
BARE_PAREN.lastIndex = 0;
|
|
3877
4321
|
let bm;
|
|
3878
4322
|
while ((bm = BARE_PAREN.exec(ctx.problem)) !== null) {
|
|
@@ -3883,7 +4327,7 @@ var circleDiameterRule = {
|
|
|
3883
4327
|
const key = `${center}|${a}${b}`;
|
|
3884
4328
|
if (emitted.has(key)) continue;
|
|
3885
4329
|
const before = ctx.problem.slice(0, bm.index);
|
|
3886
|
-
const known = (p) => new RegExp(`(?<![a-z])${p}(?![a-z])`, "u").test(before);
|
|
4330
|
+
const known = (p) => new RegExp(`(?<![a-z])${escapeRe(p)}(?![a-z])`, "u").test(before);
|
|
3887
4331
|
if (!known(a) || !known(b)) continue;
|
|
3888
4332
|
emitted.add(key);
|
|
3889
4333
|
const matched = bm[0];
|
|
@@ -3905,7 +4349,7 @@ var circleDiameterRule = {
|
|
|
3905
4349
|
var PREFILTER12 = /(?:nằm|thuộc|lấy\s+điểm|trên\s+(?:nửa\s+)?(?:đường\s*tròn|cung))/iu;
|
|
3906
4350
|
var NAMED_CIRCLE = /(?:nửa\s+)?đường\s*tròn\s*(?:tâm\s+)?\(?\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)?/u;
|
|
3907
4351
|
var COMPACT_CIRCLE = /\(\s*([A-Z])\s*[;,]\s*[Rr]\s*\)/u;
|
|
3908
|
-
var ON_SUFFIX = "(?:cung\\s+(?:nh\u1ECF\\s+|l\u1EDBn\\s+)?[A-Z]{2}(?:\\s*(?:nh\u1ECF|l\u1EDBn))?|(?:n\u1EEDa\\s+)?\u0111\u01B0\u1EDDng\\s*tr\xF2n
|
|
4352
|
+
var ON_SUFFIX = "(?:cung\\s+(?:nh\u1ECF\\s+|l\u1EDBn\\s+)?[A-Z]{2}(?:\\s*(?:nh\u1ECF|l\u1EDBn))?|(?:n\u1EEDa\\s+)?\u0111\u01B0\u1EDDng\\s*tr\xF2n|(?:\u0111\u01B0\u1EDDng\\s+)?\\(\\s*[A-Z]\\s*\\))";
|
|
3909
4353
|
var POINT_ON = new RegExp(
|
|
3910
4354
|
// "nằm" optional → bắt "M (bất kì)? trên (nửa)? đường tròn" / "điểm A trên (O)"
|
|
3911
4355
|
// (chỉ "trên" trần, không "nằm trên"). ON_SUFFIX neo circle/cung nên "trên"
|
|
@@ -3926,18 +4370,21 @@ var TAKE_ON_CUNG = /[Tt]rên\s+cung\s+(?:nhỏ\s+|lớn\s+)?[A-Z]{2}(?:\s*(?:nh
|
|
|
3926
4370
|
var TAKE_ON_REV = /[Tt]rên\s+(?:nửa\s+)?(?:đường\s*tròn|cung)\s*\(\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)[^.]{0,16}?lấy\s+điểm\s+([A-Z])(?![A-Z])/u;
|
|
3927
4371
|
var TWO_ON = /(?:lấy\s+)?hai\s*điểm\s+([A-Z])(?![A-Za-z])\s+và\s+([A-Z])(?![A-Za-z])(?:\s+bất\s*k[ìiyỳ])?\s+thuộc\s+(?:nửa\s+)?(?:đường\s*tròn|cung)/u;
|
|
3928
4372
|
var TWO_ON_COMMA = /(?:[CcNn]ác\s+|[Nn]hững\s+)?điểm\s+([A-Z])(?![A-Za-z])\s*,\s*([A-Z])(?![A-Za-z])\s+thuộc\s+(?:nửa\s+)?(?:đường\s*tròn|cung)/u;
|
|
3929
|
-
var TWO_ON_NAMES = /([A-Z])(?![A-Za-z])\s
|
|
4373
|
+
var TWO_ON_NAMES = /([A-Z])(?![A-Za-z])\s*(?:,|và)\s*([A-Z])(?![A-Za-z])\s+là\s+(?:hai|các)\s+điểm\s+(?:thuộc|nằm\s+trên|trên)\s+(?:(?:nửa\s+)?(?:đường\s*tròn|cung)|\(\s*[A-Z]\s*\))/u;
|
|
3930
4374
|
var TWO_ON_CUNG_REV = /[Tt]rên\s+cung\s+(?:nhỏ\s+|lớn\s+)?[A-Z]{2}(?:\s*(?:nhỏ|lớn))?\s+lấy\s+hai\s+điểm\s+([A-Z])(?![A-Za-z])\s*(?:,|và)\s*([A-Z])(?![A-Za-z])/u;
|
|
3931
4375
|
var BARE_CIRCLE = /\(\s*([A-Z])\s*\)/u;
|
|
3932
4376
|
var UNNAMED_DIAMETER = /(?:nửa\s+)?đường\s*tròn\s+đường\s*kính\s+([A-Z])([A-Z])(?![A-Z])/u;
|
|
4377
|
+
var BARE_CENTER_DIAMETER = /(?<![\p{L}\d(])([A-Z])\s+đường\s*kính/u;
|
|
3933
4378
|
function resolveCircle3(problem) {
|
|
3934
4379
|
const m = NAMED_CIRCLE.exec(problem) ?? COMPACT_CIRCLE.exec(problem) ?? BARE_CIRCLE.exec(problem);
|
|
3935
4380
|
if (!m) {
|
|
3936
4381
|
const dm = UNNAMED_DIAMETER.exec(problem);
|
|
3937
|
-
|
|
4382
|
+
if (dm) return `k${dm[1]}${dm[2]}`;
|
|
4383
|
+
const bd = BARE_CENTER_DIAMETER.exec(problem);
|
|
4384
|
+
return bd ? `${bd[1]}_c` : void 0;
|
|
3938
4385
|
}
|
|
3939
4386
|
const center = m[1];
|
|
3940
|
-
const c = center
|
|
4387
|
+
const c = escapeRe(center);
|
|
3941
4388
|
const diameterOfThisCenter = new RegExp(`(?:\\(\\s*${c}\\s*[;,)]|t\xE2m\\s+${c}(?![A-Za-z]))[^.]{0,25}?\u0111\u01B0\u1EDDng\\s*k\xEDnh`, "u").test(problem) || new RegExp(`\u0111\u01B0\u1EDDng\\s*k\xEDnh[^.]{0,25}?\\(\\s*${c}\\s*\\)`, "u").test(problem);
|
|
3942
4389
|
return diameterOfThisCenter ? `${center}_c` : center;
|
|
3943
4390
|
}
|
|
@@ -3982,10 +4429,12 @@ var onCirclePointRule = {
|
|
|
3982
4429
|
if (!m) continue;
|
|
3983
4430
|
const name = m[1];
|
|
3984
4431
|
if (name.length !== 1) continue;
|
|
4432
|
+
const major = /cung\s+lớn|lớn\s+[A-Z]{2}/u.test(c.text);
|
|
4433
|
+
const ptTheta = major ? -1.6 : theta;
|
|
3985
4434
|
out.push({
|
|
3986
4435
|
ruleId: "on-circle-point",
|
|
3987
4436
|
clauseIds: [c.id],
|
|
3988
|
-
intents: [addPoint(name, { kind: "onCircle", circle, theta })]
|
|
4437
|
+
intents: [addPoint(name, { kind: "onCircle", circle, theta: ptTheta })]
|
|
3989
4438
|
});
|
|
3990
4439
|
theta += 0.8;
|
|
3991
4440
|
}
|
|
@@ -3994,10 +4443,10 @@ var onCirclePointRule = {
|
|
|
3994
4443
|
};
|
|
3995
4444
|
|
|
3996
4445
|
// src/stamps/geometry-2d/ai/rules/tangentAt.ts
|
|
3997
|
-
var PREFILTER13 = /[Tt]iếp\s*tuyến\s+(?:với\s+|của\s+)?(?:đường\s*tròn\s*)?(?:\([^)]*\)\s*)?tại/u;
|
|
4446
|
+
var PREFILTER13 = /[Tt]iếp\s*tuyến\s+(?:với\s+|của\s+)?(?:đường\s*tròn\s*)?(?:\([^)]*\)\s*)?(?:tại|qua)/u;
|
|
3998
4447
|
var CIRCLE_REF = /đường\s*tròn\s*\(\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)|\(\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)/u;
|
|
3999
4448
|
var DISTRIB3 = /[Tt]iếp\s*tuyến\s+tại\s+([A-Z])\s+cắt\s+tiếp\s*tuyến\s+tại\s+([A-Z])\s+và\s+([A-Z])[^.]{0,60}?lần\s*lượt\s+tại\s+([A-Z])\s+và\s+([A-Z])/u;
|
|
4000
|
-
var TWO_MEET = /[Tt]iếp\s*tuyến\s+(?:với\s+|của\s+)?(?:đường\s*tròn\s*)?(?:\(\s*[A-Z]['′]?\s*\)\s*)?tại\s+([A-Z])\s*(?:,|và)\s*([A-Z])(?![A-Z])[^.]{0,30}?cắt\s+nhau\s+(?:ở|tại)\s+([A-Z])(?![A-Z])/u;
|
|
4449
|
+
var TWO_MEET = /[Tt]iếp\s*tuyến\s+(?:với\s+|của\s+)?(?:đường\s*tròn\s*)?(?:\(\s*[A-Z]['′]?\s*\)\s*)?(?:tại|qua)\s+([A-Z])\s*(?:,|và)\s*([A-Z])(?![A-Z])[^.]{0,30}?cắt\s+nhau\s+(?:ở|tại)\s+([A-Z])(?![A-Z])/u;
|
|
4001
4450
|
var SINGLE2 = /[Tt]iếp\s*tuyến\s+tại\s+(?:điểm\s+)?([A-Z])(?:\s+với|\s+của|\s+đường|\s*$)/u;
|
|
4002
4451
|
function circleName(problem) {
|
|
4003
4452
|
const m = CIRCLE_REF.exec(problem);
|
|
@@ -4068,17 +4517,37 @@ var tangentAtRule = {
|
|
|
4068
4517
|
};
|
|
4069
4518
|
|
|
4070
4519
|
// src/stamps/geometry-2d/ai/rules/onSegmentPoint.ts
|
|
4071
|
-
var PREFILTER14 = /(?:thuộc\s+(?:các\s+|hai\s+|ba\s+)?(?:cạnh|đoạn|đáy|tia|bán\s*kính|đường\s*kính|dây|[A-
|
|
4520
|
+
var PREFILTER14 = /(?:thuộc\s+(?:các\s+|hai\s+|ba\s+)?(?:cạnh|đoạn|đáy|tia|bán\s*kính|đường\s*kính|dây|[A-Za-z]{1,2}[0-9]?(?![\p{L}\d]))|[Tt]rên\s+(?:các\s+|hai\s+|ba\s+)?(?:cạnh|đoạn|đáy|tia|bán\s*kính|đường\s*kính|đường\s*thẳng|dây|[A-Za-z]{1,2}[0-9]?(?![\p{L}\d]))|(?:di\s*chuyển|di\s*động)\s+trên|nằm\s+giữa)/u;
|
|
4072
4521
|
var ON_NAMED_LINE = new RegExp(
|
|
4073
4522
|
String.raw`[Tt]rên\s+đường\s*thẳng\s+([a-z][0-9]?)(?!\p{L})[^.]{0,20}?(?:lấy\s+)?(?:một\s+)?(?:điểm\s+)?([A-Z](?:['′])?)(?![A-Z])`,
|
|
4074
4523
|
"gu"
|
|
4075
4524
|
);
|
|
4525
|
+
var ON_NAMED_LINE_BARE = new RegExp(
|
|
4526
|
+
String.raw`[Tt]rên\s+([a-z]{1,2}[0-9]?)(?![\p{L}\d])\s+(?:lấy\s+)?(?:một\s+)?(?:điểm\s+)?([A-Z](?:['′])?)(?![A-Z])`,
|
|
4527
|
+
"gu"
|
|
4528
|
+
);
|
|
4529
|
+
var POINT_THUOC_NAMED_BARE = new RegExp(
|
|
4530
|
+
String.raw`(?:điểm\s+)?([A-Z](?:['′])?)(?![A-Z])\s+(?:là\s+(?:một\s+)?điểm\s+(?:bất\s*k[iìyỳ]\s+)?)?thuộc\s+([a-z]{1,2}[0-9]?)(?![\p{L}\d])`,
|
|
4531
|
+
"gu"
|
|
4532
|
+
);
|
|
4076
4533
|
var SEG = "([A-Z]{2})(?![A-Z])";
|
|
4077
4534
|
var POINT = "([A-Z](?:['\u2032])?)(?![A-Z])";
|
|
4535
|
+
var TAKE_POINT_ON_SEG = new RegExp(
|
|
4536
|
+
String.raw`[Ll]ấy\s+(?:điểm\s+)?${POINT}\s+(?:bất\s*k[iìyỳ]\s+)?(?:trên|thuộc)\s+(?:cạnh\s+|đáy\s+|đoạn(?:\s+thẳng)?\s+|tia\s+|bán\s*kính\s+|đường\s*kính\s+)?${SEG}`,
|
|
4537
|
+
"gu"
|
|
4538
|
+
);
|
|
4539
|
+
var TAKE_TWO_ON_TWO_SEG = new RegExp(
|
|
4540
|
+
String.raw`[Ll]ấy\s+(?:điểm\s+)?([A-Z](?:['′])?)\s*,\s*([A-Z](?:['′])?)(?![A-Z])\s+(?:bất\s*k[iìyỳ]\s+)?(?:trên|thuộc)\s+(?:các\s+)?(?:cạnh\s+|đoạn\s+|tia\s+)?([A-Z]{2})\s*(?:,|và)\s*([A-Z]{2})(?![A-Z])`,
|
|
4541
|
+
"gu"
|
|
4542
|
+
);
|
|
4078
4543
|
var ON_SEG_THEN_POINT = new RegExp(
|
|
4079
4544
|
String.raw`[Tt]rên\s+(?:cạnh|đáy|đoạn(?:\s+thẳng)?|bán\s*kính|đường\s*kính|dây\s*(?:cung)?)\s+${SEG}[^.]{0,30}?(?:lấy\s+)?(?:một\s+)?(?:điểm\s+)?${POINT}`,
|
|
4080
4545
|
"gu"
|
|
4081
4546
|
);
|
|
4547
|
+
var ON_BARE_SEG_THEN_POINT = new RegExp(
|
|
4548
|
+
String.raw`[Tt]rên\s+${SEG}[^.]{0,14}?\blấy\s+(?:một\s+)?(?:điểm\s+)?${POINT}`,
|
|
4549
|
+
"gu"
|
|
4550
|
+
);
|
|
4082
4551
|
var ON_SEG_MULTI_POINT = new RegExp(
|
|
4083
4552
|
String.raw`[Tt]rên\s+(?:cạnh\s+|đáy\s+|đoạn(?:\s+thẳng)?\s+)?${SEG}\s+(?:lấy\s+)?(?:các\s+)?(?:điểm\s+)?((?:[A-Z](?:['′])?\s*,\s*)+[A-Z](?:['′])?)(?![A-Z])`,
|
|
4084
4553
|
"gu"
|
|
@@ -4112,13 +4581,17 @@ var ZIP_SEG = new RegExp(
|
|
|
4112
4581
|
"gu"
|
|
4113
4582
|
);
|
|
4114
4583
|
var SEGS_THEN_POINTS = new RegExp(
|
|
4115
|
-
String.raw`[Tt]rên\s+(?:các\s+)?(?:cạnh\s+|đoạn(?:\s+thẳng)?\s+|đáy\s+|bán\s*kính\s+)?((?:[A-Z]{2}\s*,\s*)+[A-Z]{2})(?![A-Z])(?:\s+của\s+(?:tam\s*giác\s+|tứ\s*giác\s+)?[A-Z]{2,4})?\s+(?:(?:theo\s+)?thứ\s+tự\s+|lần\s*lượt\s+)?lấy\s+(?:các\s+)
|
|
4584
|
+
String.raw`[Tt]rên\s+(?:các\s+)?(?:cạnh\s+|đoạn(?:\s+thẳng)?\s+|đáy\s+|bán\s*kính\s+)?((?:[A-Z]{2}\s*,\s*)+[A-Z]{2})(?![A-Z])(?:\s+của\s+(?:tam\s*giác\s+|tứ\s*giác\s+)?[A-Z]{2,4})?\s+(?:(?:theo\s+)?thứ\s+tự\s+|lần\s*lượt\s+)?lấy\s+(?:các\s+)?(?:điểm\s+)?((?:[A-Z]\s*,\s*)+[A-Z])(?![A-Z])`,
|
|
4116
4585
|
"gu"
|
|
4117
4586
|
);
|
|
4118
4587
|
var TWO_SEG_LAY = new RegExp(
|
|
4119
4588
|
String.raw`[Tt]rên\s+(?:cạnh|đáy|đoạn(?:\s+thẳng)?)\s+${SEG}\s+(?:lấy\s+)?(?:một\s+)?(?:điểm\s+)?${POINT}\s+và\s+(?:trên\s+)?(?:cạnh|đáy|đoạn(?:\s+thẳng)?)\s+${SEG}\s+(?:lấy\s+)?(?:một\s+)?(?:điểm\s+)?${POINT}`,
|
|
4120
4589
|
"gu"
|
|
4121
4590
|
);
|
|
4591
|
+
var TWO_ON_NAMED_RAY = new RegExp(
|
|
4592
|
+
String.raw`[Tt]rên\s+(?:tia\s+)?([A-Z][xyzt])(?!\p{L})\s+(?:lấy\s+)?(?:hai\s+|2\s+)?(?:điểm\s+)?([A-Z](?:['′])?)(?![A-Z])\s*(?:,|và)\s*([A-Z](?:['′])?)(?![A-Z])`,
|
|
4593
|
+
"gu"
|
|
4594
|
+
);
|
|
4122
4595
|
function normalizePoint(name) {
|
|
4123
4596
|
return name.replace("\u2032", "'");
|
|
4124
4597
|
}
|
|
@@ -4128,6 +4601,18 @@ function validOnSegment(name, segment) {
|
|
|
4128
4601
|
function hasMetricConstraint(text) {
|
|
4129
4602
|
return /sao\s+cho[^.]{0,40}(?:=|>|<)/u.test(text);
|
|
4130
4603
|
}
|
|
4604
|
+
function declaredNamedLines3(problem) {
|
|
4605
|
+
const out = /* @__PURE__ */ new Set();
|
|
4606
|
+
const RE31 = /(?:đường\s*thẳng|tiếp\s*tuyến|cát\s*tuyến)\s+([a-z]{1,2}[0-9]?)(?![\p{L}\d])/gu;
|
|
4607
|
+
for (const m of problem.matchAll(RE31)) out.add(m[1]);
|
|
4608
|
+
return out;
|
|
4609
|
+
}
|
|
4610
|
+
function declaredNamedRays(problem) {
|
|
4611
|
+
const out = /* @__PURE__ */ new Set();
|
|
4612
|
+
const RE31 = /tiếp\s*tuyến\s+([A-Z][xyzt])(?![A-Za-z])/gu;
|
|
4613
|
+
for (const m of problem.matchAll(RE31)) out.add(m[1]);
|
|
4614
|
+
return out;
|
|
4615
|
+
}
|
|
4131
4616
|
var onSegmentPointRule = {
|
|
4132
4617
|
id: "on-segment-point",
|
|
4133
4618
|
priority: 62,
|
|
@@ -4135,8 +4620,73 @@ var onSegmentPointRule = {
|
|
|
4135
4620
|
patterns: [PREFILTER14],
|
|
4136
4621
|
match(ctx) {
|
|
4137
4622
|
const out = [];
|
|
4623
|
+
const namedLines = declaredNamedLines3(ctx.problem);
|
|
4624
|
+
const namedRays = declaredNamedRays(ctx.problem);
|
|
4138
4625
|
for (const c of ctx.clauses) {
|
|
4139
4626
|
const intents = [];
|
|
4627
|
+
ON_NAMED_LINE.lastIndex = 0;
|
|
4628
|
+
for (const m of c.text.matchAll(ON_NAMED_LINE)) {
|
|
4629
|
+
const line = m[1];
|
|
4630
|
+
const name = normalizePoint(m[2]);
|
|
4631
|
+
if (/^[A-Z]['′]?$/u.test(name)) intents.push(addPoint(name, { kind: "onSegment", of: line }));
|
|
4632
|
+
}
|
|
4633
|
+
if (namedLines.size > 0) {
|
|
4634
|
+
ON_NAMED_LINE_BARE.lastIndex = 0;
|
|
4635
|
+
for (const m of c.text.matchAll(ON_NAMED_LINE_BARE)) {
|
|
4636
|
+
const line = m[1];
|
|
4637
|
+
const name = normalizePoint(m[2]);
|
|
4638
|
+
if (namedLines.has(line) && /^[A-Z]['′]?$/u.test(name)) {
|
|
4639
|
+
intents.push(addPoint(name, { kind: "onSegment", of: line }));
|
|
4640
|
+
}
|
|
4641
|
+
}
|
|
4642
|
+
POINT_THUOC_NAMED_BARE.lastIndex = 0;
|
|
4643
|
+
for (const m of c.text.matchAll(POINT_THUOC_NAMED_BARE)) {
|
|
4644
|
+
const name = normalizePoint(m[1]);
|
|
4645
|
+
const line = m[2];
|
|
4646
|
+
if (namedLines.has(line) && /^[A-Z]['′]?$/u.test(name)) {
|
|
4647
|
+
intents.push(addPoint(name, { kind: "onSegment", of: line }));
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
if (namedRays.size > 0) {
|
|
4652
|
+
TWO_ON_NAMED_RAY.lastIndex = 0;
|
|
4653
|
+
for (const m of c.text.matchAll(TWO_ON_NAMED_RAY)) {
|
|
4654
|
+
const ray = m[1];
|
|
4655
|
+
if (!namedRays.has(ray)) continue;
|
|
4656
|
+
for (const raw of [m[2], m[3]]) {
|
|
4657
|
+
const name = normalizePoint(raw);
|
|
4658
|
+
if (/^[A-Z]['′]?$/u.test(name) && name[0] !== ray[0]) {
|
|
4659
|
+
intents.push(addPoint(name, { kind: "onSegment", of: ray }));
|
|
4660
|
+
}
|
|
4661
|
+
}
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4664
|
+
if (!/tia\s+đối/u.test(c.text)) {
|
|
4665
|
+
TAKE_TWO_ON_TWO_SEG.lastIndex = 0;
|
|
4666
|
+
for (const m of c.text.matchAll(TAKE_TWO_ON_TWO_SEG)) {
|
|
4667
|
+
const [n1, n2, s1, s2] = [normalizePoint(m[1]), normalizePoint(m[2]), m[3], m[4]];
|
|
4668
|
+
if (validOnSegment(n1, s1) && validOnSegment(n2, s2)) {
|
|
4669
|
+
intents.push(addPoint(n1, { kind: "onSegment", of: s1 }));
|
|
4670
|
+
intents.push(addPoint(n2, { kind: "onSegment", of: s2 }));
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
TAKE_POINT_ON_SEG.lastIndex = 0;
|
|
4674
|
+
for (const m of c.text.matchAll(TAKE_POINT_ON_SEG)) {
|
|
4675
|
+
const name = normalizePoint(m[1]);
|
|
4676
|
+
const segment = m[2];
|
|
4677
|
+
if (validOnSegment(name, segment)) {
|
|
4678
|
+
intents.push(addPoint(name, { kind: "onSegment", of: segment }));
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
ON_BARE_SEG_THEN_POINT.lastIndex = 0;
|
|
4682
|
+
for (const m of c.text.matchAll(ON_BARE_SEG_THEN_POINT)) {
|
|
4683
|
+
const segment = m[1];
|
|
4684
|
+
const name = normalizePoint(m[2]);
|
|
4685
|
+
if (validOnSegment(name, segment)) {
|
|
4686
|
+
intents.push(addPoint(name, { kind: "onSegment", of: segment }));
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
}
|
|
4140
4690
|
SEGS_THEN_POINTS.lastIndex = 0;
|
|
4141
4691
|
for (const m of c.text.matchAll(SEGS_THEN_POINTS)) {
|
|
4142
4692
|
const segs = m[1].split(",").map((s) => s.trim());
|
|
@@ -4196,12 +4746,6 @@ var onSegmentPointRule = {
|
|
|
4196
4746
|
if (intents.length > 0) out.push({ ruleId: "on-segment-point", clauseIds: [c.id], intents });
|
|
4197
4747
|
continue;
|
|
4198
4748
|
}
|
|
4199
|
-
ON_NAMED_LINE.lastIndex = 0;
|
|
4200
|
-
for (const m of c.text.matchAll(ON_NAMED_LINE)) {
|
|
4201
|
-
const line = m[1];
|
|
4202
|
-
const name = normalizePoint(m[2]);
|
|
4203
|
-
if (/^[A-Z]['′]?$/u.test(name)) intents.push(addPoint(name, { kind: "onSegment", of: line }));
|
|
4204
|
-
}
|
|
4205
4749
|
ON_SEG_THEN_POINT.lastIndex = 0;
|
|
4206
4750
|
for (const m of c.text.matchAll(ON_SEG_THEN_POINT)) {
|
|
4207
4751
|
const segment = m[1];
|
|
@@ -4253,7 +4797,7 @@ var onSegmentPointRule = {
|
|
|
4253
4797
|
};
|
|
4254
4798
|
|
|
4255
4799
|
// src/stamps/geometry-2d/ai/rules/lineCircleIntersection.ts
|
|
4256
|
-
var PREFILTER15 = /cắt\s+(?:lại\s+)?(?:(?:nửa\s+)?đường\s*tròn\s*)?\(|cắt\s+(?:lại\s+)?(?:nửa\s+)?đường\s*tròn\s+(?:ở|tại)|cắt\s+[A-Z]{2}\s+và\s+\(|giao\s*điểm\s+(?:thứ\s+hai\s+)?(?:của\s+|khác\s+[A-Z]\s+của\s+)?(?:đường\s*thẳng\s+)?[A-Z]{2}
|
|
4800
|
+
var PREFILTER15 = /cắt\s+(?:lại\s+)?(?:(?:nửa\s+)?đường\s*tròn\s*)?\(|cắt\s+(?:lại\s+)?(?:nửa\s+)?đường\s*tròn\s+(?:tâm\s+[A-Z](?:['′])?\s+)?(?:ở|tại)|cắt\s+[A-Z]{2}\s+và\s+\(|\(\s*[A-Z](?:['′]?)\s*(?:[;,]\s*[Rr]\s*)?\)\s+cắt\s+(?:lại\s+)?[A-Z]{2}\s*(?:,|và)?\s*(?:[A-Z]{2})?\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+|tương\s+ứng\s+)?(?:ở|tại)|\(\s*[A-Z]{3}\s*\)\s*(?:,|và)\s*\(\s*[A-Z]{3}\s*\)\s+cắt|giao\s*điểm\s+(?:thứ\s+hai\s+)?(?:của\s+|khác\s+[A-Z]\s+của\s+)?(?:đường\s*thẳng\s+)?[A-Z]{2}(?:\s*,\s*[A-Z]{2})?\s+(?:và|với)\s+(?:(?:nửa\s+)?đường\s*tròn|\(|ngoại\s*tiếp\s+tam\s*giác)/u;
|
|
4257
4801
|
var CIRCLE = String.raw`(?:(?:nửa\s+)?đường\s*tròn\s*)?\(\s*([A-Z])(?:['′]?)\s*(?:[;,]\s*[Rr]\s*)?\)`;
|
|
4258
4802
|
var CIRCLE_P = String.raw`(?:(?:nửa\s+)?đường\s*tròn\s*)?\(\s*([A-Z](?:['′])?)\s*(?:[;,]\s*[Rr]\s*)?\)`;
|
|
4259
4803
|
var PERP_CUTS_CIRCLE = new RegExp(
|
|
@@ -4269,13 +4813,21 @@ var DOUBLE_DISTRIB = new RegExp(
|
|
|
4269
4813
|
"gu"
|
|
4270
4814
|
);
|
|
4271
4815
|
var SINGLE3 = new RegExp(
|
|
4272
|
-
String.raw`([A-Z]{2})(?![A-Z])\s+cắt\s+(?:lại\s+)?` + CIRCLE + String.raw`\s+(?:ở|tại)\s+(?:điểm\s+(?:thứ\s+hai\s+)?)?(?:là\s+)?([A-Z])(?![A-Z])(?:\s+khác\s+([A-Z])(?![A-Z]))?`,
|
|
4816
|
+
String.raw`([A-Z]{2})(?![A-Z])\s+(?:kéo\s+dài\s+)?cắt\s+(?:lại\s+)?` + CIRCLE + String.raw`\s+(?:ở|tại)\s+(?:điểm\s+(?:thứ\s+hai\s+)?)?(?:là\s+)?([A-Z])(?![A-Z])(?:\s+khác\s+([A-Z])(?![A-Z]))?`,
|
|
4817
|
+
"gu"
|
|
4818
|
+
);
|
|
4819
|
+
var SINGLE_NAMED_CENTER = new RegExp(
|
|
4820
|
+
String.raw`([A-Z]{2})(?![A-Z])\s+(?:kéo\s+dài\s+)?cắt\s+(?:lại\s+)?(?:nửa\s+)?đường\s*tròn\s+tâm\s+([A-Z](?:['′])?)(?![A-Za-z])` + String.raw`\s+(?:ở|tại)\s+(?:điểm\s+(?:thứ\s+hai\s+)?)?(?:là\s+)?([A-Z])(?![A-Z])`,
|
|
4273
4821
|
"gu"
|
|
4274
4822
|
);
|
|
4275
4823
|
var BOTH = new RegExp(
|
|
4276
4824
|
String.raw`([A-Z]{2})(?![A-Z])\s+cắt\s+` + CIRCLE + String.raw`\s+(?:ở|tại)\s+(?:hai\s+|các\s+)?điểm\s+(?:phân\s*biệt\s+)?([A-Z])\s*(?:,|và)\s*([A-Z])(?![A-Z])`,
|
|
4277
4825
|
"gu"
|
|
4278
4826
|
);
|
|
4827
|
+
var TWO_NAMED = new RegExp(
|
|
4828
|
+
String.raw`([A-Z]{2})(?![A-Z])\s+(?:kéo\s+dài\s+)?cắt\s+(?:lại\s+)?` + CIRCLE + String.raw`\s+(?:ở|tại)\s+([A-Z])\s+và\s+([A-Z])(?![A-Z])`,
|
|
4829
|
+
"gu"
|
|
4830
|
+
);
|
|
4279
4831
|
var LINE_AND_CIRCLE = new RegExp(
|
|
4280
4832
|
String.raw`([A-Z]{2})(?![A-Z])\s+cắt\s+([A-Z]{2})(?![A-Z])\s+và\s+` + CIRCLE + String.raw`\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+)?(?:tại|ở)\s+([A-Z])\s*(?:,|và)\s*([A-Z])(?![A-Z])`,
|
|
4281
4833
|
"gu"
|
|
@@ -4292,12 +4844,16 @@ var CIRCLE_SUBJECT = new RegExp(
|
|
|
4292
4844
|
CIRCLE + String.raw`\s+cắt\s+(?:lại\s+)?([A-Z]{2})(?![A-Z])\s+(?:ở|tại)\s+(?:điểm\s+(?:thứ\s+hai\s+)?)?(?:là\s+)?([A-Z])(?![A-Z])`,
|
|
4293
4845
|
"gu"
|
|
4294
4846
|
);
|
|
4847
|
+
var CIRCLE_SUBJECT_TWO = new RegExp(
|
|
4848
|
+
CIRCLE + String.raw`\s+cắt\s+(?:lại\s+)?([A-Z]{2})\s*(?:,|và)\s*([A-Z]{2})(?![A-Z])\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+|tương\s+ứng\s+)?(?:ở|tại)\s+([A-Z])\s*(?:,|và)\s*([A-Z])(?![A-Z])`,
|
|
4849
|
+
"gu"
|
|
4850
|
+
);
|
|
4295
4851
|
var GIAO_CIRCLE = new RegExp(
|
|
4296
4852
|
String.raw`giao\s*điểm\s+(?:thứ\s+hai\s+)?(?:của\s+)?([A-Z]{2})(?![A-Z])\s+(?:và|với)\s+` + CIRCLE + String.raw`\s+là\s+([A-Z])(?![A-Z])(?:\s+khác\s+([A-Z])(?![A-Z]))?`,
|
|
4297
4853
|
"gu"
|
|
4298
4854
|
);
|
|
4299
4855
|
var NAME_2ND_CUA = new RegExp(
|
|
4300
|
-
String.raw`([A-Z])(?![A-Z])\s+là\s+giao\s*điểm\s+thứ\s+hai\s+của\s+([A-Z]{2})(?![A-Z])\s+(?:và|với)\s+` + CIRCLE,
|
|
4856
|
+
String.raw`([A-Z])(?![A-Z])\s+là\s+giao\s*điểm\s+thứ\s+hai\s+của\s+(?:đường\s*thẳng\s+)?([A-Z]{2})(?![A-Z])\s+(?:và|với)\s+` + CIRCLE,
|
|
4301
4857
|
"gu"
|
|
4302
4858
|
);
|
|
4303
4859
|
var NAME_2ND_BARE = new RegExp(
|
|
@@ -4313,13 +4869,28 @@ var NAME_KHAC_CUA = new RegExp(
|
|
|
4313
4869
|
String.raw`([A-Z])(?![A-Z])\s+là\s+giao\s*điểm\s+(?:thứ\s+hai\s+)?khác\s+([A-Z])(?![A-Z])\s+của\s+([A-Z]{2})(?![A-Z])\s+(?:và|với)\s+` + CIRCLE,
|
|
4314
4870
|
"gu"
|
|
4315
4871
|
);
|
|
4872
|
+
var NAME_CUA = new RegExp(
|
|
4873
|
+
String.raw`([A-Z])(?![A-Z])\s+là\s+giao\s*điểm\s+của\s+(?!thứ\s+hai)(?!khác)(?:đường\s*thẳng\s+)?([A-Z]{2})(?![A-Z])\s+(?:và|với)\s+` + CIRCLE,
|
|
4874
|
+
"gu"
|
|
4875
|
+
);
|
|
4316
4876
|
var NAME_2ND_CIRCUM = new RegExp(
|
|
4317
4877
|
String.raw`giao\s*điểm\s+thứ\s+hai\s+của\s+([A-Z]{2})(?![A-Z])\s+(?:và|với)\s+` + String.raw`(?:nửa\s+)?` + CIRCLE_KW + String.raw`\s+ngoại\s*tiếp\s+tam\s*giác\s+[A-Z]{3}(?![A-Z])` + String.raw`\s+là\s+(?:điểm\s+)?([A-Z])(?![A-Z])(?:\s+khác\s+([A-Z])(?![A-Z]))?`,
|
|
4318
4878
|
"gu"
|
|
4319
4879
|
);
|
|
4880
|
+
var LINE_CUTS_PAREN_PAIR = new RegExp(
|
|
4881
|
+
String.raw`([A-Z]{2})(?![A-Z])\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+|tương\s+ứng\s+)?cắt\s+(?:lại\s+)?` + String.raw`\(\s*([A-Z]{3})\s*\)\s*(?:,|và)\s*\(\s*([A-Z]{3})\s*\)` + String.raw`\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+)?(?:ở|tại)\s+(?:điểm\s+(?:thứ\s+hai\s+)?(?:là\s+)?)?([A-Z])\s*(?:,|và)\s*([A-Z])(?![A-Z])`,
|
|
4882
|
+
"gu"
|
|
4883
|
+
);
|
|
4884
|
+
var PAREN_PAIR_CUTS = new RegExp(
|
|
4885
|
+
String.raw`\(\s*([A-Z]{3})\s*\)\s*(?:,|và)\s*\(\s*([A-Z]{3})\s*\)\s+cắt\s+(?:lại\s+)?` + String.raw`([A-Z]{2})\s*(?:,|và)\s*([A-Z]{2})(?![A-Z])\s+(?:lần\s*lượt\s+|theo\s+thứ\s+tự\s+)?(?:ở|tại)\s+([A-Z])\s*(?:,|và)\s*([A-Z])(?![A-Z])`,
|
|
4886
|
+
"gu"
|
|
4887
|
+
);
|
|
4320
4888
|
function secondIntersection(name, line, circle, other) {
|
|
4321
4889
|
return addPoint(name, { kind: "secondIntersection", line, circle, other: other ?? line[0] });
|
|
4322
4890
|
}
|
|
4891
|
+
function vertexOnLine(verts, line) {
|
|
4892
|
+
return verts.split("").find((v) => line.includes(v));
|
|
4893
|
+
}
|
|
4323
4894
|
function valid(name, line) {
|
|
4324
4895
|
return /^[A-Z]$/u.test(name) && /^[A-Z]{2}$/u.test(line) && !line.includes(name);
|
|
4325
4896
|
}
|
|
@@ -4356,6 +4927,13 @@ var lineCircleIntersectionRule = {
|
|
|
4356
4927
|
const other = m[4];
|
|
4357
4928
|
if (valid(name, line)) intents.push(secondIntersection(name, line, circle, other));
|
|
4358
4929
|
}
|
|
4930
|
+
SINGLE_NAMED_CENTER.lastIndex = 0;
|
|
4931
|
+
for (const m of c.text.matchAll(SINGLE_NAMED_CENTER)) {
|
|
4932
|
+
const line = m[1];
|
|
4933
|
+
const circle = m[2].replace(/′/g, "'");
|
|
4934
|
+
const name = m[3];
|
|
4935
|
+
if (valid(name, line)) intents.push(secondIntersection(name, line, circle, line[0]));
|
|
4936
|
+
}
|
|
4359
4937
|
LINE_AND_CIRCLE.lastIndex = 0;
|
|
4360
4938
|
for (const m of c.text.matchAll(LINE_AND_CIRCLE)) {
|
|
4361
4939
|
const [line, line2, circle, n1, n2] = [m[1], m[2], m[3], m[4], m[5]];
|
|
@@ -4376,7 +4954,14 @@ var lineCircleIntersectionRule = {
|
|
|
4376
4954
|
addPoint(y, { kind: "intersection", of: [line, circle], branch: 1 })
|
|
4377
4955
|
);
|
|
4378
4956
|
}
|
|
4379
|
-
|
|
4957
|
+
TWO_NAMED.lastIndex = 0;
|
|
4958
|
+
for (const m of c.text.matchAll(TWO_NAMED)) {
|
|
4959
|
+
const [line, circle, p, q] = [m[1], m[2], m[3], m[4]];
|
|
4960
|
+
if (p === q) continue;
|
|
4961
|
+
if (line.includes(p) && !line.includes(q)) intents.push(secondIntersection(q, line, circle, p));
|
|
4962
|
+
else if (line.includes(q) && !line.includes(p)) intents.push(secondIntersection(p, line, circle, q));
|
|
4963
|
+
}
|
|
4964
|
+
NAME_2ND_BARE.lastIndex = 0;
|
|
4380
4965
|
for (const m of c.text.matchAll(NAME_2ND_BARE)) {
|
|
4381
4966
|
const rc = RESOLVE_CIRCLE_BARE.exec(ctx.problem);
|
|
4382
4967
|
const circle = rc?.[1] ?? rc?.[2];
|
|
@@ -4413,6 +4998,13 @@ var lineCircleIntersectionRule = {
|
|
|
4413
4998
|
const [line, name] = [m[1], m[2]];
|
|
4414
4999
|
if (circle && valid(name, line)) intents.push(secondIntersection(name, line, circle, line[0]));
|
|
4415
5000
|
}
|
|
5001
|
+
CIRCLE_SUBJECT_TWO.lastIndex = 0;
|
|
5002
|
+
for (const m of c.text.matchAll(CIRCLE_SUBJECT_TWO)) {
|
|
5003
|
+
const [circle, l1, l2, n1, n2] = [m[1], m[2], m[3], m[4], m[5]];
|
|
5004
|
+
if (n1 === n2) continue;
|
|
5005
|
+
if (valid(n1, l1)) intents.push(secondIntersection(n1, l1, circle));
|
|
5006
|
+
if (valid(n2, l2)) intents.push(secondIntersection(n2, l2, circle));
|
|
5007
|
+
}
|
|
4416
5008
|
CIRCLE_SUBJECT.lastIndex = 0;
|
|
4417
5009
|
for (const m of c.text.matchAll(CIRCLE_SUBJECT)) {
|
|
4418
5010
|
const [circle, line, name] = [m[1], m[2], m[3]];
|
|
@@ -4434,6 +5026,11 @@ var lineCircleIntersectionRule = {
|
|
|
4434
5026
|
const circle = m[4];
|
|
4435
5027
|
if (valid(name, line)) intents.push(secondIntersection(name, line, circle, other));
|
|
4436
5028
|
}
|
|
5029
|
+
NAME_CUA.lastIndex = 0;
|
|
5030
|
+
for (const m of c.text.matchAll(NAME_CUA)) {
|
|
5031
|
+
const [name, line, circle] = [m[1], m[2], m[3]];
|
|
5032
|
+
if (valid(name, line)) intents.push(secondIntersection(name, line, circle));
|
|
5033
|
+
}
|
|
4437
5034
|
NAME_2ND_CIRCUM.lastIndex = 0;
|
|
4438
5035
|
for (const m of c.text.matchAll(NAME_2ND_CIRCUM)) {
|
|
4439
5036
|
const line = m[1];
|
|
@@ -4449,6 +5046,34 @@ var lineCircleIntersectionRule = {
|
|
|
4449
5046
|
if (line.includes(name)) continue;
|
|
4450
5047
|
intents.push(addPoint(name, { kind: "intersection", of: [line, circle], branch: 0 }));
|
|
4451
5048
|
}
|
|
5049
|
+
LINE_CUTS_PAREN_PAIR.lastIndex = 0;
|
|
5050
|
+
for (const m of c.text.matchAll(LINE_CUTS_PAREN_PAIR)) {
|
|
5051
|
+
const [line, t1, t2, n1, n2] = [m[1], m[2], m[3], m[4], m[5]];
|
|
5052
|
+
const o1 = vertexOnLine(t1, line);
|
|
5053
|
+
const o2 = vertexOnLine(t2, line);
|
|
5054
|
+
if (!o1 || !o2 || n1 === n2) continue;
|
|
5055
|
+
if (line.includes(n1) || line.includes(n2)) continue;
|
|
5056
|
+
intents.push(
|
|
5057
|
+
drawCircle(`w${t1}`, "through3", { points: t1.split("") }),
|
|
5058
|
+
drawCircle(`w${t2}`, "through3", { points: t2.split("") }),
|
|
5059
|
+
secondIntersection(n1, line, `w${t1}`, o1),
|
|
5060
|
+
secondIntersection(n2, line, `w${t2}`, o2)
|
|
5061
|
+
);
|
|
5062
|
+
}
|
|
5063
|
+
PAREN_PAIR_CUTS.lastIndex = 0;
|
|
5064
|
+
for (const m of c.text.matchAll(PAREN_PAIR_CUTS)) {
|
|
5065
|
+
const [t1, t2, l1, l2, n1, n2] = [m[1], m[2], m[3], m[4], m[5], m[6]];
|
|
5066
|
+
const o1 = vertexOnLine(t1, l1);
|
|
5067
|
+
const o2 = vertexOnLine(t2, l2);
|
|
5068
|
+
if (!o1 || !o2 || n1 === n2) continue;
|
|
5069
|
+
if (l1.includes(n1) || l2.includes(n2)) continue;
|
|
5070
|
+
intents.push(
|
|
5071
|
+
drawCircle(`w${t1}`, "through3", { points: t1.split("") }),
|
|
5072
|
+
drawCircle(`w${t2}`, "through3", { points: t2.split("") }),
|
|
5073
|
+
secondIntersection(n1, l1, `w${t1}`, o1),
|
|
5074
|
+
secondIntersection(n2, l2, `w${t2}`, o2)
|
|
5075
|
+
);
|
|
5076
|
+
}
|
|
4452
5077
|
if (intents.length > 0) out.push({ ruleId: "line-circle-intersection", clauseIds: [c.id], intents });
|
|
4453
5078
|
}
|
|
4454
5079
|
return out;
|
|
@@ -4509,8 +5134,9 @@ var incenterNamedTriangleRule = {
|
|
|
4509
5134
|
};
|
|
4510
5135
|
|
|
4511
5136
|
// src/stamps/geometry-2d/ai/rules/perpThroughCutsLines.ts
|
|
5137
|
+
var LTOK = "(?:[A-Z]{2}|[A-Z][a-z][0-9]?)";
|
|
4512
5138
|
var RE2 = new RegExp(
|
|
4513
|
-
|
|
5139
|
+
`(?:Qua|qua|T\u1EEB|t\u1EEB)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?:['\u2032]?)(?!\\p{L})[^.]{0,24}?(song\\s*song|vu\xF4ng\\s*g\xF3c)\\s+(?:v\u1EDBi\\s+)?(?:c\u1EA1nh\\s+|\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+)?([A-Z])([A-Z])(?!\\p{L})[^.]{0,40}?c\u1EAFt\\s+(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+)?(${LTOK})\\s*(?:,|v\xE0)\\s*(${LTOK})(?![A-Z])[^.]{0,30}?(?:\u1EDF|t\u1EA1i)\\s+([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])`,
|
|
4514
5140
|
"gu"
|
|
4515
5141
|
);
|
|
4516
5142
|
var PREFILTER16 = /(?:Qua|qua|Từ|từ)\s+(?:điểm\s+)?[A-Z][^.]{0,24}?(?:song\s*song|vuông\s*góc)[^.]{0,80}?cắt/u;
|
|
@@ -4533,10 +5159,10 @@ var perpThroughCutsLinesRule = {
|
|
|
4533
5159
|
const through = m[1];
|
|
4534
5160
|
const isParallel = /song/.test(m[2]);
|
|
4535
5161
|
const to = m[3] + m[4];
|
|
4536
|
-
const line1 = m[5]
|
|
4537
|
-
const line2 = m[
|
|
4538
|
-
const h = m[
|
|
4539
|
-
const k = m[
|
|
5162
|
+
const line1 = m[5];
|
|
5163
|
+
const line2 = m[6];
|
|
5164
|
+
const h = m[7];
|
|
5165
|
+
const k = m[8];
|
|
4540
5166
|
if (isParallel && to.includes(through)) continue;
|
|
4541
5167
|
if (h === k) continue;
|
|
4542
5168
|
const kind = isParallel ? "parallelThrough" : "perpThrough";
|
|
@@ -4582,6 +5208,40 @@ var CIRCLE_CENTER3 = /\(\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)|tâm\s+([A-Z])(?![A-Z
|
|
|
4582
5208
|
var DIAMETER_ENDS = /đường\s*kính\s+([A-Z])([A-Z])(?![A-Z])/u;
|
|
4583
5209
|
var AFTER_TANGENT = /tiếp\s*tuyến\s+((?:[A-Z][xyzt](?![A-Za-z])\s*[,và\s]*)+)/gu;
|
|
4584
5210
|
var RAY_TOKEN = /([A-Z])([xyzt])(?![A-Za-z])/gu;
|
|
5211
|
+
var SHAPE_VERTICES = /[Cc]ho\s+(?:tam\s*giác|tứ\s*giác|hình\s+(?:vuông|chữ\s*nhật|thang|bình\s*hành|thoi))\s+([A-Z]{3,})/gu;
|
|
5212
|
+
function matchFreeTangentPoint(ctx, cm) {
|
|
5213
|
+
const center = cm ? cm[1] ?? cm[2] : void 0;
|
|
5214
|
+
if (!center) return [];
|
|
5215
|
+
const known = /* @__PURE__ */ new Set();
|
|
5216
|
+
SHAPE_VERTICES.lastIndex = 0;
|
|
5217
|
+
for (const m of ctx.problem.matchAll(SHAPE_VERTICES)) {
|
|
5218
|
+
for (const ch of m[1]) known.add(ch);
|
|
5219
|
+
}
|
|
5220
|
+
const out = [];
|
|
5221
|
+
for (const c of ctx.clauses) {
|
|
5222
|
+
const intents = [];
|
|
5223
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5224
|
+
AFTER_TANGENT.lastIndex = 0;
|
|
5225
|
+
for (const grp of c.text.matchAll(AFTER_TANGENT)) {
|
|
5226
|
+
RAY_TOKEN.lastIndex = 0;
|
|
5227
|
+
for (const m of grp[1].matchAll(RAY_TOKEN)) {
|
|
5228
|
+
const endpoint = m[1];
|
|
5229
|
+
const token = `${m[1]}${m[2]}`;
|
|
5230
|
+
if (known.has(endpoint)) continue;
|
|
5231
|
+
if (seen.has(token)) continue;
|
|
5232
|
+
seen.add(token);
|
|
5233
|
+
intents.push(
|
|
5234
|
+
addPoint(endpoint, { kind: "onCircle", circle: center, theta: 0.7 }),
|
|
5235
|
+
drawLine(token, "tangentAt", { through: endpoint, circle: center })
|
|
5236
|
+
);
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
5239
|
+
if (intents.length > 0) {
|
|
5240
|
+
out.push({ ruleId: "tangent-ray", clauseIds: [c.id], intents });
|
|
5241
|
+
}
|
|
5242
|
+
}
|
|
5243
|
+
return out;
|
|
5244
|
+
}
|
|
4585
5245
|
var tangentRayRule = {
|
|
4586
5246
|
id: "tangent-ray",
|
|
4587
5247
|
// Cao hơn point-on-ray (55) + intersect-ray (48): token tia phải được dựng
|
|
@@ -4593,7 +5253,7 @@ var tangentRayRule = {
|
|
|
4593
5253
|
match(ctx) {
|
|
4594
5254
|
const cm = CIRCLE_CENTER3.exec(ctx.problem);
|
|
4595
5255
|
const dm = DIAMETER_ENDS.exec(ctx.problem);
|
|
4596
|
-
if (!dm) return
|
|
5256
|
+
if (!dm) return matchFreeTangentPoint(ctx, cm);
|
|
4597
5257
|
const center = cm ? cm[1] ?? cm[2] : `k${dm[1]}${dm[2]}`;
|
|
4598
5258
|
const ends = /* @__PURE__ */ new Set([dm[1], dm[2]]);
|
|
4599
5259
|
const out = [];
|
|
@@ -5242,7 +5902,7 @@ var PREFILTER30 = /[Tt]iếp\s*tuyến[^.]{0,30}?tại\s+[A-Z][^.]{0,40}?cắt|Q
|
|
|
5242
5902
|
var CIRCLE_REF4 = /đường\s*tròn\s*(?:tâm\s+)?\(?\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)?/u;
|
|
5243
5903
|
var UNNAMED_DIAMETER2 = /(?:nửa\s+)?đường\s*tròn\s+đường\s*kính\s+([A-Z])([A-Z])(?![A-Z])/u;
|
|
5244
5904
|
var QUA_RE = new RegExp(
|
|
5245
|
-
"Qua\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})[^.]{0,40}?ti\u1EBFp\\s*tuy\u1EBFn\\s+(?:th\u1EE9\\s+(?:ba|3|hai|2)\\s+)?c\u1EAFt\\s+(?:c\xE1c\\s+)?(?:(?:c\xE1c\\s+)?ti\u1EBFp\\s*tuy\u1EBFn\\s+|\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+)?([A-Z](?:[A-Z]|[xyzt]))\\s*(?:,|v\xE0)\\s*([A-Z](?:[A-Z]|[xyzt]))(?!\\p{L})\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:\u1EDF|t\u1EA1i)\\s+([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])",
|
|
5905
|
+
"Qua\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})[^.]{0,40}?ti\u1EBFp\\s*tuy\u1EBFn\\s+(?:th\u1EE9\\s+(?:ba|3|hai|2)\\s+)?c\u1EAFt\\s+(?:c\xE1c\\s+)?(?:(?:c\xE1c\\s+)?ti\u1EBFp\\s*tuy\u1EBFn\\s+|\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+)?([A-Z](?:[A-Z]|[xyzt]))\\s*(?:,|v\xE0)\\s*([A-Z](?:[A-Z]|[xyzt]))(?!\\p{L})(?:\\s+k\xE9o\\s+d\xE0i)?\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:\u1EDF|t\u1EA1i)\\s+([A-Z])\\s*(?:,|v\xE0)\\s*(?:(?:t\u1EA1i|\u1EDF)\\s+)?([A-Z])(?![A-Z])",
|
|
5246
5906
|
"gu"
|
|
5247
5907
|
);
|
|
5248
5908
|
var SINGLE_RE = new RegExp(
|
|
@@ -5250,7 +5910,7 @@ var SINGLE_RE = new RegExp(
|
|
|
5250
5910
|
"gu"
|
|
5251
5911
|
);
|
|
5252
5912
|
var RE6 = new RegExp(
|
|
5253
|
-
"[Tt]i\u1EBFp\\s*tuy\u1EBFn\\s+(?:c\u1EE7a\\s+[^.]{0,14}?\\s+)?t\u1EA1i\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})(?:\\s+c\u1EE7a\\s+[^.]{0,14}?)?\\s+c\u1EAFt\\s+(?:c\xE1c\\s+)?(?:(?:c\xE1c\\s+)?ti\u1EBFp\\s*tuy\u1EBFn\\s+|\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+)?([A-Z](?:[A-Z]|[xyzt]))\\s*(?:,|v\xE0)\\s*([A-Z](?:[A-Z]|[xyzt]))(?!\\p{L})\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:t\u1EA1i|\u1EDF)\\s+([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])",
|
|
5913
|
+
"[Tt]i\u1EBFp\\s*tuy\u1EBFn\\s+(?:(?:c\u1EE7a|v\u1EDBi)\\s+[^.]{0,14}?\\s+)?t\u1EA1i\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})(?:\\s+(?:c\u1EE7a|v\u1EDBi)\\s+[^.]{0,14}?)?\\s+c\u1EAFt\\s+(?:c\xE1c\\s+)?(?:(?:c\xE1c\\s+)?ti\u1EBFp\\s*tuy\u1EBFn\\s+|\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+)?([A-Z](?:[A-Z]|[xyzt]))\\s*(?:,|v\xE0)\\s*([A-Z](?:[A-Z]|[xyzt]))(?!\\p{L})(?:\\s+k\xE9o\\s+d\xE0i)?\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:t\u1EA1i|\u1EDF)\\s+([A-Z])\\s*(?:,|v\xE0)\\s*(?:(?:t\u1EA1i|\u1EDF)\\s+)?([A-Z])(?![A-Z])",
|
|
5254
5914
|
"gu"
|
|
5255
5915
|
);
|
|
5256
5916
|
function circleName3(problem) {
|
|
@@ -5334,12 +5994,16 @@ var RE7 = new RegExp(
|
|
|
5334
5994
|
"([A-Z])([A-Z])(?![A-Z])\\s+l\xE0\\s+\u0111\u01B0\u1EDDng\\s*k\xEDnh\\s+(?:c\u1EE7a\\s+)?(?:\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s*)?(?:t\xE2m\\s+)?\\(?\\s*([A-Z])\\s*\\)?",
|
|
5335
5995
|
"gu"
|
|
5336
5996
|
);
|
|
5997
|
+
var VERB_GAP = "(?:[^.;,]{0,24}?v\xE0\\s+|(?:(?!\u0111\u01B0\u1EDDng\\s*tr\xF2n|\\()[^.;,]){0,24}?)?";
|
|
5337
5998
|
var RE_VERB = new RegExp(
|
|
5338
|
-
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng)\\s+
|
|
5999
|
+
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng)\\s+" + VERB_GAP + "\u0111\u01B0\u1EDDng\\s*k\xEDnh\\s+([A-Z])([A-Z]['\u2032]?)(?![A-Za-z'\u2032])",
|
|
5339
6000
|
"gu"
|
|
5340
6001
|
);
|
|
5341
6002
|
var RESOLVE_CENTER = /\(\s*([A-Z])(?:\s*[;,]\s*[Rr0-9][^)]*)?\s*\)|tâm\s+([A-Z])(?![A-Za-z])/u;
|
|
5342
|
-
var PREFILTER31 =
|
|
6003
|
+
var PREFILTER31 = new RegExp(
|
|
6004
|
+
"[A-Z]{2}\\s+l\xE0\\s+\u0111\u01B0\u1EDDng\\s*k\xEDnh|(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng)\\s+" + VERB_GAP + "\u0111\u01B0\u1EDDng\\s*k\xEDnh",
|
|
6005
|
+
"u"
|
|
6006
|
+
);
|
|
5343
6007
|
var diameterEndpointRule = {
|
|
5344
6008
|
id: "diameterEndpoint",
|
|
5345
6009
|
priority: 56,
|
|
@@ -5373,7 +6037,7 @@ var diameterEndpointRule = {
|
|
|
5373
6037
|
const d = m[2].replace(/′/g, "'");
|
|
5374
6038
|
if (a === d || center === a || center === d || emitted.has(d)) continue;
|
|
5375
6039
|
const before = ctx.problem.slice(0, ctx.problem.indexOf(m[0]));
|
|
5376
|
-
if (!new RegExp(`(?<![a-z])${a}(?![a-z])`, "u").test(before)) continue;
|
|
6040
|
+
if (!new RegExp(`(?<![a-z])${escapeRe(a)}(?![a-z])`, "u").test(before)) continue;
|
|
5377
6041
|
emitted.add(d);
|
|
5378
6042
|
out.push({
|
|
5379
6043
|
ruleId: "diameterEndpoint",
|
|
@@ -5437,8 +6101,69 @@ var circleThroughTwoCutsSidesRule = {
|
|
|
5437
6101
|
}
|
|
5438
6102
|
};
|
|
5439
6103
|
|
|
6104
|
+
// src/stamps/geometry-2d/ai/rules/circleCenterRadiusCutsSides.ts
|
|
6105
|
+
var PATTERN4 = new RegExp(
|
|
6106
|
+
CIRCLE_KW + "\\s+t\xE2m\\s+([A-Z])(?![A-Z])\\s*b\xE1n\\s*k\xEDnh\\s+([A-Z])([A-Z])(?![A-Z])[^.;\\n]*?c\u1EAFt\\s+([^.;\\n]*?)\\s+(?:(?:l\u1EA7n\\s*l\u01B0\u1EE3t|theo\\s+th\u1EE9\\s+t\u1EF1)\\s+)?t\u1EA1i\\s+(?:c\xE1c\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z](?!\\p{L})(?:\\s*(?:,|v\xE0)\\s*[A-Z](?!\\p{L}))*)",
|
|
6107
|
+
"gu"
|
|
6108
|
+
);
|
|
6109
|
+
var PREFILTER33 = new RegExp(
|
|
6110
|
+
CIRCLE_KW + "\\s+t\xE2m\\s+[A-Z]\\s*b\xE1n\\s*k\xEDnh\\s+[A-Z]{2}[^.;\\n]*?c\u1EAFt",
|
|
6111
|
+
"u"
|
|
6112
|
+
);
|
|
6113
|
+
var circleCenterRadiusCutsSidesRule = {
|
|
6114
|
+
id: "circle-center-radius-cuts-sides",
|
|
6115
|
+
// THẤP HƠN triangle (100) + centers (cần đỉnh + tâm H dựng trước). Cùng họ với
|
|
6116
|
+
// diameterCircleCutsSides (66) / circleThroughTwoCutsSides (58). Đặt 57.
|
|
6117
|
+
priority: 57,
|
|
6118
|
+
languages: ["vi"],
|
|
6119
|
+
patterns: [PREFILTER33],
|
|
6120
|
+
match(ctx) {
|
|
6121
|
+
const out = [];
|
|
6122
|
+
for (const c of ctx.clauses) {
|
|
6123
|
+
PATTERN4.lastIndex = 0;
|
|
6124
|
+
for (const m of c.text.matchAll(PATTERN4)) {
|
|
6125
|
+
const center = m[1];
|
|
6126
|
+
const r0 = m[2];
|
|
6127
|
+
const r1 = m[3];
|
|
6128
|
+
let through;
|
|
6129
|
+
if (r0 === center && r1 !== center) through = r1;
|
|
6130
|
+
else if (r1 === center && r0 !== center) through = r0;
|
|
6131
|
+
if (!through) continue;
|
|
6132
|
+
const lines = m[4].match(/[A-Z][A-Z](?![A-Z])/gu) ?? [];
|
|
6133
|
+
const points = m[5].match(/[A-Z](?![A-Z])/gu) ?? [];
|
|
6134
|
+
if (lines.length === 0 || lines.length !== points.length) continue;
|
|
6135
|
+
const circ = `${center}_c`;
|
|
6136
|
+
const intents = [
|
|
6137
|
+
drawCircle(circ, "centerThrough", { center, through })
|
|
6138
|
+
];
|
|
6139
|
+
let ok = true;
|
|
6140
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6141
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6142
|
+
const line = lines[i];
|
|
6143
|
+
const pt = points[i];
|
|
6144
|
+
if (!line.includes(through)) {
|
|
6145
|
+
ok = false;
|
|
6146
|
+
break;
|
|
6147
|
+
}
|
|
6148
|
+
if (line.includes(pt) || pt === through || pt === center || seen.has(pt)) {
|
|
6149
|
+
ok = false;
|
|
6150
|
+
break;
|
|
6151
|
+
}
|
|
6152
|
+
seen.add(pt);
|
|
6153
|
+
intents.push(
|
|
6154
|
+
addPoint(pt, { kind: "secondIntersection", line, circle: circ, other: through })
|
|
6155
|
+
);
|
|
6156
|
+
}
|
|
6157
|
+
if (!ok) continue;
|
|
6158
|
+
out.push({ ruleId: "circle-center-radius-cuts-sides", clauseIds: [c.id], intents });
|
|
6159
|
+
}
|
|
6160
|
+
}
|
|
6161
|
+
return out;
|
|
6162
|
+
}
|
|
6163
|
+
};
|
|
6164
|
+
|
|
5440
6165
|
// src/stamps/geometry-2d/ai/rules/circumcircleNamedDistrib.ts
|
|
5441
|
-
var
|
|
6166
|
+
var PREFILTER34 = /lần\s*lượt\s+là\s+(?:đường\s*tròn\s+)?ngoại\s*tiếp/u;
|
|
5442
6167
|
var RE9 = new RegExp(
|
|
5443
6168
|
"([A-Z])\\s*,\\s*([A-Z])\\s+l\u1EA7n\\s*l\u01B0\u1EE3t\\s+l\xE0\\s+(?:\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s+)?ngo\u1EA1i\\s*ti\u1EBFp\\s+(?:c\xE1c\\s+)?tam\\s*gi\xE1c\\s+([A-Z])([A-Z])([A-Z])(?![A-Z])\\s*,\\s*(?:tam\\s*gi\xE1c\\s+)?([A-Z])([A-Z])([A-Z])(?![A-Z])",
|
|
5444
6169
|
"u"
|
|
@@ -5447,7 +6172,7 @@ var circumcircleNamedDistribRule = {
|
|
|
5447
6172
|
id: "circumcircleNamedDistrib",
|
|
5448
6173
|
priority: 67,
|
|
5449
6174
|
languages: ["vi"],
|
|
5450
|
-
patterns: [
|
|
6175
|
+
patterns: [PREFILTER34],
|
|
5451
6176
|
match(ctx) {
|
|
5452
6177
|
const out = [];
|
|
5453
6178
|
for (const c of ctx.clauses) {
|
|
@@ -5472,7 +6197,7 @@ var circumcircleNamedDistribRule = {
|
|
|
5472
6197
|
};
|
|
5473
6198
|
|
|
5474
6199
|
// src/stamps/geometry-2d/ai/rules/circumcirclePairMeet.ts
|
|
5475
|
-
var
|
|
6200
|
+
var PREFILTER35 = /[Đđ]ường\s*tròn\s+ngoại\s*tiếp\s+(?:các\s+)?tam\s*giác\s+[A-Z]{3}\s*(?:,|\s+(?:và|cắt)\s+(?:lại\s+)?đường\s*tròn\s+ngoại\s*tiếp\s+tam\s*giác)/u;
|
|
5476
6201
|
var RE_CAT = new RegExp(
|
|
5477
6202
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*tr\xF2n\\s+ngo\u1EA1i\\s*ti\u1EBFp\\s+tam\\s*gi\xE1c\\s+([A-Z]{3})(?![A-Z])\\s+c\u1EAFt\\s+(?:l\u1EA1i\\s+)?[\u0110\u0111]\u01B0\u1EDDng\\s*tr\xF2n\\s+ngo\u1EA1i\\s*ti\u1EBFp\\s+tam\\s*gi\xE1c\\s+([A-Z]{3})(?![A-Z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+(?:th\u1EE9\\s+hai\\s+)?)?(?:l\xE0\\s+)?([A-Z])(?![A-Z])(?:\\s+kh\xE1c\\s+([A-Z])(?![A-Z]))?",
|
|
5478
6203
|
"u"
|
|
@@ -5494,22 +6219,27 @@ var RE_PAREN_BEFORE = new RegExp(
|
|
|
5494
6219
|
`([A-Z])(?![A-Z])\\s+l\xE0\\s+giao\\s*\u0111i\u1EC3m\\s+(?:th\u1EE9\\s+hai\\s+)?(?:c\u1EE7a\\s+)?${PAR}\\s+v\xE0\\s+${PAR}`,
|
|
5495
6220
|
"u"
|
|
5496
6221
|
);
|
|
6222
|
+
var RE_PAREN_CAT = new RegExp(
|
|
6223
|
+
`${PAR}\\s+v\xE0\\s+${PAR}\\s+c\u1EAFt\\s+nhau\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+(?:th\u1EE9\\s+hai\\s+)?)?([A-Z])(?![A-Z])(?:\\s+kh\xE1c\\s+([A-Z])(?![A-Z]))?`,
|
|
6224
|
+
"u"
|
|
6225
|
+
);
|
|
5497
6226
|
var circumcirclePairMeetRule = {
|
|
5498
6227
|
id: "circumcirclePairMeet",
|
|
5499
6228
|
priority: 59,
|
|
5500
6229
|
languages: ["vi"],
|
|
5501
|
-
patterns: [
|
|
6230
|
+
patterns: [PREFILTER35, new RegExp(`${PAR}\\s+v\xE0\\s+${PAR}`, "u")],
|
|
5502
6231
|
match(ctx) {
|
|
5503
6232
|
const out = [];
|
|
5504
6233
|
for (const c of ctx.clauses) {
|
|
5505
6234
|
const pl = RE_PAREN_LA.exec(c.text);
|
|
5506
|
-
const
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
const
|
|
5510
|
-
const
|
|
6235
|
+
const pc = pl ? null : RE_PAREN_CAT.exec(c.text);
|
|
6236
|
+
const pb = pl || pc ? null : RE_PAREN_BEFORE.exec(c.text);
|
|
6237
|
+
if (pl || pc || pb) {
|
|
6238
|
+
const t1 = pl ? pl[1] : pc ? pc[1] : pb[2];
|
|
6239
|
+
const t2 = pl ? pl[2] : pc ? pc[2] : pb[3];
|
|
6240
|
+
const q2 = pl ? pl[3] : pc ? pc[3] : pb[1];
|
|
5511
6241
|
const tri12 = t1.split(""), tri22 = t2.split("");
|
|
5512
|
-
const shared2 = pl && pl[4] || tri12.find((v) => tri22.includes(v));
|
|
6242
|
+
const shared2 = pl && pl[4] || pc && pc[4] || tri12.find((v) => tri22.includes(v));
|
|
5513
6243
|
if (shared2 && !tri12.includes(q2) && !tri22.includes(q2)) {
|
|
5514
6244
|
out.push({
|
|
5515
6245
|
ruleId: "circumcirclePairMeet",
|
|
@@ -5547,7 +6277,7 @@ var circumcirclePairMeetRule = {
|
|
|
5547
6277
|
};
|
|
5548
6278
|
|
|
5549
6279
|
// src/stamps/geometry-2d/ai/rules/tangentCircleAtPoint.ts
|
|
5550
|
-
var
|
|
6280
|
+
var PREFILTER36 = /[Đđ]ường\s*tròn\s*\(?[A-Z]\)?[^.]{0,40}?tiếp\s*xúc[^.]{0,20}?tại\s+[A-Z]/u;
|
|
5551
6281
|
var RE11 = new RegExp(
|
|
5552
6282
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*tr\xF2n\\s*\\(\\s*([A-Z])\\s*\\)\\s*(?:\u0111i\\s+)?qua\\s+([A-Z])\\s+(?:v\xE0\\s+)?ti\u1EBFp\\s*x\xFAc\\s+(?:v\u1EDBi\\s+)?(?:c\u1EA1nh\\s+|\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+)?([A-Z]{2})\\s+t\u1EA1i\\s+([A-Z])(?!\\p{L})[^.]{0,24}?c\u1EAFt\\s+(?:c\xE1c\\s+(?:c\u1EA1nh|\u0111o\u1EA1n)\\s+|c\u1EA1nh\\s+)?([A-Z]{2})\\s*(?:,|v\xE0)\\s*([A-Z]{2})(?!\\p{L})\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:\u1EDF|t\u1EA1i)\\s+([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])",
|
|
5553
6283
|
"gu"
|
|
@@ -5560,7 +6290,7 @@ var tangentCircleAtPointRule = {
|
|
|
5560
6290
|
id: "tangentCircleAtPoint",
|
|
5561
6291
|
priority: 60,
|
|
5562
6292
|
languages: ["vi"],
|
|
5563
|
-
patterns: [
|
|
6293
|
+
patterns: [PREFILTER36],
|
|
5564
6294
|
match(ctx) {
|
|
5565
6295
|
const out = [];
|
|
5566
6296
|
for (const c of ctx.clauses) {
|
|
@@ -5616,7 +6346,7 @@ var tangentCircleAtPointRule = {
|
|
|
5616
6346
|
};
|
|
5617
6347
|
|
|
5618
6348
|
// src/stamps/geometry-2d/ai/rules/externalBisectorCutsLines.ts
|
|
5619
|
-
var
|
|
6349
|
+
var PREFILTER37 = /phân\s*giác\s+ngoài\s+(?:của\s+)?góc\s+[A-Z]{3}[^.]{0,30}?cắt/u;
|
|
5620
6350
|
var RE12 = new RegExp(
|
|
5621
6351
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*th\u1EB3ng\\s+(?:ch\u1EE9a\\s+)?ph\xE2n\\s*gi\xE1c\\s+ngo\xE0i\\s+(?:c\u1EE7a\\s+)?g\xF3c\\s+([A-Z])([A-Z])([A-Z])(?![A-Z])[^.]{0,20}?c\u1EAFt\\s+(?:c\xE1c\\s+(?:c\u1EA1nh|\u0111\u01B0\u1EDDng\\s*th\u1EB3ng)\\s+)?([A-Z]{2})\\s*(?:,|v\xE0)\\s*([A-Z]{2})(?!\\p{L})\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:\u1EDF|t\u1EA1i)\\s+(?:c\xE1c\\s+\u0111i\u1EC3m\\s+)?([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])",
|
|
5622
6352
|
"gu"
|
|
@@ -5625,7 +6355,7 @@ var externalBisectorCutsLinesRule = {
|
|
|
5625
6355
|
id: "externalBisectorCutsLines",
|
|
5626
6356
|
priority: 57,
|
|
5627
6357
|
languages: ["vi"],
|
|
5628
|
-
patterns: [
|
|
6358
|
+
patterns: [PREFILTER37],
|
|
5629
6359
|
match(ctx) {
|
|
5630
6360
|
const out = [];
|
|
5631
6361
|
for (const c of ctx.clauses) {
|
|
@@ -5656,7 +6386,7 @@ var externalBisectorCutsLinesRule = {
|
|
|
5656
6386
|
};
|
|
5657
6387
|
|
|
5658
6388
|
// src/stamps/geometry-2d/ai/rules/tangentTwoSidesCircle.ts
|
|
5659
|
-
var
|
|
6389
|
+
var PREFILTER38 = /[Đđ]ường\s*tròn[^.]{0,30}?tiếp\s*xúc[^.]{0,40}?tiếp\s*xúc\s+trong/u;
|
|
5660
6390
|
var RE13 = new RegExp(
|
|
5661
6391
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*tr\xF2n\\s*\\(?\\s*([A-Z])\\s*\\)?\\s+ti\u1EBFp\\s*x\xFAc\\s+(?:v\u1EDBi\\s+)?(?:c\xE1c\\s+c\u1EA1nh\\s+|c\u1EA1nh\\s+)?([A-Z]{2})\\s*,\\s*([A-Z]{2})(?!\\p{L})\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?(?:\u1EDF|t\u1EA1i)\\s+([A-Z])\\s*,\\s*([A-Z])(?!\\p{L})\\s+v\xE0\\s+ti\u1EBFp\\s*x\xFAc\\s+trong\\s+(?:v\u1EDBi\\s+)?\\(?\\s*[A-Z]\\s*\\)?\\s+(?:\u1EDF|t\u1EA1i)\\s+([A-Z])(?![A-Z])",
|
|
5662
6392
|
"gu"
|
|
@@ -5669,7 +6399,7 @@ var tangentTwoSidesCircleRule = {
|
|
|
5669
6399
|
id: "tangentTwoSidesCircle",
|
|
5670
6400
|
priority: 61,
|
|
5671
6401
|
languages: ["vi"],
|
|
5672
|
-
patterns: [
|
|
6402
|
+
patterns: [PREFILTER38],
|
|
5673
6403
|
match(ctx) {
|
|
5674
6404
|
const out = [];
|
|
5675
6405
|
for (const c of ctx.clauses) {
|
|
@@ -5705,14 +6435,22 @@ var tangentTwoSidesCircleRule = {
|
|
|
5705
6435
|
};
|
|
5706
6436
|
|
|
5707
6437
|
// src/stamps/geometry-2d/ai/rules/interiorPoint.ts
|
|
5708
|
-
var
|
|
5709
|
-
var
|
|
5710
|
-
var
|
|
6438
|
+
var SHAPE = "(?:(?:tam\\s*gi\xE1c|t\u1EE9\\s*gi\xE1c|\u0111\u01B0\u1EDDng\\s*tr\xF2n|h\xECnh(?:\\s+(?:ch\u1EEF\\s*nh\u1EADt|vu\xF4ng|thoi|b\xECnh\\s*h\xE0nh|thang))?)(?:\\s+(?:[A-Z]{3,4}(?![A-Z])|\\([^)]*\\)))?|\\([^)]*\\))";
|
|
6439
|
+
var QUAL = "(?:\\s+(?:b\u1EA5t\\s*k[i\xECy\u1EF3]|t[u\xF9][y\u1EF3]\\s*\xFD))?";
|
|
6440
|
+
var PREFILTER39 = /(?:nằm\s+)?(?:bên\s+)?trong\s+(?:tam\s*giác|tứ\s*giác|đường\s*tròn|hình|mặt\s*phẳng|\()/u;
|
|
6441
|
+
var RE14 = new RegExp(
|
|
6442
|
+
"(?:[Gg]\u1ECDi\\s+|[Ll]\u1EA5y\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})\\s+(?:l\xE0\\s+)?(?:m\u1ED9t\\s+)?\u0111i\u1EC3m" + QUAL + "\\s+(?:n\u1EB1m\\s+)?(?:b\xEAn\\s+)?trong\\s+(?:m\u1EB7t\\s*ph\u1EB3ng\\s+(?:ch\u1EE9a\\s+)?)?" + SHAPE,
|
|
6443
|
+
"u"
|
|
6444
|
+
);
|
|
6445
|
+
var RE22 = new RegExp(
|
|
6446
|
+
"(?:m\u1ED9t\\s+)?[\u0110\u0111]i\u1EC3m\\s+([A-Z])(?!\\p{L})" + QUAL + "\\s+n\u1EB1m\\s+(?:b\xEAn\\s+)?trong\\s+" + SHAPE,
|
|
6447
|
+
"u"
|
|
6448
|
+
);
|
|
5711
6449
|
var interiorPointRule = {
|
|
5712
6450
|
id: "interiorPoint",
|
|
5713
6451
|
priority: 30,
|
|
5714
6452
|
languages: ["vi"],
|
|
5715
|
-
patterns: [
|
|
6453
|
+
patterns: [PREFILTER39],
|
|
5716
6454
|
match(ctx) {
|
|
5717
6455
|
const out = [];
|
|
5718
6456
|
for (const c of ctx.clauses) {
|
|
@@ -5730,7 +6468,7 @@ var interiorPointRule = {
|
|
|
5730
6468
|
};
|
|
5731
6469
|
|
|
5732
6470
|
// src/stamps/geometry-2d/ai/rules/parallelCutsTangentAt.ts
|
|
5733
|
-
var
|
|
6471
|
+
var PREFILTER40 = /[Đđ]ường\s*thẳng\s+qua\s+[A-Z][^.]{0,40}?tiếp\s*tuyến\s+tại\s+[A-Z]/u;
|
|
5734
6472
|
var CIRCLE_REF5 = /đường\s*tròn\s*\(\s*([A-Z])\s*\)|\(\s*([A-Z])\s*\)/u;
|
|
5735
6473
|
var RE15 = new RegExp(
|
|
5736
6474
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*th\u1EB3ng\\s+qua\\s+([A-Z])(?!\\p{L})\\s+(song\\s*song|vu\xF4ng\\s*g\xF3c)\\s+(?:v\u1EDBi\\s+)?([A-Z]{2})(?!\\p{L})\\s+c\u1EAFt\\s+ti\u1EBFp\\s*tuy\u1EBFn\\s+t\u1EA1i\\s+([A-Z])(?!\\p{L})(?:\\s+c\u1EE7a\\s+[^.]{0,12}?)?\\s+(?:\u1EDF|t\u1EA1i)\\s+([A-Z])(?![A-Z])",
|
|
@@ -5744,7 +6482,7 @@ var parallelCutsTangentAtRule = {
|
|
|
5744
6482
|
id: "parallelCutsTangentAt",
|
|
5745
6483
|
priority: 52,
|
|
5746
6484
|
languages: ["vi"],
|
|
5747
|
-
patterns: [
|
|
6485
|
+
patterns: [PREFILTER40],
|
|
5748
6486
|
match(ctx) {
|
|
5749
6487
|
const circle = circleName4(ctx.problem);
|
|
5750
6488
|
if (!circle) return [];
|
|
@@ -5775,7 +6513,7 @@ var parallelCutsTangentAtRule = {
|
|
|
5775
6513
|
};
|
|
5776
6514
|
|
|
5777
6515
|
// src/stamps/geometry-2d/ai/rules/inExCircleTangentBC.ts
|
|
5778
|
-
var
|
|
6516
|
+
var PREFILTER41 = /[Đđ]ường\s*tròn\s+(?:nội|bàng)\s*tiếp[^.]{0,60}?tâm\s+[A-Z][^.]{0,30}?tiếp\s*xúc/u;
|
|
5779
6517
|
var TANGENT_TAIL = "\\s*,?\\s*ti\u1EBFp\\s*x\xFAc\\s+(?:v\u1EDBi\\s+)?(?:c\u1EA1nh\\s+|\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+)?([A-Z]{2})(?!\\p{L})\\s+(?:\u1EDF|t\u1EA1i)\\s+([A-Z])(?![A-Z])";
|
|
5780
6518
|
var INCIRCLE = new RegExp(
|
|
5781
6519
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*tr\xF2n\\s+n\u1ED9i\\s*ti\u1EBFp\\s+tam\\s*gi\xE1c\\s+([A-Z]{3})(?![A-Z])[^.]{0,20}?t\xE2m\\s+([A-Z])(?!\\p{L})" + TANGENT_TAIL,
|
|
@@ -5789,7 +6527,7 @@ var inExCircleTangentBCRule = {
|
|
|
5789
6527
|
id: "inExCircleTangentBC",
|
|
5790
6528
|
priority: 69,
|
|
5791
6529
|
languages: ["vi"],
|
|
5792
|
-
patterns: [
|
|
6530
|
+
patterns: [PREFILTER41],
|
|
5793
6531
|
match(ctx) {
|
|
5794
6532
|
const out = [];
|
|
5795
6533
|
for (const c of ctx.clauses) {
|
|
@@ -5831,7 +6569,7 @@ var inExCircleTangentBCRule = {
|
|
|
5831
6569
|
};
|
|
5832
6570
|
|
|
5833
6571
|
// src/stamps/geometry-2d/ai/rules/tangentPointsFromExt.ts
|
|
5834
|
-
var
|
|
6572
|
+
var PREFILTER42 = /[Tt]ừ\s+(?:điểm\s+)?[A-Z][^.]{0,60}?tiếp\s*tuyến\s+[A-Z]{2}\s*,\s*[A-Z]{2}/u;
|
|
5835
6573
|
var RE16 = new RegExp(
|
|
5836
6574
|
"[Tt]\u1EEB\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})[^.]{0,60}?ti\u1EBFp\\s*tuy\u1EBFn\\s+([A-Z])([A-Z])\\s*,\\s*([A-Z])([A-Z])(?!\\p{L})",
|
|
5837
6575
|
"gu"
|
|
@@ -5841,7 +6579,7 @@ var tangentPointsFromExtRule = {
|
|
|
5841
6579
|
id: "tangentPointsFromExt",
|
|
5842
6580
|
priority: 50,
|
|
5843
6581
|
languages: ["vi"],
|
|
5844
|
-
patterns: [
|
|
6582
|
+
patterns: [PREFILTER42],
|
|
5845
6583
|
match(ctx) {
|
|
5846
6584
|
const out = [];
|
|
5847
6585
|
for (const c of ctx.clauses) {
|
|
@@ -5876,7 +6614,11 @@ var VN_FORM2 = new RegExp(
|
|
|
5876
6614
|
"u"
|
|
5877
6615
|
);
|
|
5878
6616
|
var VN_REV = new RegExp(
|
|
5879
|
-
String.raw`điểm\s+([A-Z])(?:['′]?)
|
|
6617
|
+
String.raw`điểm\s+([A-Z])(?:['′]?)[^.A-Z]{0,18}?ngoài\s+đường\s*tròn\s*(?:tâm\s+)?\(?\s*([A-Z])?`,
|
|
6618
|
+
"u"
|
|
6619
|
+
);
|
|
6620
|
+
var VN_REV_BARE = new RegExp(
|
|
6621
|
+
String.raw`(?<![\p{L}])([A-Z])(?:['′]?)[^.A-Z]{0,18}?ngoài\s+(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)`,
|
|
5880
6622
|
"u"
|
|
5881
6623
|
);
|
|
5882
6624
|
var RESOLVE_CENTER2 = /đường\s*tròn\s*(?:\(\s*)?(?:tâm\s+)?([A-Z])(?![A-Za-z])|\(\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)/u;
|
|
@@ -5905,17 +6647,23 @@ var circleExternalPointRule = {
|
|
|
5905
6647
|
} else if (VN_METRIC.test(c.text)) {
|
|
5906
6648
|
const mm = VN_METRIC.exec(c.text);
|
|
5907
6649
|
const [ctr, r, pt, pair, d] = [mm[1], mm[2], mm[3], mm[4], mm[5]];
|
|
5908
|
-
const
|
|
5909
|
-
if (pair.includes(ctr) && pair.includes(pt) &&
|
|
6650
|
+
const norm5 = (s) => parseFloat(s.replace(",", "."));
|
|
6651
|
+
if (pair.includes(ctr) && pair.includes(pt) && norm5(d) > norm5(r)) {
|
|
5910
6652
|
center = ctr;
|
|
5911
6653
|
ext = pt;
|
|
5912
6654
|
}
|
|
5913
6655
|
} else {
|
|
5914
6656
|
const rv = VN_REV.exec(c.text);
|
|
5915
|
-
if (
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
6657
|
+
if (rv) {
|
|
6658
|
+
ext = rv[1];
|
|
6659
|
+
const rc = rv[2] ? void 0 : RESOLVE_CENTER2.exec(ctx.problem);
|
|
6660
|
+
center = rv[2] ?? rc?.[1] ?? rc?.[2] ?? "O";
|
|
6661
|
+
} else {
|
|
6662
|
+
const rb = VN_REV_BARE.exec(c.text);
|
|
6663
|
+
if (!rb) continue;
|
|
6664
|
+
ext = rb[1];
|
|
6665
|
+
center = rb[2];
|
|
6666
|
+
}
|
|
5919
6667
|
}
|
|
5920
6668
|
if (!center || !ext || center === ext) continue;
|
|
5921
6669
|
out.push({
|
|
@@ -5932,7 +6680,7 @@ var circleExternalPointRule = {
|
|
|
5932
6680
|
};
|
|
5933
6681
|
|
|
5934
6682
|
// src/stamps/geometry-2d/ai/rules/tangentNamedFromExt.ts
|
|
5935
|
-
var
|
|
6683
|
+
var PREFILTER43 = /(?:[Kk]ẻ|[Vv]ẽ)\s+(?:các\s+|hai\s+|2\s+)?tiếp\s*tuyến\s+(?:thứ\s+\S+\s+)?[A-Z]{2}|[Cc]ác\s+tiếp\s*tuyến\s+với|[Tt]iếp\s*tuyến\s+[A-Z]{2}\s*(?:,|và)\s*[A-Z]{2}|[Tt]ừ\s+[A-Z][^.;]{0,20}?(?:kẻ|vẽ)\s+(?:hai|2|các)\s+tiếp\s*tuyến|[A-Z]{2}\s*(?:,|và)\s*[A-Z]{2}\s+là\s+(?:hai\s+|2\s+|các\s+)?tiếp\s*tuyến/u;
|
|
5936
6684
|
var TWO = new RegExp(
|
|
5937
6685
|
"(?:[Kk]\u1EBB|[Vv]\u1EBD)\\s+(?:c\xE1c\\s+|hai\\s+|2\\s+)?ti\u1EBFp\\s*tuy\u1EBFn\\s+([A-Z])([A-Z])\\s*(?:,\\s*|v\xE0\\s+)([A-Z])([A-Z])(?!\\p{L})",
|
|
5938
6686
|
"gu"
|
|
@@ -5945,6 +6693,18 @@ var FROM_TOUCH = new RegExp(
|
|
|
5945
6693
|
"[Cc]\xE1c\\s+ti\u1EBFp\\s*tuy\u1EBFn\\s+v\u1EDBi\\s+\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s+k\u1EBB\\s+t\u1EEB\\s+([A-Z])(?!\\p{L})[^.;]{0,40}?t\u1EA1i\\s+([A-Z])\\s*(?:,\\s*|v\xE0\\s+)([A-Z])(?!\\p{L})",
|
|
5946
6694
|
"gu"
|
|
5947
6695
|
);
|
|
6696
|
+
var FROM_DRAW_TOUCH = new RegExp(
|
|
6697
|
+
"[Tt]\u1EEB\\s+([A-Z])(?!\\p{L})[^.;]{0,20}?(?:k\u1EBB|v\u1EBD)\\s+(?:hai|2|c\xE1c)\\s+ti\u1EBFp\\s*tuy\u1EBFn[^.;]{0,40}?t\u1EA1i\\s+([A-Z])\\s*(?:,\\s*|v\xE0\\s+)([A-Z])(?!\\p{L})",
|
|
6698
|
+
"gu"
|
|
6699
|
+
);
|
|
6700
|
+
var TWO_BARE = new RegExp(
|
|
6701
|
+
"[Tt]i\u1EBFp\\s*tuy\u1EBFn\\s+([A-Z])([A-Z])\\s*(?:,\\s*|v\xE0\\s+)([A-Z])([A-Z])(?!\\p{L})",
|
|
6702
|
+
"gu"
|
|
6703
|
+
);
|
|
6704
|
+
var NAME_BEFORE_TWO = new RegExp(
|
|
6705
|
+
"([A-Z])([A-Z])\\s*(?:,\\s*|v\xE0\\s+)([A-Z])([A-Z])(?!\\p{L})\\s+l\xE0\\s+(?:hai\\s+|2\\s+|c\xE1c\\s+)?ti\u1EBFp\\s*tuy\u1EBFn",
|
|
6706
|
+
"gu"
|
|
6707
|
+
);
|
|
5948
6708
|
var TOUCH_APPOS = new RegExp(
|
|
5949
6709
|
"^(?:v\u1EDBi\\s+)?([A-Z])\\s*(?:,\\s*|v\xE0\\s+)([A-Z])\\s+l\xE0\\s+(?:c\xE1c\\s+|hai\\s+|2\\s+)?ti\u1EBFp\\s*\u0111i\u1EC3m",
|
|
5950
6710
|
"u"
|
|
@@ -5954,7 +6714,7 @@ var tangentNamedFromExtRule = {
|
|
|
5954
6714
|
id: "tangentNamedFromExt",
|
|
5955
6715
|
priority: 50,
|
|
5956
6716
|
languages: ["vi"],
|
|
5957
|
-
patterns: [
|
|
6717
|
+
patterns: [PREFILTER43],
|
|
5958
6718
|
match(ctx) {
|
|
5959
6719
|
const par = PAREN_CIRCLE2.exec(ctx.problem);
|
|
5960
6720
|
const circle = par ? par[1] : "O";
|
|
@@ -5983,14 +6743,43 @@ var tangentNamedFromExtRule = {
|
|
|
5983
6743
|
});
|
|
5984
6744
|
}
|
|
5985
6745
|
}
|
|
6746
|
+
for (const c of ctx.clauses) {
|
|
6747
|
+
if (twoClauses.has(c.id)) continue;
|
|
6748
|
+
TWO_BARE.lastIndex = 0;
|
|
6749
|
+
NAME_BEFORE_TWO.lastIndex = 0;
|
|
6750
|
+
const hits = [...c.text.matchAll(TWO_BARE), ...c.text.matchAll(NAME_BEFORE_TWO)];
|
|
6751
|
+
for (const m of hits) {
|
|
6752
|
+
if (twoClauses.has(c.id)) continue;
|
|
6753
|
+
const ext = m[1];
|
|
6754
|
+
if (m[3] !== ext) continue;
|
|
6755
|
+
const p = m[2];
|
|
6756
|
+
const q = m[4];
|
|
6757
|
+
if (p === q || p === ext || q === ext) continue;
|
|
6758
|
+
twoClauses.add(c.id);
|
|
6759
|
+
whichOf.set(ext, 1);
|
|
6760
|
+
out.push({
|
|
6761
|
+
ruleId: "tangentNamedFromExt",
|
|
6762
|
+
clauseIds: [c.id],
|
|
6763
|
+
intents: [
|
|
6764
|
+
addPoint(p, { kind: "tangentPoint", from: ext, circle, which: 0 }),
|
|
6765
|
+
addPoint(q, { kind: "tangentPoint", from: ext, circle, which: 1 }),
|
|
6766
|
+
connect(ext, p, "segment"),
|
|
6767
|
+
connect(ext, q, "segment")
|
|
6768
|
+
]
|
|
6769
|
+
});
|
|
6770
|
+
}
|
|
6771
|
+
}
|
|
5986
6772
|
for (const c of ctx.clauses) {
|
|
5987
6773
|
if (twoClauses.has(c.id)) continue;
|
|
5988
6774
|
FROM_TOUCH.lastIndex = 0;
|
|
5989
|
-
|
|
6775
|
+
FROM_DRAW_TOUCH.lastIndex = 0;
|
|
6776
|
+
const hits = [...c.text.matchAll(FROM_TOUCH), ...c.text.matchAll(FROM_DRAW_TOUCH)];
|
|
6777
|
+
for (const m of hits) {
|
|
5990
6778
|
const ext = m[1];
|
|
5991
6779
|
const p = m[2];
|
|
5992
6780
|
const q = m[3];
|
|
5993
6781
|
if (p === q || p === ext || q === ext) continue;
|
|
6782
|
+
if (twoClauses.has(c.id)) continue;
|
|
5994
6783
|
twoClauses.add(c.id);
|
|
5995
6784
|
whichOf.set(ext, 1);
|
|
5996
6785
|
out.push({
|
|
@@ -6044,11 +6833,16 @@ var tangentNamedFromExtRule = {
|
|
|
6044
6833
|
// src/stamps/geometry-2d/ai/rules/secant.ts
|
|
6045
6834
|
var CAT_TUYEN = /cát\s*tuyến\s+([A-Z])([A-Z])([A-Z])(?![A-Z])/gu;
|
|
6046
6835
|
var LINE_THROUGH = new RegExp(
|
|
6047
|
-
String.raw`[Qq]ua\s+(?:điểm\s+)?([A-Z])(?!\p{L})[^.]{0,30}?cắt\s+(?:đường\s*tròn\s*)?(?:\(\s*([A-Z])\s*\)\s
|
|
6836
|
+
String.raw`[Qq]ua\s+(?:điểm\s+)?([A-Z])(?!\p{L})[^.]{0,30}?cắt\s+(?:lại\s+)?(?:nửa\s+)?(?:đường\s*tròn\s*)?(?:\(\s*([A-Z])\s*\)\s*|[A-Z]\s+)?(?:tại|ở)\s+(?:hai\s+|2\s+)?(?:điểm\s+)?([A-Z])\s*(?:và|,)\s*([A-Z])(?![A-Z])`,
|
|
6837
|
+
"gu"
|
|
6838
|
+
);
|
|
6839
|
+
var CAT_CUTS = new RegExp(
|
|
6840
|
+
String.raw`cát\s*tuyến\s+(?:[a-z][a-z0-9]?\s+)?cắt\s+(?:lại\s+)?(?:nửa\s+)?(?:đường\s*tròn\s*)?(?:\(\s*([A-Z])\s*\)\s*|[A-Z]\s+)?(?:tại|ở)\s+(?:hai\s+|2\s+)?(?:điểm\s+)?([A-Z])\s*(?:và|,)\s*([A-Z])(?![A-Z])`,
|
|
6048
6841
|
"gu"
|
|
6049
6842
|
);
|
|
6050
6843
|
var PAREN_CIRCLE3 = /\(\s*([A-Z])\s*\)/u;
|
|
6051
|
-
var
|
|
6844
|
+
var EXT_RESOLVE = /([A-Z])(?!\p{L})[^.A-Z]{0,25}?(?:ở\s+|nằm\s+)?ngoài\s+(?:\(|nửa|đường\s*tròn)/u;
|
|
6845
|
+
var PREFILTER44 = /cát\s*tuyến|[Qq]ua\s+(?:điểm\s+)?[A-Z][^.]{0,30}?cắt/u;
|
|
6052
6846
|
function secantIntents(ext, near, far, circle, theta) {
|
|
6053
6847
|
if ((/* @__PURE__ */ new Set([ext, near, far])).size !== 3) return null;
|
|
6054
6848
|
const line = ext + near;
|
|
@@ -6062,13 +6856,26 @@ var secantRule = {
|
|
|
6062
6856
|
id: "secant",
|
|
6063
6857
|
priority: 58,
|
|
6064
6858
|
languages: ["vi"],
|
|
6065
|
-
patterns: [
|
|
6859
|
+
patterns: [PREFILTER44],
|
|
6066
6860
|
match(ctx) {
|
|
6067
6861
|
const par = PAREN_CIRCLE3.exec(ctx.problem);
|
|
6068
6862
|
const fallbackCircle = par ? par[1] : "O";
|
|
6863
|
+
const extM = EXT_RESOLVE.exec(ctx.problem);
|
|
6864
|
+
const resolvedExt = extM ? extM[1] : void 0;
|
|
6069
6865
|
const out = [];
|
|
6070
6866
|
let theta = 2.4;
|
|
6071
6867
|
for (const c of ctx.clauses) {
|
|
6868
|
+
if (resolvedExt) {
|
|
6869
|
+
CAT_CUTS.lastIndex = 0;
|
|
6870
|
+
for (const m of c.text.matchAll(CAT_CUTS)) {
|
|
6871
|
+
const circle = m[1] ?? fallbackCircle;
|
|
6872
|
+
const intents = secantIntents(resolvedExt, m[2], m[3], circle, theta);
|
|
6873
|
+
if (intents) {
|
|
6874
|
+
out.push({ ruleId: "secant", clauseIds: [c.id], intents });
|
|
6875
|
+
theta += 0.7;
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6878
|
+
}
|
|
6072
6879
|
CAT_TUYEN.lastIndex = 0;
|
|
6073
6880
|
for (const m of c.text.matchAll(CAT_TUYEN)) {
|
|
6074
6881
|
const intents = secantIntents(m[1], m[2], m[3], fallbackCircle, theta);
|
|
@@ -6093,14 +6900,21 @@ var secantRule = {
|
|
|
6093
6900
|
|
|
6094
6901
|
// src/stamps/geometry-2d/ai/rules/oppositeRayPoint.ts
|
|
6095
6902
|
var RE17 = new RegExp(
|
|
6096
|
-
String.raw`[Tt]rên\s+tia\s+đối\s+(?:của\s+)?tia\s+([A-Z])([A-Z])(?![A-Z])[^.]{0,20}?lấy\s+(?:một\s+)?(?:điểm\s+)?([A-Z])(?![A-Z])`,
|
|
6903
|
+
String.raw`[Tt]rên\s+tia\s+đối\s+(?:của\s+)?(?:tia\s+)?([A-Z])([A-Z])(?![A-Z])[^.]{0,20}?lấy\s+(?:một\s+)?(?:điểm\s+)?([A-Z])(?![A-Z])`,
|
|
6097
6904
|
"gu"
|
|
6098
6905
|
);
|
|
6099
6906
|
var RE_NAME_FIRST = new RegExp(
|
|
6100
|
-
String.raw`[Ll]ấy\s+(?:điểm\s+)?([A-Z])(?![A-Z])\s+trên\s+tia\s+đối\s+(?:của\s+)?tia\s+([A-Z])([A-Z])(?![A-Z])`,
|
|
6907
|
+
String.raw`[Ll]ấy\s+(?:điểm\s+)?([A-Z])(?![A-Z])\s+trên\s+tia\s+đối\s+(?:của\s+)?(?:tia\s+)?([A-Z])([A-Z])(?![A-Z])`,
|
|
6101
6908
|
"gu"
|
|
6102
6909
|
);
|
|
6910
|
+
var RE_DISTRIB = new RegExp(
|
|
6911
|
+
String.raw`[Ll]ấy\s+(?:các\s+)?(?:điểm\s+)?((?:[A-Z]\s*(?:,|và)\s*)+[A-Z])(?![A-Z])\s+(?:lần\s*lượt\s+|(?:theo\s+)?thứ\s+tự\s+)?(?:thuộc|trên|nằm\s+trên)\s+(?:các\s+)?tia\s+đối\s+(?:của\s+)?(?:các\s+)?(?:tia\s+)?((?:[A-Z]{2}\s*(?:,|và)\s*)+[A-Z]{2})(?![A-Z])`,
|
|
6912
|
+
"u"
|
|
6913
|
+
);
|
|
6103
6914
|
var CANON = 2.5;
|
|
6915
|
+
function splitNames2(blob) {
|
|
6916
|
+
return blob.split(/\s*,\s*|\s+và\s+/u).map((s) => s.trim()).filter(Boolean);
|
|
6917
|
+
}
|
|
6104
6918
|
var oppositeRayPointRule = {
|
|
6105
6919
|
id: "oppositeRayPoint",
|
|
6106
6920
|
priority: 56,
|
|
@@ -6109,6 +6923,35 @@ var oppositeRayPointRule = {
|
|
|
6109
6923
|
match(ctx) {
|
|
6110
6924
|
const out = [];
|
|
6111
6925
|
for (const c of ctx.clauses) {
|
|
6926
|
+
const dm = RE_DISTRIB.exec(c.text);
|
|
6927
|
+
if (dm) {
|
|
6928
|
+
const names = splitNames2(dm[1]);
|
|
6929
|
+
const rays = splitNames2(dm[2]);
|
|
6930
|
+
if (names.length >= 2 && names.length === rays.length) {
|
|
6931
|
+
const intents = [];
|
|
6932
|
+
let valid3 = true;
|
|
6933
|
+
for (let i = 0; i < names.length; i++) {
|
|
6934
|
+
const a = rays[i][0];
|
|
6935
|
+
const b = rays[i][1];
|
|
6936
|
+
if ((/* @__PURE__ */ new Set([a, b, names[i]])).size !== 3) {
|
|
6937
|
+
valid3 = false;
|
|
6938
|
+
break;
|
|
6939
|
+
}
|
|
6940
|
+
intents.push(
|
|
6941
|
+
addPoint(names[i], {
|
|
6942
|
+
kind: "pointAtDistance",
|
|
6943
|
+
from: b,
|
|
6944
|
+
through: a,
|
|
6945
|
+
distance: { kind: "literal", value: CANON }
|
|
6946
|
+
})
|
|
6947
|
+
);
|
|
6948
|
+
}
|
|
6949
|
+
if (valid3) {
|
|
6950
|
+
out.push({ ruleId: "oppositeRayPoint", clauseIds: [c.id], intents });
|
|
6951
|
+
continue;
|
|
6952
|
+
}
|
|
6953
|
+
}
|
|
6954
|
+
}
|
|
6112
6955
|
RE17.lastIndex = 0;
|
|
6113
6956
|
for (const m of c.text.matchAll(RE17)) {
|
|
6114
6957
|
const a = m[1];
|
|
@@ -6164,7 +7007,7 @@ var CENTER3 = /\(\s*([A-Z])\s*[;,)]|tâm\s+([A-Z])(?![A-Za-z])/u;
|
|
|
6164
7007
|
function appearsBefore(problem, chordText, d) {
|
|
6165
7008
|
const pos = problem.indexOf(chordText);
|
|
6166
7009
|
if (pos < 0) return true;
|
|
6167
|
-
return new RegExp(`(?<![A-Za-z])${d}(?![a-z])`, "u").test(problem.slice(0, pos));
|
|
7010
|
+
return new RegExp(`(?<![A-Za-z])${escapeRe(d)}(?![a-z])`, "u").test(problem.slice(0, pos));
|
|
6168
7011
|
}
|
|
6169
7012
|
var perpChordAtFootRule = {
|
|
6170
7013
|
id: "perpChordAtFoot",
|
|
@@ -6260,7 +7103,7 @@ var diameterCircleSecantRule = {
|
|
|
6260
7103
|
};
|
|
6261
7104
|
|
|
6262
7105
|
// src/stamps/geometry-2d/ai/rules/tangentsAtMeet.ts
|
|
6263
|
-
var
|
|
7106
|
+
var PREFILTER45 = /[Tt]iếp\s*tuyến[^.]{0,40}?tại\s+[A-Z][^.]{0,30}?(?:và|,)\s*[A-Z][^.]{0,30}?cắt\s+nhau/u;
|
|
6264
7107
|
var CIRCLE_REF6 = /đường\s*tròn\s*(?:tâm\s+)?\(?\s*([A-ZωΩ])(?:\s*[;,]\s*[Rr])?\s*\)?|\(\s*([A-ZωΩ])\s*\)/u;
|
|
6265
7108
|
var RE19 = new RegExp(
|
|
6266
7109
|
"[Tt]i\u1EBFp\\s*tuy\u1EBFn\\s+(?:c\u1EE7a\\s+[^.]{0,14}?\\s+)?t\u1EA1i\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])\\s*(?:v\xE0|,)\\s*([A-Z])(?!\\p{L})(?:\\s+c\u1EE7a\\s+[^.]{0,14}?)?\\s+c\u1EAFt\\s+nhau\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
@@ -6277,7 +7120,7 @@ var tangentsAtMeetRule = {
|
|
|
6277
7120
|
priority: 55,
|
|
6278
7121
|
// trên intersection(45) — 2 tiếp tuyến + giao trong CÙNG match nên tự đủ thứ tự
|
|
6279
7122
|
languages: ["vi"],
|
|
6280
|
-
patterns: [
|
|
7123
|
+
patterns: [PREFILTER45],
|
|
6281
7124
|
match(ctx) {
|
|
6282
7125
|
const circle = circleName5(ctx.problem);
|
|
6283
7126
|
if (!circle) return [];
|
|
@@ -6305,7 +7148,7 @@ var tangentsAtMeetRule = {
|
|
|
6305
7148
|
};
|
|
6306
7149
|
|
|
6307
7150
|
// src/stamps/geometry-2d/ai/rules/angleBisectorCutsSideCircle.ts
|
|
6308
|
-
var
|
|
7151
|
+
var PREFILTER46 = /phân\s*giác[^.]{0,40}?cắt/u;
|
|
6309
7152
|
var FOOT = new RegExp(
|
|
6310
7153
|
"ph\xE2n\\s*gi\xE1c\\s+(?:trong\\s+)?(?:c\u1EE7a\\s+)?g\xF3c\\s+([A-Z])([A-Z])?([A-Z])?(?![A-Z])[^.]{0,20}?c\u1EAFt\\s+(?:c\u1EA1nh\\s+)?([A-Z])([A-Z])(?![A-Z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
6311
7154
|
"u"
|
|
@@ -6320,7 +7163,7 @@ var angleBisectorCutsSideCircleRule = {
|
|
|
6320
7163
|
priority: 48,
|
|
6321
7164
|
// sau triangle/circle/cevian; trước intersection(45)
|
|
6322
7165
|
languages: ["vi"],
|
|
6323
|
-
patterns: [
|
|
7166
|
+
patterns: [PREFILTER46],
|
|
6324
7167
|
match(ctx) {
|
|
6325
7168
|
const out = [];
|
|
6326
7169
|
for (const c of ctx.clauses) {
|
|
@@ -6366,7 +7209,7 @@ var angleBisectorCutsSideCircleRule = {
|
|
|
6366
7209
|
};
|
|
6367
7210
|
|
|
6368
7211
|
// src/stamps/geometry-2d/ai/rules/circleCutsArcSecond.ts
|
|
6369
|
-
var
|
|
7212
|
+
var PREFILTER47 = /đường\s*tròn\s+đường\s*kính\s+[A-Z]{2}[^.]{0,40}?cắt\s+(?:cung|(?:nửa\s+)?đường\s*tròn)/u;
|
|
6370
7213
|
var CUTS_ARC = new RegExp(
|
|
6371
7214
|
"\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s+\u0111\u01B0\u1EDDng\\s*k\xEDnh\\s+([A-Z])([A-Z])(?![A-Z])[^.]{0,20}?c\u1EAFt\\s+cung\\s+(?:nh\u1ECF\\s+|l\u1EDBn\\s+)?([A-Z])([A-Z])(?![A-Z])[^.]{0,24}?(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])(?:\\s+kh\xE1c\\s+([A-Z])(?![A-Z]))?",
|
|
6372
7215
|
"u"
|
|
@@ -6381,7 +7224,7 @@ var circleCutsArcSecondRule = {
|
|
|
6381
7224
|
priority: 45,
|
|
6382
7225
|
// sau mọi rule dựng đường tròn + điểm chung
|
|
6383
7226
|
languages: ["vi"],
|
|
6384
|
-
patterns: [
|
|
7227
|
+
patterns: [PREFILTER47],
|
|
6385
7228
|
match(ctx) {
|
|
6386
7229
|
const out = [];
|
|
6387
7230
|
for (const c of ctx.clauses) {
|
|
@@ -6420,7 +7263,7 @@ var circleCutsArcSecondRule = {
|
|
|
6420
7263
|
};
|
|
6421
7264
|
|
|
6422
7265
|
// src/stamps/geometry-2d/ai/rules/angleBisectorCutsCircleLine.ts
|
|
6423
|
-
var
|
|
7266
|
+
var PREFILTER48 = /[Pp]hân\s*giác[^.]{0,40}?cắt\s+(?:đường\s*tròn|\()/u;
|
|
6424
7267
|
var RE20 = new RegExp(
|
|
6425
7268
|
"[Pp]h\xE2n\\s*gi\xE1c\\s+(?:trong\\s+)?(?:c\u1EE7a\\s+)?g\xF3c\\s+([A-Z])([A-Z])([A-Z])(?![A-Z])\\s+c\u1EAFt\\s+(?:\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s*)?(?:\\(\\s*([A-Z])(?:['\u2032]?)\\s*\\))?[^.]{0,16}?(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])(?:\\s+kh\xE1c\\s+[A-Z](?![A-Z]))?(?:\\s*,?\\s*v\xE0\\s+(?:c\u1EAFt\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?([A-Z]{2})(?![A-Z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z]))?",
|
|
6426
7269
|
"u"
|
|
@@ -6433,7 +7276,7 @@ var angleBisectorCutsCircleLineRule = {
|
|
|
6433
7276
|
// perpFoot 65 dựng tia/C/H trước).
|
|
6434
7277
|
priority: 49,
|
|
6435
7278
|
languages: ["vi"],
|
|
6436
|
-
patterns: [
|
|
7279
|
+
patterns: [PREFILTER48],
|
|
6437
7280
|
match(ctx) {
|
|
6438
7281
|
const out = [];
|
|
6439
7282
|
for (const c of ctx.clauses) {
|
|
@@ -6465,17 +7308,34 @@ var angleBisectorCutsCircleLineRule = {
|
|
|
6465
7308
|
};
|
|
6466
7309
|
|
|
6467
7310
|
// src/stamps/geometry-2d/ai/rules/tangentLineNamedAtPoint.ts
|
|
6468
|
-
var
|
|
7311
|
+
var PREFILTER49 = /(?:trên|thuộc)\s+\(\s*[A-Z][^)]*\)[^.]{0,24}?kẻ\s+tiếp\s*tuyến\s+[a-z]|tiếp\s*tuyến\s+[a-z][0-9]?\s+(?:của|với)\s+|(?:[Qq]ua|[Tt]ừ)\s+[A-Z]\s+(?:vẽ|kẻ)\s+tiếp\s*tuyến\s+[a-z]/u;
|
|
6469
7312
|
var RE21 = new RegExp(
|
|
6470
7313
|
"(?:t\u1EEB\\s+|qua\\s+)?(?:m\u1ED9t\\s+)?\u0111i\u1EC3m\\s+([A-Z])(?![A-Z])\\s+(?:tr\xEAn|thu\u1ED9c)\\s+\\(\\s*([A-Z])(?:['\u2032]?)\\s*\\)[^.]{0,24}?k\u1EBB\\s+ti\u1EBFp\\s*tuy\u1EBFn\\s+([a-z][0-9]?)(?!\\p{L})",
|
|
6471
7314
|
"gu"
|
|
6472
7315
|
);
|
|
7316
|
+
var RE_NAMED_OF_AT = new RegExp(
|
|
7317
|
+
"(?:[VvKk]\u1EBD|[Kk]\u1EBB|[Dd]\u1EF1ng)?\\s*ti\u1EBFp\\s*tuy\u1EBFn\\s+([a-z][0-9]?)(?!\\p{L})\\s+(?:c\u1EE7a|v\u1EDBi)\\s+(?:\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s*)?\\(\\s*([A-Z])(?:['\u2032]?)\\s*\\)\\s+t\u1EA1i\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
7318
|
+
"gu"
|
|
7319
|
+
);
|
|
7320
|
+
var RE_THROUGH_NAMED = new RegExp(
|
|
7321
|
+
"(?:[Qq]ua|[Tt]\u1EEB)\\s+([A-Z])(?![A-Z])\\s+(?:v\u1EBD|k\u1EBB)\\s+ti\u1EBFp\\s*tuy\u1EBFn\\s+([a-z]{1,2}[0-9]?)(?!\\p{L})",
|
|
7322
|
+
"gu"
|
|
7323
|
+
);
|
|
7324
|
+
var PAREN_CIRCLE4 = /\(\s*([A-Z])(?:\s*[;,]\s*[Rr])?\s*\)|đường\s*tròn\s+tâm\s+([A-Z])(?![A-Za-z])/u;
|
|
7325
|
+
function circleDslName(problem, center) {
|
|
7326
|
+
return /đường\s*kính/u.test(problem) ? `${center}_c` : center;
|
|
7327
|
+
}
|
|
7328
|
+
function resolveProblemCircle(problem) {
|
|
7329
|
+
const m = PAREN_CIRCLE4.exec(problem);
|
|
7330
|
+
const center = m?.[1] ?? m?.[2];
|
|
7331
|
+
return center ? circleDslName(problem, center) : void 0;
|
|
7332
|
+
}
|
|
6473
7333
|
var tangentLineNamedAtPointRule = {
|
|
6474
7334
|
id: "tangentLineNamedAtPoint",
|
|
6475
7335
|
priority: 63,
|
|
6476
7336
|
// cao như tangentAt — đường d phải dựng trước điểm-trên-d
|
|
6477
7337
|
languages: ["vi"],
|
|
6478
|
-
patterns: [
|
|
7338
|
+
patterns: [PREFILTER49],
|
|
6479
7339
|
match(ctx) {
|
|
6480
7340
|
const out = [];
|
|
6481
7341
|
for (const c of ctx.clauses) {
|
|
@@ -6490,6 +7350,30 @@ var tangentLineNamedAtPointRule = {
|
|
|
6490
7350
|
intents: [drawLine(line, "tangentAt", { through, circle })]
|
|
6491
7351
|
});
|
|
6492
7352
|
}
|
|
7353
|
+
RE_NAMED_OF_AT.lastIndex = 0;
|
|
7354
|
+
for (const m of c.text.matchAll(RE_NAMED_OF_AT)) {
|
|
7355
|
+
const line = m[1];
|
|
7356
|
+
const circle = circleDslName(ctx.problem, m[2]);
|
|
7357
|
+
const through = m[3];
|
|
7358
|
+
out.push({
|
|
7359
|
+
ruleId: "tangentLineNamedAtPoint",
|
|
7360
|
+
clauseIds: [c.id],
|
|
7361
|
+
intents: [drawLine(line, "tangentAt", { through, circle })]
|
|
7362
|
+
});
|
|
7363
|
+
}
|
|
7364
|
+
RE_THROUGH_NAMED.lastIndex = 0;
|
|
7365
|
+
const probCircle = resolveProblemCircle(ctx.problem);
|
|
7366
|
+
if (probCircle) {
|
|
7367
|
+
for (const m of c.text.matchAll(RE_THROUGH_NAMED)) {
|
|
7368
|
+
const through = m[1];
|
|
7369
|
+
const line = m[2];
|
|
7370
|
+
out.push({
|
|
7371
|
+
ruleId: "tangentLineNamedAtPoint",
|
|
7372
|
+
clauseIds: [c.id],
|
|
7373
|
+
intents: [drawLine(line, "tangentAt", { through, circle: probCircle })]
|
|
7374
|
+
});
|
|
7375
|
+
}
|
|
7376
|
+
}
|
|
6493
7377
|
}
|
|
6494
7378
|
return out;
|
|
6495
7379
|
}
|
|
@@ -6535,13 +7419,13 @@ var givenDiameterCircleRule = {
|
|
|
6535
7419
|
|
|
6536
7420
|
// src/stamps/geometry-2d/ai/rules/multiDiameterCircles.ts
|
|
6537
7421
|
var RE23 = /đường\s*kính\s+(?:theo\s+thứ\s+tự\s+)?(?:là\s+)?((?:[A-Z]{2}\s*,\s*)+[A-Z]{2})(?![A-Z])[^.]{0,30}?tâm\s+(?:theo\s+thứ\s+tự\s+)?(?:là\s+)?((?:[A-Z](?:['′])?\s*,\s*)+[A-Z](?:['′])?)(?![A-Z])/u;
|
|
6538
|
-
var
|
|
7422
|
+
var PREFILTER50 = /đường\s*kính[^.]{0,40}?tâm/u;
|
|
6539
7423
|
var multiDiameterCirclesRule = {
|
|
6540
7424
|
id: "multiDiameterCircles",
|
|
6541
7425
|
priority: 67,
|
|
6542
7426
|
// như circleDiameter — dựng đầu mút + tâm trước điểm phái sinh
|
|
6543
7427
|
languages: ["vi"],
|
|
6544
|
-
patterns: [
|
|
7428
|
+
patterns: [PREFILTER50],
|
|
6545
7429
|
match(ctx) {
|
|
6546
7430
|
for (const c of ctx.clauses) {
|
|
6547
7431
|
const m = RE23.exec(c.text);
|
|
@@ -6593,12 +7477,12 @@ function diameterMap(problem) {
|
|
|
6593
7477
|
}
|
|
6594
7478
|
return map;
|
|
6595
7479
|
}
|
|
6596
|
-
var
|
|
7480
|
+
var PREFILTER51 = /giao\s*điểm\s+(?:của\s+)?[A-Z]{2}\s*,\s*[A-Z]{2}[^.]{0,30}?(?:với|cắt)\s+(?:các\s+)?(?:(?:nửa\s+)?đường\s*tròn\s*)?\(/u;
|
|
6597
7481
|
var cutCirclesDistribRule = {
|
|
6598
7482
|
id: "cutCirclesDistrib",
|
|
6599
7483
|
priority: 45,
|
|
6600
7484
|
languages: ["vi"],
|
|
6601
|
-
patterns: [
|
|
7485
|
+
patterns: [PREFILTER51],
|
|
6602
7486
|
match(ctx) {
|
|
6603
7487
|
const map = diameterMap(ctx.problem);
|
|
6604
7488
|
const out = [];
|
|
@@ -6626,6 +7510,7 @@ var cutCirclesDistribRule = {
|
|
|
6626
7510
|
// src/stamps/geometry-2d/ai/rules/givenNamedCircle.ts
|
|
6627
7511
|
var BARE_PAREN2 = /\(\s*([A-Z])\s*\)/gu;
|
|
6628
7512
|
var HAS_POINTS_ON = /(?:trên|thuộc)\s+(?:nửa\s+)?(?:đường\s*tròn|cung|\(\s*[A-Z]\s*\))/u;
|
|
7513
|
+
var HAS_TANGENT_OR_SECANT = /tiếp\s*tuyến|cát\s*tuyến/u;
|
|
6629
7514
|
var givenNamedCircleRule = {
|
|
6630
7515
|
id: "givenNamedCircle",
|
|
6631
7516
|
priority: 74,
|
|
@@ -6633,7 +7518,7 @@ var givenNamedCircleRule = {
|
|
|
6633
7518
|
languages: ["vi"],
|
|
6634
7519
|
patterns: [/\(\s*[A-Z]\s*\)/u],
|
|
6635
7520
|
match(ctx) {
|
|
6636
|
-
if (!HAS_POINTS_ON.test(ctx.problem)) return [];
|
|
7521
|
+
if (!HAS_POINTS_ON.test(ctx.problem) && !HAS_TANGENT_OR_SECANT.test(ctx.problem)) return [];
|
|
6637
7522
|
const out = [];
|
|
6638
7523
|
const seen = /* @__PURE__ */ new Set();
|
|
6639
7524
|
BARE_PAREN2.lastIndex = 0;
|
|
@@ -6641,12 +7526,14 @@ var givenNamedCircleRule = {
|
|
|
6641
7526
|
const center = m[1];
|
|
6642
7527
|
if (seen.has(center)) continue;
|
|
6643
7528
|
const c = center;
|
|
6644
|
-
if (new RegExp(`\\(\\s*${c}\\s*[;,]`, "u").test(ctx.problem)) continue;
|
|
6645
|
-
if (new RegExp(`\\(\\s*${c}\\s*\\)[^.]{0,30}?\u0111\u01B0\u1EDDng\\s*k\xEDnh|\u0111\u01B0\u1EDDng\\s*k\xEDnh[^.]{0,30}?\\(\\s*${c}\\s*\\)`, "u").test(ctx.problem)) continue;
|
|
6646
|
-
if (new RegExp(`(?:n\u1ED9i|ngo\u1EA1i)\\s*ti\u1EBFp[^.]{0,30}?\\(\\s*${c}\\s*\\)|\\(\\s*${c}\\s*\\)[^.]{0,30}?(?:n\u1ED9i|ngo\u1EA1i)\\s*ti\u1EBFp`, "u").test(ctx.problem)) continue;
|
|
6647
|
-
|
|
7529
|
+
if (new RegExp(`\\(\\s*${escapeRe(c)}\\s*[;,]`, "u").test(ctx.problem)) continue;
|
|
7530
|
+
if (new RegExp(`\\(\\s*${escapeRe(c)}\\s*\\)[^.]{0,30}?\u0111\u01B0\u1EDDng\\s*k\xEDnh|\u0111\u01B0\u1EDDng\\s*k\xEDnh[^.]{0,30}?\\(\\s*${escapeRe(c)}\\s*\\)`, "u").test(ctx.problem)) continue;
|
|
7531
|
+
if (new RegExp(`(?:n\u1ED9i|ngo\u1EA1i)\\s*ti\u1EBFp[^.]{0,30}?\\(\\s*${escapeRe(c)}\\s*\\)|\\(\\s*${escapeRe(c)}\\s*\\)[^.]{0,30}?(?:n\u1ED9i|ngo\u1EA1i)\\s*ti\u1EBFp`, "u").test(ctx.problem)) continue;
|
|
7532
|
+
const isNamedCircle = new RegExp(`${CIRCLE_KW}\\s*\\(\\s*${escapeRe(c)}\\s*\\)`, "u").test(ctx.problem);
|
|
7533
|
+
const isOpenedBare = new RegExp(`^[Cc]ho\\s+\\(\\s*${escapeRe(c)}\\s*\\)`, "u").test(ctx.problem.trimStart());
|
|
7534
|
+
if (!isNamedCircle && !isOpenedBare) continue;
|
|
6648
7535
|
seen.add(center);
|
|
6649
|
-
const owner = ctx.clauses.find((cl) => new RegExp(`\\(\\s*${c}\\s*\\)`, "u").test(cl.text));
|
|
7536
|
+
const owner = ctx.clauses.find((cl) => new RegExp(`\\(\\s*${escapeRe(c)}\\s*\\)`, "u").test(cl.text));
|
|
6650
7537
|
out.push({
|
|
6651
7538
|
ruleId: "givenNamedCircle",
|
|
6652
7539
|
clauseIds: owner ? [owner.id] : [],
|
|
@@ -6658,12 +7545,16 @@ var givenNamedCircleRule = {
|
|
|
6658
7545
|
};
|
|
6659
7546
|
|
|
6660
7547
|
// src/stamps/geometry-2d/ai/rules/twoPerpLinesMeet.ts
|
|
6661
|
-
var
|
|
7548
|
+
var PREFILTER52 = /(?:[Đđ]ường\s*thẳng\s+qua|(?:Qua|qua|Từ|từ)\s+[A-Z][^.]{0,20}?(?:vẽ|kẻ|dựng)\s+đường\s*thẳng)\s*[^.]{0,30}?(?:vuông\s*góc|⊥)[^.]{0,40}?cắt\s+(?:[Đđ]ường\s*thẳng\s+qua|nhau)/u;
|
|
6662
7549
|
var RE25 = new RegExp(
|
|
6663
7550
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*th\u1EB3ng\\s+qua\\s+([A-Z])(?!\\p{L})\\s+(?:vu\xF4ng\\s*g\xF3c|\u22A5)\\s+(?:v\u1EDBi\\s+)?([A-Z])([A-Z])(?![A-Z])[^.]{0,20}?c\u1EAFt\\s+[\u0110\u0111]\u01B0\u1EDDng\\s*th\u1EB3ng\\s+qua\\s+([A-Z])(?!\\p{L})\\s+(?:vu\xF4ng\\s*g\xF3c|\u22A5)\\s+(?:v\u1EDBi\\s+)?([A-Z])([A-Z])(?![A-Z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
6664
7551
|
"gu"
|
|
6665
7552
|
);
|
|
6666
|
-
var
|
|
7553
|
+
var RE_DRAW_FIRST = new RegExp(
|
|
7554
|
+
"(?:Qua|qua|T\u1EEB|t\u1EEB)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})\\s*[^.]{0,16}?(?:v\u1EBD|k\u1EBB|d\u1EF1ng)\\s+\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+(?:vu\xF4ng\\s*g\xF3c|\u22A5)\\s+(?:v\u1EDBi\\s+)?([A-Z])([A-Z])(?![A-Z])\\s*[^.]{0,16}?c\u1EAFt\\s+[\u0110\u0111]\u01B0\u1EDDng\\s*th\u1EB3ng\\s+qua\\s+([A-Z])(?!\\p{L})\\s+(?:vu\xF4ng\\s*g\xF3c|\u22A5)\\s+(?:v\u1EDBi\\s+)?([A-Z])([A-Z])(?![A-Z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
7555
|
+
"gu"
|
|
7556
|
+
);
|
|
7557
|
+
var RE_DISTRIB2 = new RegExp(
|
|
6667
7558
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*th\u1EB3ng\\s+qua\\s+([A-Z])(?!\\p{L})\\s*,\\s*([A-Z])(?!\\p{L})\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s*th\u1EE9\\s*t\u1EF1\\s+)?(?:vu\xF4ng\\s*g\xF3c|\u22A5)\\s+(?:v\u1EDBi\\s+)?([A-Z])([A-Z])(?![A-Z])\\s*,\\s*([A-Z])([A-Z])(?![A-Z])[^.]{0,20}?c\u1EAFt\\s+nhau\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
6668
7559
|
"gu"
|
|
6669
7560
|
);
|
|
@@ -6672,7 +7563,7 @@ var twoPerpLinesMeetRule = {
|
|
|
6672
7563
|
priority: 49,
|
|
6673
7564
|
// sau parallelPerp (dựng đường), trước/đủ để S tham chiếu 2 đường
|
|
6674
7565
|
languages: ["vi"],
|
|
6675
|
-
patterns: [
|
|
7566
|
+
patterns: [PREFILTER52],
|
|
6676
7567
|
match(ctx) {
|
|
6677
7568
|
const out = [];
|
|
6678
7569
|
const emit = (c, p1, l1, p2, l2, s) => {
|
|
@@ -6694,8 +7585,12 @@ var twoPerpLinesMeetRule = {
|
|
|
6694
7585
|
for (const m of c.text.matchAll(RE25)) {
|
|
6695
7586
|
emit(c, m[1], m[2] + m[3], m[4], m[5] + m[6], m[7]);
|
|
6696
7587
|
}
|
|
6697
|
-
|
|
6698
|
-
for (const m of c.text.matchAll(
|
|
7588
|
+
RE_DRAW_FIRST.lastIndex = 0;
|
|
7589
|
+
for (const m of c.text.matchAll(RE_DRAW_FIRST)) {
|
|
7590
|
+
emit(c, m[1], m[2] + m[3], m[4], m[5] + m[6], m[7]);
|
|
7591
|
+
}
|
|
7592
|
+
RE_DISTRIB2.lastIndex = 0;
|
|
7593
|
+
for (const m of c.text.matchAll(RE_DISTRIB2)) {
|
|
6699
7594
|
emit(c, m[1], m[3] + m[4], m[2], m[5] + m[6], m[7]);
|
|
6700
7595
|
}
|
|
6701
7596
|
}
|
|
@@ -6704,27 +7599,57 @@ var twoPerpLinesMeetRule = {
|
|
|
6704
7599
|
};
|
|
6705
7600
|
|
|
6706
7601
|
// src/stamps/geometry-2d/ai/rules/circumcircleCutsLine.ts
|
|
6707
|
-
var
|
|
7602
|
+
var PREFILTER53 = /[Đđ]ường\s*tròn\s+ngoại\s*tiếp\s+(?:tam\s*giác\s+)?[A-Z]{3}[^.]{0,20}?cắt\s+[A-Z]{2}|[Đđ]ường\s*tròn\s+\([A-Z]{3}\)[^.]{0,20}?cắt\s+[A-Z]{2}|\([A-Z]{3}\)\s*∩\s*[A-Z]{2}\s*=\s*\{/u;
|
|
6708
7603
|
var RE26 = new RegExp(
|
|
6709
7604
|
"[\u0110\u0111]\u01B0\u1EDDng\\s*tr\xF2n\\s+ngo\u1EA1i\\s*ti\u1EBFp\\s+(?:tam\\s*gi\xE1c\\s+)?([A-Z]{3})(?![A-Z])\\s+c\u1EAFt\\s+(?:l\u1EA1i\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|\u0111o\u1EA1n\\s+|tia\\s+|c\u1EA1nh\\s+)?([A-Z]{2})(?![A-Z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+(?:th\u1EE9\\s+hai\\s+)?)?([A-Z])(?![A-Z])(?:\\s+kh\xE1c\\s+([A-Z])(?![A-Z]))?",
|
|
6710
7605
|
"gu"
|
|
6711
7606
|
);
|
|
7607
|
+
var RE_PAREN = new RegExp(
|
|
7608
|
+
"[\u0110\u0111]\u01B0\u1EDDng\\s*tr\xF2n\\s+\\(([A-Z]{3})\\)\\s+c\u1EAFt\\s+(?:l\u1EA1i\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|\u0111o\u1EA1n\\s+|tia\\s+|c\u1EA1nh\\s+)?([A-Z]{2})(?![A-Z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+(?:th\u1EE9\\s+(?:hai|2)\\s+)?)?(?:l\xE0\\s+)?([A-Z])(?![A-Z])(?:\\s+kh\xE1c\\s+([A-Z])(?![A-Z]))?",
|
|
7609
|
+
"gu"
|
|
7610
|
+
);
|
|
7611
|
+
var RE_SET = new RegExp(
|
|
7612
|
+
"\\(([A-Z]{3})\\)\\s*\u2229\\s*(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|\u0111o\u1EA1n\\s+|tia\\s+|c\u1EA1nh\\s+)?([A-Z]{2})(?![A-Z])\\s*=\\s*\\{\\s*([A-Z])(?![A-Z])\\s*,\\s*([A-Z])(?![A-Z])\\s*\\}",
|
|
7613
|
+
"gu"
|
|
7614
|
+
);
|
|
6712
7615
|
var circumcircleCutsLineRule = {
|
|
6713
7616
|
id: "circumcircleCutsLine",
|
|
6714
7617
|
priority: 58,
|
|
6715
7618
|
// sau circleTriangle/cevian; trước intersection(45)
|
|
6716
7619
|
languages: ["vi"],
|
|
6717
|
-
patterns: [
|
|
7620
|
+
patterns: [PREFILTER53],
|
|
6718
7621
|
match(ctx) {
|
|
6719
7622
|
const out = [];
|
|
6720
7623
|
for (const c of ctx.clauses) {
|
|
6721
|
-
RE26
|
|
6722
|
-
|
|
7624
|
+
for (const re of [RE26, RE_PAREN]) {
|
|
7625
|
+
re.lastIndex = 0;
|
|
7626
|
+
for (const m of c.text.matchAll(re)) {
|
|
7627
|
+
const tri = m[1].split("");
|
|
7628
|
+
const line = m[2];
|
|
7629
|
+
const k = m[3];
|
|
7630
|
+
const shared = m[4] ?? line.split("").find((v) => tri.includes(v));
|
|
7631
|
+
if (!shared || tri.includes(k) || line.includes(k)) continue;
|
|
7632
|
+
const w = `w${m[1]}`;
|
|
7633
|
+
out.push({
|
|
7634
|
+
ruleId: "circumcircleCutsLine",
|
|
7635
|
+
clauseIds: [c.id],
|
|
7636
|
+
intents: [
|
|
7637
|
+
drawCircle(w, "through3", { points: tri }),
|
|
7638
|
+
addPoint(k, { kind: "secondIntersection", line, circle: w, other: shared })
|
|
7639
|
+
]
|
|
7640
|
+
});
|
|
7641
|
+
}
|
|
7642
|
+
}
|
|
7643
|
+
RE_SET.lastIndex = 0;
|
|
7644
|
+
for (const m of c.text.matchAll(RE_SET)) {
|
|
6723
7645
|
const tri = m[1].split("");
|
|
6724
7646
|
const line = m[2];
|
|
6725
|
-
const
|
|
6726
|
-
const
|
|
6727
|
-
if (
|
|
7647
|
+
const set = [m[3], m[4]];
|
|
7648
|
+
const onLine = set.filter((v) => line.includes(v));
|
|
7649
|
+
if (onLine.length !== 1) continue;
|
|
7650
|
+
const shared = onLine[0];
|
|
7651
|
+
const k = set.find((v) => v !== shared);
|
|
7652
|
+
if (tri.includes(k) || line.includes(k)) continue;
|
|
6728
7653
|
const w = `w${m[1]}`;
|
|
6729
7654
|
out.push({
|
|
6730
7655
|
ruleId: "circumcircleCutsLine",
|
|
@@ -6754,7 +7679,7 @@ var LINE_TYPES = [
|
|
|
6754
7679
|
var CONCURRENCY = /(?:cắt\s+nhau|đồng\s*quy|gặp\s+nhau|cùng\s+đi\s+qua)\s+(?:tại\s+|qua\s+)?(?:điểm\s+)?([A-Z])(?!\p{L})/u;
|
|
6755
7680
|
var VERTEX_PAIR = /(?<!\p{L})[A-Z][A-Z](?:['′])?(?!\p{L})/gu;
|
|
6756
7681
|
var TRI_G9 = /tam\s*giác\s+(?:(?:nhọn|cân|đều|vuông|tù)\s+)*([A-Z])([A-Z])([A-Z])(?![A-Z])/gu;
|
|
6757
|
-
var
|
|
7682
|
+
var PREFILTER54 = LINE_TYPES.map((t) => t.kw);
|
|
6758
7683
|
function trianglesIn4(text) {
|
|
6759
7684
|
const out = [];
|
|
6760
7685
|
TRI_G9.lastIndex = 0;
|
|
@@ -6787,7 +7712,7 @@ var lineConcurrencyRule = {
|
|
|
6787
7712
|
id: "lineConcurrency",
|
|
6788
7713
|
priority: 69,
|
|
6789
7714
|
languages: ["vi"],
|
|
6790
|
-
patterns:
|
|
7715
|
+
patterns: PREFILTER54,
|
|
6791
7716
|
match(ctx) {
|
|
6792
7717
|
const fallbackTri = uniqueTriangle5(ctx.problem);
|
|
6793
7718
|
const out = [];
|
|
@@ -6819,7 +7744,7 @@ var lineConcurrencyRule = {
|
|
|
6819
7744
|
};
|
|
6820
7745
|
|
|
6821
7746
|
// src/stamps/geometry-2d/ai/rules/twoCirclesMeet.ts
|
|
6822
|
-
var
|
|
7747
|
+
var PREFILTER55 = /hai\s+đường\s*tròn[^.]{0,70}?cắt\s+nhau/u;
|
|
6823
7748
|
var CIRCLE2 = "\\(\\s*([A-Z]['\u2032]?)(?:\\s*[;,]\\s*(?:[Rr]['\u2032]?|\\d+(?:[.,]\\d+)?\\s*[a-z]*))?\\s*\\)";
|
|
6824
7749
|
var TWO_MEET2 = new RegExp(
|
|
6825
7750
|
"hai\\s+\u0111\u01B0\u1EDDng\\s*tr\xF2n\\s*" + CIRCLE2 + "\\s*(?:[;,]\\s*)?v\xE0\\s*" + CIRCLE2 + "[^.]{0,50}?c\u1EAFt\\s+nhau\\s+(?:\u1EDF|t\u1EA1i)\\s+(?:hai\\s+\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Za-z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Za-z])",
|
|
@@ -6832,7 +7757,7 @@ var twoCirclesMeetRule = {
|
|
|
6832
7757
|
// phái sinh downstream → dựng sớm. DƯỚI circleRadius (75) cho nhất quán.
|
|
6833
7758
|
priority: 74,
|
|
6834
7759
|
languages: ["vi"],
|
|
6835
|
-
patterns: [
|
|
7760
|
+
patterns: [PREFILTER55],
|
|
6836
7761
|
match(ctx) {
|
|
6837
7762
|
const m = TWO_MEET2.exec(ctx.problem);
|
|
6838
7763
|
if (!m) return [];
|
|
@@ -6859,7 +7784,7 @@ var twoCirclesMeetRule = {
|
|
|
6859
7784
|
};
|
|
6860
7785
|
|
|
6861
7786
|
// src/stamps/geometry-2d/ai/rules/radius.ts
|
|
6862
|
-
var
|
|
7787
|
+
var PREFILTER56 = /bán\s*kính\s+[A-Z]{2}/u;
|
|
6863
7788
|
var DRAW_RADII = new RegExp(
|
|
6864
7789
|
"(?:[Vv]\u1EBD|[Kk]\u1EBB|[Dd]\u1EF1ng)\\s+(?:hai\\s+|ba\\s+|2\\s+|3\\s+|c\xE1c\\s+)?b\xE1n\\s*k\xEDnh\\s+([A-Z]{2}(?:\\s*(?:,|v\xE0)\\s*[A-Z]{2})*)(?![A-Z])",
|
|
6865
7790
|
"u"
|
|
@@ -6870,7 +7795,7 @@ var radiusRule = {
|
|
|
6870
7795
|
id: "radius",
|
|
6871
7796
|
priority: 60,
|
|
6872
7797
|
languages: ["vi"],
|
|
6873
|
-
patterns: [
|
|
7798
|
+
patterns: [PREFILTER56],
|
|
6874
7799
|
match(ctx) {
|
|
6875
7800
|
const out = [];
|
|
6876
7801
|
for (const c of ctx.clauses) {
|
|
@@ -6894,17 +7819,17 @@ var radiusRule = {
|
|
|
6894
7819
|
};
|
|
6895
7820
|
|
|
6896
7821
|
// src/stamps/geometry-2d/ai/rules/perpNamedLine.ts
|
|
6897
|
-
var
|
|
7822
|
+
var PREFILTER57 = /(?:[Kk]ẻ|[Vv]ẽ|[Dd]ựng)\s+(?:đường\s*thẳng\s+|tia\s+)?[A-Z][a-z]\s*(?:⊥|vuông\s*góc)/u;
|
|
6898
7823
|
var RE27 = new RegExp(
|
|
6899
7824
|
// `⊥\\s*` (KHÔNG `\\s+`): OCR hay dính "Ax⊥MN" (vao10:11). Dạng chữ giữ `\\s+`.
|
|
6900
|
-
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng)\\s+(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+)?([A-Z][a-z])(?![A-Za-z])\\s*(?:\u22A5\\s*|vu\xF4ng\\s*g\xF3c(?:\\s+v\u1EDBi)?\\s+)(?:v\u1EDBi\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?([A-Z]{1,2})(?![A-Z])(?:\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Za-z]))?",
|
|
7825
|
+
"(?:[Kk]\u1EBB|[Vv]\u1EBD|[Dd]\u1EF1ng)\\s+(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|tia\\s+)?([A-Z][a-z])(?![A-Za-z])\\s*(?:\u22A5\\s*|vu\xF4ng\\s*g\xF3c(?:\\s+v\u1EDBi)?\\s+)(?:v\u1EDBi\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n\\s+)?([A-Z]{1,2})(?![A-Z])(?:\\s*,?\\s*c\u1EAFt\\s+[A-Z]{1,2})?(?:\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Za-z]))?",
|
|
6901
7826
|
"gu"
|
|
6902
7827
|
);
|
|
6903
7828
|
var perpNamedLineRule = {
|
|
6904
7829
|
id: "perp-named-line",
|
|
6905
7830
|
priority: 60,
|
|
6906
7831
|
languages: ["vi"],
|
|
6907
|
-
patterns: [
|
|
7832
|
+
patterns: [PREFILTER57],
|
|
6908
7833
|
match(ctx) {
|
|
6909
7834
|
const out = [];
|
|
6910
7835
|
for (const c of ctx.clauses) {
|
|
@@ -6930,7 +7855,7 @@ var perpNamedLineRule = {
|
|
|
6930
7855
|
};
|
|
6931
7856
|
|
|
6932
7857
|
// src/stamps/geometry-2d/ai/rules/parallelSidePoints.ts
|
|
6933
|
-
var
|
|
7858
|
+
var PREFILTER58 = /thuộc\s+[A-Z]{2}\s+và\s+[A-Z]{2}\s+sao\s+cho[^.]*?(?:∥|\/\/|song\s*song)/u;
|
|
6934
7859
|
var PAR2 = "(?:\u2225|\\/\\/|song\\s*song(?:\\s+v\u1EDBi)?)";
|
|
6935
7860
|
var RE28 = new RegExp(
|
|
6936
7861
|
"([A-Z])\\s*,\\s*([A-Z])(?![A-Z])\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?thu\u1ED9c\\s+([A-Z]{2})\\s+v\xE0\\s+([A-Z]{2})(?![A-Z])\\s+sao\\s+cho\\s+([A-Z])([A-Z])(?![A-Z])\\s*" + PAR2 + "\\s*([A-Z]{2})(?![A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])([A-Z])(?![A-Z])\\s*" + PAR2 + "\\s*([A-Z]{2})(?![A-Z])",
|
|
@@ -6940,7 +7865,7 @@ var parallelSidePointsRule = {
|
|
|
6940
7865
|
id: "parallel-side-points",
|
|
6941
7866
|
priority: 55,
|
|
6942
7867
|
languages: ["vi"],
|
|
6943
|
-
patterns: [
|
|
7868
|
+
patterns: [PREFILTER58],
|
|
6944
7869
|
match(ctx) {
|
|
6945
7870
|
const out = [];
|
|
6946
7871
|
for (const c of ctx.clauses) {
|
|
@@ -6963,36 +7888,370 @@ var parallelSidePointsRule = {
|
|
|
6963
7888
|
}
|
|
6964
7889
|
};
|
|
6965
7890
|
|
|
6966
|
-
// src/stamps/geometry-2d/ai/
|
|
6967
|
-
var
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
7891
|
+
// src/stamps/geometry-2d/ai/intent-builders/shared.ts
|
|
7892
|
+
var SQRT3 = 1.7320508075688772;
|
|
7893
|
+
function triangleCanonical(variant) {
|
|
7894
|
+
switch (variant) {
|
|
7895
|
+
case "equilateral":
|
|
7896
|
+
return [[0, 0], [4, 0], [2, 2 * SQRT3]];
|
|
7897
|
+
case "isoceles-AB":
|
|
7898
|
+
return [[0, 0], [4, 0], [2, 3]];
|
|
7899
|
+
case "isoceles-BC":
|
|
7900
|
+
return [[0, 3], [-2, 0], [2, 0]];
|
|
7901
|
+
case "isoceles-CA":
|
|
7902
|
+
return [[0, 0], [2, 3], [4, 0]];
|
|
7903
|
+
case "right-at-A":
|
|
7904
|
+
return [[0, 0], [4, 0], [0, 3]];
|
|
7905
|
+
case "right-at-B":
|
|
7906
|
+
return [[0, 0], [4, 0], [4, 3]];
|
|
7907
|
+
case "right-at-C":
|
|
7908
|
+
return [[0, 0], [6, 0], [3, 3]];
|
|
7909
|
+
case "any":
|
|
7910
|
+
default:
|
|
7911
|
+
return [[0, 0], [5, 0], [2, 3]];
|
|
7912
|
+
}
|
|
6982
7913
|
}
|
|
6983
|
-
function
|
|
6984
|
-
|
|
6985
|
-
if (!m) return void 0;
|
|
6986
|
-
return m[1] ?? m[2];
|
|
7914
|
+
function squareCanonical() {
|
|
7915
|
+
return [[0, 0], [4, 0], [4, 4], [0, 4]];
|
|
6987
7916
|
}
|
|
6988
|
-
function
|
|
6989
|
-
|
|
7917
|
+
function rectangleCanonical(variant) {
|
|
7918
|
+
if (variant === "tall") return [[0, 0], [2.5, 0], [2.5, 4], [0, 4]];
|
|
7919
|
+
return [[0, 0], [4, 0], [4, 2.5], [0, 2.5]];
|
|
6990
7920
|
}
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
7921
|
+
function rhombusCanonical() {
|
|
7922
|
+
return [[-2, 0], [0, -1.5], [2, 0], [0, 1.5]];
|
|
7923
|
+
}
|
|
7924
|
+
function trapezoidCanonical(variant) {
|
|
7925
|
+
switch (variant) {
|
|
7926
|
+
case "right":
|
|
7927
|
+
return [[0, 0], [5, 0], [3, 3], [0, 3]];
|
|
7928
|
+
case "isoceles":
|
|
7929
|
+
return [[0, 0], [5, 0], [4, 3], [1, 3]];
|
|
7930
|
+
case "general":
|
|
7931
|
+
default:
|
|
7932
|
+
return [[0, 0], [5, 0], [3.5, 3], [1, 3]];
|
|
7933
|
+
}
|
|
7934
|
+
}
|
|
7935
|
+
function parallelogramCanonical() {
|
|
7936
|
+
return [[0, 0], [4, 0], [5, 3], [1, 3]];
|
|
7937
|
+
}
|
|
7938
|
+
function quadrilateralCanonical() {
|
|
7939
|
+
return [[0, 0], [5, 0], [4.5, 3.5], [0.5, 3]];
|
|
7940
|
+
}
|
|
7941
|
+
function addPoint2(s, p) {
|
|
7942
|
+
if (s.pointNames.has(p.name)) {
|
|
7943
|
+
if (p.kind !== "free") {
|
|
7944
|
+
const idx = s.points.findIndex((q) => q.name === p.name);
|
|
7945
|
+
if (idx >= 0 && s.points[idx].kind === "free") s.points[idx] = p;
|
|
7946
|
+
}
|
|
7947
|
+
return;
|
|
7948
|
+
}
|
|
7949
|
+
s.points.push(p);
|
|
7950
|
+
s.pointNames.add(p.name);
|
|
7951
|
+
}
|
|
7952
|
+
var FREE_DEFAULT_SPREAD = [
|
|
7953
|
+
[3, 3],
|
|
7954
|
+
[-3, 3],
|
|
7955
|
+
[3, -3],
|
|
7956
|
+
[-3, -3],
|
|
7957
|
+
[4, 0],
|
|
7958
|
+
[-4, 0],
|
|
7959
|
+
[0, 4],
|
|
7960
|
+
[0, -4]
|
|
7961
|
+
];
|
|
7962
|
+
function defaultFreeCoord(s) {
|
|
7963
|
+
let count = 0;
|
|
7964
|
+
for (const p of s.points) if (p.kind === "free") count++;
|
|
7965
|
+
return [...FREE_DEFAULT_SPREAD[count % FREE_DEFAULT_SPREAD.length]];
|
|
7966
|
+
}
|
|
7967
|
+
function addShape(s, sh) {
|
|
7968
|
+
if (s.shapeNames.has(sh.name)) return;
|
|
7969
|
+
s.shapes.push(sh);
|
|
7970
|
+
s.shapeNames.add(sh.name);
|
|
7971
|
+
}
|
|
7972
|
+
function uniqueShapeName(s, base) {
|
|
7973
|
+
if (!s.shapeNames.has(base)) return base;
|
|
7974
|
+
let i = 2;
|
|
7975
|
+
while (s.shapeNames.has(`${base}${i}`)) i++;
|
|
7976
|
+
return `${base}${i}`;
|
|
7977
|
+
}
|
|
7978
|
+
function uniquePointName(s, base) {
|
|
7979
|
+
if (!s.pointNames.has(base)) return base;
|
|
7980
|
+
let i = 2;
|
|
7981
|
+
while (s.pointNames.has(`${base}${i}`)) i++;
|
|
7982
|
+
return `${base}${i}`;
|
|
7983
|
+
}
|
|
7984
|
+
function ensureSegment(s, a, b) {
|
|
7985
|
+
const k1 = `${a}-${b}`;
|
|
7986
|
+
const k2 = `${b}-${a}`;
|
|
7987
|
+
const existing = s.segmentByEnds.get(k1) ?? s.segmentByEnds.get(k2);
|
|
7988
|
+
if (existing) return existing;
|
|
7989
|
+
const name = uniqueShapeName(s, `${a}${b}`);
|
|
7990
|
+
addShape(s, { name, kind: "segment", p1: a, p2: b });
|
|
7991
|
+
s.segmentByEnds.set(k1, name);
|
|
7992
|
+
return name;
|
|
7993
|
+
}
|
|
7994
|
+
function resolveLineRefWithFallback(s, ref, anchor) {
|
|
7995
|
+
if (/^[A-Za-z]$/.test(ref) && /^[A-Za-z]$/.test(anchor) && ref !== anchor && s.pointNames.has(ref) && s.pointNames.has(anchor)) {
|
|
7996
|
+
const [a, b] = ref < anchor ? [ref, anchor] : [anchor, ref];
|
|
7997
|
+
return ensureSegment(s, a, b);
|
|
7998
|
+
}
|
|
7999
|
+
return resolveSegmentRef(s, ref);
|
|
8000
|
+
}
|
|
8001
|
+
function resolveSegmentRef(s, ref) {
|
|
8002
|
+
if (s.shapeNames.has(ref)) return ref;
|
|
8003
|
+
if (/^[A-Z][A-Z]$/u.test(ref)) {
|
|
8004
|
+
return ensureSegment(s, ref[0], ref[1]);
|
|
8005
|
+
}
|
|
8006
|
+
if (ref.length === 2 && s.pointNames.has(ref[0]) && s.pointNames.has(ref[1])) {
|
|
8007
|
+
return ensureSegment(s, ref[0], ref[1]);
|
|
8008
|
+
}
|
|
8009
|
+
const dashMatch = ref.match(/^([A-Za-z][A-Za-z0-9'_]*)[-_]?([A-Za-z][A-Za-z0-9'_]*)$/);
|
|
8010
|
+
if (dashMatch && s.pointNames.has(dashMatch[1]) && s.pointNames.has(dashMatch[2])) {
|
|
8011
|
+
return ensureSegment(s, dashMatch[1], dashMatch[2]);
|
|
8012
|
+
}
|
|
8013
|
+
const sub2 = splitKnownPair(s, ref);
|
|
8014
|
+
if (sub2) return ensureSegment(s, sub2[0], sub2[1]);
|
|
8015
|
+
return ref;
|
|
8016
|
+
}
|
|
8017
|
+
function splitKnownPair(s, ref) {
|
|
8018
|
+
for (let i = ref.length - 1; i >= 1; i--) {
|
|
8019
|
+
const a = ref.slice(0, i);
|
|
8020
|
+
const b = ref.slice(i);
|
|
8021
|
+
if (s.pointNames.has(a) && s.pointNames.has(b)) return [a, b];
|
|
8022
|
+
}
|
|
8023
|
+
return null;
|
|
8024
|
+
}
|
|
8025
|
+
function parseEnds(ref) {
|
|
8026
|
+
if (ref.length === 2) return [ref[0], ref[1]];
|
|
8027
|
+
const m = ref.match(/^([A-Za-z][A-Za-z0-9'_]*)[-_]?([A-Za-z][A-Za-z0-9'_]*)$/);
|
|
8028
|
+
if (m) return [m[1], m[2]];
|
|
8029
|
+
return null;
|
|
8030
|
+
}
|
|
8031
|
+
var SHAPE_VARIANTS = {
|
|
8032
|
+
triangle: ["any", "equilateral", "isoceles-AB", "isoceles-BC", "isoceles-CA", "right-at-A", "right-at-B", "right-at-C"],
|
|
8033
|
+
square: ["standard"],
|
|
8034
|
+
rectangle: ["standard", "wide", "tall"],
|
|
8035
|
+
rhombus: ["standard"],
|
|
8036
|
+
trapezoid: ["right", "isoceles", "general"],
|
|
8037
|
+
parallelogram: ["standard"],
|
|
8038
|
+
quadrilateral: ["any"]
|
|
8039
|
+
};
|
|
8040
|
+
|
|
8041
|
+
// src/stamps/geometry-2d/ai/rules/inscribedSquare.ts
|
|
8042
|
+
function sub(p, q) {
|
|
8043
|
+
return [p[0] - q[0], p[1] - q[1]];
|
|
8044
|
+
}
|
|
8045
|
+
function add(p, q) {
|
|
8046
|
+
return [p[0] + q[0], p[1] + q[1]];
|
|
8047
|
+
}
|
|
8048
|
+
function mul(p, k) {
|
|
8049
|
+
return [p[0] * k, p[1] * k];
|
|
8050
|
+
}
|
|
8051
|
+
function dot(p, q) {
|
|
8052
|
+
return p[0] * q[0] + p[1] * q[1];
|
|
8053
|
+
}
|
|
8054
|
+
function cross(p, q) {
|
|
8055
|
+
return p[0] * q[1] - p[1] * q[0];
|
|
8056
|
+
}
|
|
8057
|
+
function len(p) {
|
|
8058
|
+
return Math.hypot(p[0], p[1]);
|
|
8059
|
+
}
|
|
8060
|
+
function norm2(p) {
|
|
8061
|
+
const l = len(p) || 1;
|
|
8062
|
+
return [p[0] / l, p[1] / l];
|
|
8063
|
+
}
|
|
8064
|
+
function inscribedSquareCoords(A, B, C) {
|
|
8065
|
+
const u = norm2(sub(C, B));
|
|
8066
|
+
const a = len(sub(C, B));
|
|
8067
|
+
const tA = dot(sub(A, B), u);
|
|
8068
|
+
const foot = add(B, mul(u, tA));
|
|
8069
|
+
const h = len(sub(A, foot));
|
|
8070
|
+
const s = a * h / (a + h);
|
|
8071
|
+
const n = norm2(sub(A, foot));
|
|
8072
|
+
const BA = sub(B, A);
|
|
8073
|
+
const q0 = -(s * cross(n, BA)) / cross(u, BA);
|
|
8074
|
+
const Q = add(B, mul(u, q0));
|
|
8075
|
+
const P = add(B, mul(u, q0 + s));
|
|
8076
|
+
const M = add(Q, mul(n, s));
|
|
8077
|
+
const N = add(P, mul(n, s));
|
|
8078
|
+
return { M, N, P, Q };
|
|
8079
|
+
}
|
|
8080
|
+
function paramOn(pt, X, Y) {
|
|
8081
|
+
const d = sub(Y, X);
|
|
8082
|
+
return dot(sub(pt, X), d) / (dot(d, d) || 1);
|
|
8083
|
+
}
|
|
8084
|
+
function inscribedSquareParams(A, B, C) {
|
|
8085
|
+
const { M, N, P, Q } = inscribedSquareCoords(A, B, C);
|
|
8086
|
+
return {
|
|
8087
|
+
tM: paramOn(M, A, B),
|
|
8088
|
+
tN: paramOn(N, A, C),
|
|
8089
|
+
tP: paramOn(P, B, C),
|
|
8090
|
+
tQ: paramOn(Q, B, C)
|
|
8091
|
+
};
|
|
8092
|
+
}
|
|
8093
|
+
var PREFILTER59 = /hình\s*vuông[^.]*?(?:trên|∈|thuộc|\bNe\b|\bEe\b)/iu;
|
|
8094
|
+
var SQUARE_LABELS = /hình\s*vuông\s+([A-Z])\s*([A-Z])\s*([A-Z])\s*([A-Z])(?![A-Z])/u;
|
|
8095
|
+
var RIGHT_AT2 = /vuông\s+tại\s+([A-Z])(?![A-Za-z])/u;
|
|
8096
|
+
var RIGHT_BY_IDX2 = ["right-at-A", "right-at-B", "right-at-C"];
|
|
8097
|
+
var MEMBERSHIP_G = /(?:([A-Z])\s*(?:∈|trên|thuộc)|\b([A-Z])e\b)\s*([A-Z])([A-Z])(?![A-Z])/gu;
|
|
8098
|
+
var LIST_MEMBERSHIP_G = /([A-Z])\s*,\s*([A-Z])\s*(?:∈|trên|thuộc)\s*([A-Z])([A-Z])(?![A-Z])/gu;
|
|
8099
|
+
function parseMemberships(text) {
|
|
8100
|
+
const out = [];
|
|
8101
|
+
let m;
|
|
8102
|
+
LIST_MEMBERSHIP_G.lastIndex = 0;
|
|
8103
|
+
const listSpans = [];
|
|
8104
|
+
while ((m = LIST_MEMBERSHIP_G.exec(text)) !== null) {
|
|
8105
|
+
const side = [m[3], m[4]];
|
|
8106
|
+
out.push({ pt: m[1], side });
|
|
8107
|
+
out.push({ pt: m[2], side });
|
|
8108
|
+
listSpans.push([m.index, m.index + m[0].length]);
|
|
8109
|
+
}
|
|
8110
|
+
MEMBERSHIP_G.lastIndex = 0;
|
|
8111
|
+
while ((m = MEMBERSHIP_G.exec(text)) !== null) {
|
|
8112
|
+
if (listSpans.some(([a, b]) => m.index >= a && m.index < b)) continue;
|
|
8113
|
+
const pt = m[1] ?? m[2];
|
|
8114
|
+
out.push({ pt, side: [m[3], m[4]] });
|
|
8115
|
+
}
|
|
8116
|
+
return out;
|
|
8117
|
+
}
|
|
8118
|
+
var inscribedSquareRule = {
|
|
8119
|
+
id: "inscribed-square",
|
|
8120
|
+
priority: 56,
|
|
8121
|
+
languages: ["vi"],
|
|
8122
|
+
patterns: [PREFILTER59],
|
|
8123
|
+
match(ctx) {
|
|
8124
|
+
const out = [];
|
|
8125
|
+
for (const c of ctx.clauses) {
|
|
8126
|
+
const labelM = SQUARE_LABELS.exec(c.text);
|
|
8127
|
+
if (!labelM) continue;
|
|
8128
|
+
const labels = [labelM[1], labelM[2], labelM[3], labelM[4]];
|
|
8129
|
+
if (new Set(labels).size !== 4) continue;
|
|
8130
|
+
const memberships = parseMemberships(c.text);
|
|
8131
|
+
if (memberships.length < 4) continue;
|
|
8132
|
+
const bySide = /* @__PURE__ */ new Map();
|
|
8133
|
+
const ptSide = /* @__PURE__ */ new Map();
|
|
8134
|
+
for (const mem of memberships) {
|
|
8135
|
+
if (!labels.includes(mem.pt)) continue;
|
|
8136
|
+
ptSide.set(mem.pt, mem.side);
|
|
8137
|
+
const key = [...mem.side].sort().join("");
|
|
8138
|
+
const arr = bySide.get(key) ?? [];
|
|
8139
|
+
if (!arr.includes(mem.pt)) arr.push(mem.pt);
|
|
8140
|
+
bySide.set(key, arr);
|
|
8141
|
+
}
|
|
8142
|
+
if (labels.some((l) => !ptSide.has(l))) continue;
|
|
8143
|
+
const sideKeys = [...bySide.keys()];
|
|
8144
|
+
const hypKey = sideKeys.find((k) => (bySide.get(k) ?? []).length === 2);
|
|
8145
|
+
if (!hypKey) continue;
|
|
8146
|
+
const legKeys = sideKeys.filter((k) => k !== hypKey);
|
|
8147
|
+
if (legKeys.length !== 2) continue;
|
|
8148
|
+
if ((bySide.get(legKeys[0]) ?? []).length !== 1) continue;
|
|
8149
|
+
if ((bySide.get(legKeys[1]) ?? []).length !== 1) continue;
|
|
8150
|
+
const triVerts = /* @__PURE__ */ new Set();
|
|
8151
|
+
for (const [, side] of ptSide) side.forEach((v) => triVerts.add(v));
|
|
8152
|
+
if (triVerts.size !== 3) continue;
|
|
8153
|
+
const legVerts = legKeys.map((k) => k.split(""));
|
|
8154
|
+
const apex = legVerts[0].find((v) => legVerts[1].includes(v));
|
|
8155
|
+
if (!apex) continue;
|
|
8156
|
+
const triLabels = [apex, ...[...triVerts].filter((v) => v !== apex).sort()];
|
|
8157
|
+
const explicitRight = collectRightAt(ctx.clauses);
|
|
8158
|
+
if (explicitRight && explicitRight !== apex) continue;
|
|
8159
|
+
const tri = triangleCanonical("right-at-A");
|
|
8160
|
+
const params = inscribedSquareParams(tri[0], tri[1], tri[2]);
|
|
8161
|
+
const placement = assignSquarePlacement(
|
|
8162
|
+
labels,
|
|
8163
|
+
ptSide,
|
|
8164
|
+
bySide.get(hypKey),
|
|
8165
|
+
triLabels[0],
|
|
8166
|
+
// apex (đỉnh góc vuông)
|
|
8167
|
+
triLabels[1],
|
|
8168
|
+
// đỉnh B của tam giác (đầu mút cạnh huyền gần Q)
|
|
8169
|
+
triLabels[2],
|
|
8170
|
+
// đỉnh C của tam giác (đầu mút cạnh huyền gần P)
|
|
8171
|
+
params
|
|
8172
|
+
);
|
|
8173
|
+
if (!placement) continue;
|
|
8174
|
+
const rightIdx = triLabels.indexOf(apex);
|
|
8175
|
+
const variant = RIGHT_BY_IDX2[rightIdx] ?? "right-at-A";
|
|
8176
|
+
const intents = [drawShape("triangle", triLabels, variant)];
|
|
8177
|
+
for (const lbl of labels) {
|
|
8178
|
+
const { of, t } = placement.get(lbl);
|
|
8179
|
+
intents.push(addPoint(lbl, { kind: "onSegment", of, t }));
|
|
8180
|
+
}
|
|
8181
|
+
intents.push(markShape("quadrilateral", labels));
|
|
8182
|
+
const claimed = /* @__PURE__ */ new Set([c.id]);
|
|
8183
|
+
for (const cc of ctx.clauses) {
|
|
8184
|
+
const rm = RIGHT_AT2.exec(cc.text);
|
|
8185
|
+
if (rm && rm[1] === apex) claimed.add(cc.id);
|
|
8186
|
+
}
|
|
8187
|
+
out.push({ ruleId: "inscribed-square", clauseIds: [...claimed], intents });
|
|
8188
|
+
}
|
|
8189
|
+
return out;
|
|
8190
|
+
}
|
|
8191
|
+
};
|
|
8192
|
+
function collectRightAt(clauses) {
|
|
8193
|
+
for (const c of clauses) {
|
|
8194
|
+
const m = RIGHT_AT2.exec(c.text);
|
|
8195
|
+
if (m) return m[1];
|
|
8196
|
+
}
|
|
8197
|
+
return void 0;
|
|
8198
|
+
}
|
|
8199
|
+
function assignSquarePlacement(labels, ptSide, hypPts, triA, triB, triC, params) {
|
|
8200
|
+
const place = /* @__PURE__ */ new Map();
|
|
8201
|
+
const onAB = labels.find((l) => !hypPts.includes(l) && ptSide.get(l).includes(triB));
|
|
8202
|
+
const onAC = labels.find((l) => !hypPts.includes(l) && ptSide.get(l).includes(triC));
|
|
8203
|
+
if (!onAB || !onAC) return null;
|
|
8204
|
+
place.set(onAB, { of: `${triA}${triB}`, t: params.tM });
|
|
8205
|
+
place.set(onAC, { of: `${triA}${triC}`, t: params.tN });
|
|
8206
|
+
const neighborInHyp = (anchor) => {
|
|
8207
|
+
const i = labels.indexOf(anchor);
|
|
8208
|
+
const next = labels[(i + 1) % labels.length];
|
|
8209
|
+
const prev = labels[(i - 1 + labels.length) % labels.length];
|
|
8210
|
+
if (hypPts.includes(next)) return next;
|
|
8211
|
+
if (hypPts.includes(prev)) return prev;
|
|
8212
|
+
return void 0;
|
|
8213
|
+
};
|
|
8214
|
+
const qVtx = neighborInHyp(onAB);
|
|
8215
|
+
const pVtx = neighborInHyp(onAC);
|
|
8216
|
+
const hyp = `${triB}${triC}`;
|
|
8217
|
+
if (qVtx) place.set(qVtx, { of: hyp, t: params.tQ });
|
|
8218
|
+
if (pVtx) place.set(pVtx, { of: hyp, t: params.tP });
|
|
8219
|
+
for (const hp of hypPts) {
|
|
8220
|
+
if (!place.has(hp)) place.set(hp, { of: hyp, t: hp === qVtx ? params.tQ : params.tP });
|
|
8221
|
+
}
|
|
8222
|
+
return labels.every((l) => place.has(l)) ? place : null;
|
|
8223
|
+
}
|
|
8224
|
+
|
|
8225
|
+
// src/stamps/geometry-2d/ai/rules/parallelThroughCutsCircle.ts
|
|
8226
|
+
var DISTRIB4 = new RegExp(
|
|
8227
|
+
String.raw`(?:[CcNn]ác\s+)?đường\s*thẳng\s+qua\s+(?:hai\s+điểm\s+)?([A-Z])(?!\p{L})\s+và\s+([A-Z])(?!\p{L})` + String.raw`\s+song\s*song\s+(?:với\s+)?(?:đường\s*thẳng\s+)?([A-Z])([A-Z])(?!\p{L})` + String.raw`[^.]{0,30}?cắt\s+(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)` + String.raw`[^.]{0,30}?lần\s*lượt\s+(?:ở|tại)\s+([A-Z])\s+và\s+([A-Z])(?!\p{L})`,
|
|
8228
|
+
"gu"
|
|
8229
|
+
);
|
|
8230
|
+
var DECL_NAMED = new RegExp(
|
|
8231
|
+
String.raw`(?:[Qq]ua|[Tt]ừ)\s+(?:điểm\s+)?([A-Z])(?!\p{L})` + String.raw`\s+(?:kẻ|vẽ|dựng)\s+đường\s*thẳng\s+([a-z])(?!\p{L})` + String.raw`\s+song\s*song\s+(?:với\s+)?(?:đường\s*thẳng\s+)?([A-Z])([A-Z])(?!\p{L})`,
|
|
8232
|
+
"u"
|
|
8233
|
+
);
|
|
8234
|
+
var NAMED_CUT_BOTH = new RegExp(
|
|
8235
|
+
String.raw`[Đđ]ường\s*thẳng\s+([a-z])(?!\p{L})` + String.raw`\s+cắt\s+(?:đường\s*thẳng\s+|cạnh\s+|đoạn\s+)?([A-Z])([A-Z])(?!\p{L})\s+(?:ở|tại)\s+(?:điểm\s+)?([A-Z])(?!\p{L})` + String.raw`[^.]{0,8}?cắt\s+(?:đường\s*tròn\s*)?\(\s*([A-Z])\s*\)\s+(?:ở|tại)\s+(?:điểm\s+(?:thứ\s+hai\s+)?)?(?:là\s+)?([A-Z])(?!\p{L})`,
|
|
8236
|
+
"u"
|
|
8237
|
+
);
|
|
8238
|
+
var PREFILTER60 = /(?:[Qq]ua|đường\s*thẳng\s+qua)\s+[^.]{0,60}?song\s*song[^.]{0,80}?cắt|(?:[Qq]ua|[Tt]ừ)\s+(?:điểm\s+)?[A-Z][^.]{0,20}?(?:kẻ|vẽ|dựng)\s+đường\s*thẳng\s+[a-z]\s+song\s*song/u;
|
|
8239
|
+
function lineNameOf2(through) {
|
|
8240
|
+
return "par" + through;
|
|
8241
|
+
}
|
|
8242
|
+
function resolveCircle4(problem) {
|
|
8243
|
+
const m = /đường\s*tròn\s*\(\s*([A-Z])\s*\)|\(\s*([A-Z])\s*\)/u.exec(problem);
|
|
8244
|
+
if (!m) return void 0;
|
|
8245
|
+
return m[1] ?? m[2];
|
|
8246
|
+
}
|
|
8247
|
+
function secondIntersection2(name, line, circle, other) {
|
|
8248
|
+
return addPoint(name, { kind: "secondIntersection", line, circle, other });
|
|
8249
|
+
}
|
|
8250
|
+
var parallelThroughCutsCircleRule = {
|
|
8251
|
+
id: "parallelThroughCutsCircle",
|
|
8252
|
+
priority: 46,
|
|
8253
|
+
languages: ["vi"],
|
|
8254
|
+
patterns: [PREFILTER60],
|
|
6996
8255
|
match(ctx) {
|
|
6997
8256
|
const circle = resolveCircle4(ctx.problem);
|
|
6998
8257
|
if (!circle) return [];
|
|
@@ -7068,12 +8327,12 @@ var RE29 = new RegExp(
|
|
|
7068
8327
|
"(?:[Mm]\u1ED9t\\s+)?\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+(?:b\u1EA5t\\s*k[i\xECy\u1EF3]|t\xF9y\\s*\xFD)\\s+(?:n\xE0o\\s+)?(?:\u0111i\\s+)?qua\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?!\\p{L})[^.]{0,20}?c\u1EAFt\\s+(?:c\xE1c\\s+)?(?:\u0111\u01B0\u1EDDng\\s*th\u1EB3ng\\s+|c\u1EA1nh\\s+|\u0111o\u1EA1n(?:\\s+th\u1EB3ng)?\\s+)?([A-Z]\\s*[A-Z])(?![A-Z])\\s*(?:,|v\xE0)\\s*([A-Z]\\s*[A-Z])(?![A-Z])[^.]{0,20}?(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])\\s*(?:,|v\xE0)\\s*([A-Z])(?![A-Z])",
|
|
7069
8328
|
"gu"
|
|
7070
8329
|
);
|
|
7071
|
-
var
|
|
8330
|
+
var PREFILTER61 = [/đường\s*thẳng\s+(?:bất\s*k[iìyỳ]|tùy\s*ý)\s+(?:nào\s+)?(?:đi\s+)?qua\s+[A-Z]/u];
|
|
7072
8331
|
var lineThroughCutsTwoLinesRule = {
|
|
7073
8332
|
id: "lineThroughCutsTwoLines",
|
|
7074
8333
|
priority: 44,
|
|
7075
8334
|
languages: ["vi"],
|
|
7076
|
-
patterns:
|
|
8335
|
+
patterns: PREFILTER61,
|
|
7077
8336
|
match(ctx) {
|
|
7078
8337
|
const out = [];
|
|
7079
8338
|
for (const c of ctx.clauses) {
|
|
@@ -7151,54 +8410,651 @@ function detectSpec(problem) {
|
|
|
7151
8410
|
a = x1;
|
|
7152
8411
|
b = y1;
|
|
7153
8412
|
}
|
|
7154
|
-
if (vertex && a && b && a !== b) {
|
|
7155
|
-
const onLine = findOnLine(problem, vertex);
|
|
7156
|
-
if (onLine) return { vertex, a, b, onLine };
|
|
8413
|
+
if (vertex && a && b && a !== b) {
|
|
8414
|
+
const onLine = findOnLine(problem, vertex);
|
|
8415
|
+
if (onLine) return { vertex, a, b, onLine };
|
|
8416
|
+
}
|
|
8417
|
+
}
|
|
8418
|
+
return null;
|
|
8419
|
+
}
|
|
8420
|
+
var rightAngleViewingRule = {
|
|
8421
|
+
id: "rightAngleViewing",
|
|
8422
|
+
priority: 63,
|
|
8423
|
+
languages: ["vi"],
|
|
8424
|
+
patterns: [
|
|
8425
|
+
/(?:[Gg]óc|∠)[^.;\n]{0,18}90/u,
|
|
8426
|
+
/(?:[Gg]óc|∠)\s*[A-Z]{3}\s*vuông/u,
|
|
8427
|
+
/[A-Z]{2}\s*(?:⊥|vuông\s*góc)\s*[A-Z]{2}/u
|
|
8428
|
+
],
|
|
8429
|
+
match(ctx) {
|
|
8430
|
+
const spec = detectSpec(ctx.problem);
|
|
8431
|
+
if (!spec) return [];
|
|
8432
|
+
const clauseIds = ctx.clauses.filter(
|
|
8433
|
+
(c) => ANGLE_EQ_RE.test(c.text) || ANGLE_VUONG_RE.test(c.text) || PERP_RE.test(c.text) || findOnLine(c.text, spec.vertex) !== null
|
|
8434
|
+
).map((c) => c.id);
|
|
8435
|
+
const out = [
|
|
8436
|
+
{
|
|
8437
|
+
ruleId: "rightAngleViewing",
|
|
8438
|
+
clauseIds,
|
|
8439
|
+
intents: [
|
|
8440
|
+
addPoint(spec.vertex, {
|
|
8441
|
+
kind: "rightAngleViewing",
|
|
8442
|
+
a: spec.a,
|
|
8443
|
+
b: spec.b,
|
|
8444
|
+
onLine: spec.onLine
|
|
8445
|
+
})
|
|
8446
|
+
]
|
|
8447
|
+
}
|
|
8448
|
+
];
|
|
8449
|
+
return out;
|
|
8450
|
+
}
|
|
8451
|
+
};
|
|
8452
|
+
|
|
8453
|
+
// src/stamps/geometry-2d/ai/rules/collinearPoints.ts
|
|
8454
|
+
var PREFILTER62 = /thẳng\s+hàng|(?:cùng\s+)?(?:thuộc|nằm\s+trên|ở\s+trên)\s+(?:một\s+)?(?:cùng\s+)?(?:một\s+)?[Đđ]ư[ờơ]ng\s*thẳng/u;
|
|
8455
|
+
var NAMES = "([A-Z](?:['\u2032])?(?:\\s*(?:,|v\xE0)\\s*[A-Z](?:['\u2032])?){2,})";
|
|
8456
|
+
var ON_LINE = new RegExp(
|
|
8457
|
+
"(?:[Cc]\xE1c\\s+|[Cc]ho\\s+)?(?:\\d+\\s+|(?:hai|ba|b\u1ED1n|n\u0103m|s\xE1u)\\s+)?\u0111i\u1EC3m\\s+" + NAMES + "\\s+(?:c\xF9ng\\s+)?(?:thu\u1ED9c|n\u1EB1m\\s+tr\xEAn|\u1EDF\\s+tr\xEAn)\\s+(?:m\u1ED9t\\s+)?[\u0110\u0111]\u01B0[\u1EDD\u01A1]ng\\s*th\u1EB3ng",
|
|
8458
|
+
"u"
|
|
8459
|
+
);
|
|
8460
|
+
var COLLINEAR = new RegExp(
|
|
8461
|
+
"(?:[Cc]\xE1c\\s+|[Cc]ho\\s+)?(?:\\d+\\s+|(?:hai|ba|b\u1ED1n|n\u0103m|s\xE1u)\\s+)?\u0111i\u1EC3m\\s+" + NAMES + "\\s+th\u1EB3ng\\s+h\xE0ng",
|
|
8462
|
+
"u"
|
|
8463
|
+
);
|
|
8464
|
+
function parseNames(blob) {
|
|
8465
|
+
return blob.split(/\s*(?:,|và)\s*/u).map((s) => s.trim().replace("\u2032", "'")).filter((s) => /^[A-Z]'?$/u.test(s));
|
|
8466
|
+
}
|
|
8467
|
+
var collinearPointsRule = {
|
|
8468
|
+
id: "collinear-points",
|
|
8469
|
+
priority: 95,
|
|
8470
|
+
// cao: dựng "bộ khung" điểm thẳng hàng sớm (như triangle/quad).
|
|
8471
|
+
languages: ["vi"],
|
|
8472
|
+
patterns: [PREFILTER62],
|
|
8473
|
+
match(ctx) {
|
|
8474
|
+
const out = [];
|
|
8475
|
+
for (const c of ctx.clauses) {
|
|
8476
|
+
const m = ON_LINE.exec(c.text) ?? COLLINEAR.exec(c.text);
|
|
8477
|
+
if (!m) continue;
|
|
8478
|
+
const names = parseNames(m[1]);
|
|
8479
|
+
if (names.length < 3) continue;
|
|
8480
|
+
if (new Set(names).size !== names.length) continue;
|
|
8481
|
+
const first = names[0];
|
|
8482
|
+
const last = names[names.length - 1];
|
|
8483
|
+
const span = 12;
|
|
8484
|
+
const intents = [
|
|
8485
|
+
addPoint(first, { kind: "free", at: [-span / 2, 0] }),
|
|
8486
|
+
addPoint(last, { kind: "free", at: [span / 2, 0] }),
|
|
8487
|
+
// Điểm giữa: onSegment trên đoạn đầu-cuối, t cách đều theo thứ tự liệt kê.
|
|
8488
|
+
...names.slice(1, -1).map(
|
|
8489
|
+
(nm, i) => addPoint(nm, { kind: "onSegment", of: `${first}${last}`, t: (i + 1) / (names.length - 1) })
|
|
8490
|
+
),
|
|
8491
|
+
connect(first, last, "segment")
|
|
8492
|
+
];
|
|
8493
|
+
out.push({ ruleId: "collinear-points", clauseIds: [c.id], intents });
|
|
8494
|
+
}
|
|
8495
|
+
return out;
|
|
8496
|
+
}
|
|
8497
|
+
};
|
|
8498
|
+
|
|
8499
|
+
// src/stamps/geometry-2d/ai/rules/quadDiagonals.ts
|
|
8500
|
+
var PREFILTER63 = /[A-Z]{4}\s+là\s+(?:một\s+)?tứ\s*giác|tứ\s*giác\s+[A-Z]{4}[^.]{0,60}?đường\s*chéo/u;
|
|
8501
|
+
var QUAD_DECL = /([A-Z])([A-Z])([A-Z])([A-Z])(?![A-Z])\s+là\s+(?:một\s+)?tứ\s*giác/u;
|
|
8502
|
+
var DIAGONALS = new RegExp(
|
|
8503
|
+
"(?:hai\\s+|c\xE1c\\s+)?\u0111\u01B0\u1EDDng\\s*ch\xE9o\\s+([A-Z]{2})\\s+(?:v\xE0|,)\\s+([A-Z]{2})[^.]{0,40}?c\u1EAFt\\s+nhau\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
8504
|
+
"u"
|
|
8505
|
+
);
|
|
8506
|
+
var quadDiagonalsRule = {
|
|
8507
|
+
id: "quad-diagonals",
|
|
8508
|
+
priority: 99,
|
|
8509
|
+
// ~quad (100): vẽ tứ giác sớm; intersection (45) dựng E sau.
|
|
8510
|
+
languages: ["vi"],
|
|
8511
|
+
patterns: [PREFILTER63],
|
|
8512
|
+
match(ctx) {
|
|
8513
|
+
const out = [];
|
|
8514
|
+
for (const c of ctx.clauses) {
|
|
8515
|
+
const q = QUAD_DECL.exec(c.text);
|
|
8516
|
+
if (!q) continue;
|
|
8517
|
+
const labels = [q[1], q[2], q[3], q[4]];
|
|
8518
|
+
if (new Set(labels).size !== 4) continue;
|
|
8519
|
+
const intents = [drawShape("quadrilateral", labels, "any")];
|
|
8520
|
+
const d = DIAGONALS.exec(c.text);
|
|
8521
|
+
if (d) {
|
|
8522
|
+
const diag1 = d[1];
|
|
8523
|
+
const diag2 = d[2];
|
|
8524
|
+
const e = d[3];
|
|
8525
|
+
const ends = [diag1[0], diag1[1], diag2[0], diag2[1]];
|
|
8526
|
+
const okDiag = new Set(ends).size === 4 && ends.every((x) => labels.includes(x)) && !labels.includes(e);
|
|
8527
|
+
if (okDiag) {
|
|
8528
|
+
intents.push(connect(diag1[0], diag1[1], "segment"));
|
|
8529
|
+
intents.push(connect(diag2[0], diag2[1], "segment"));
|
|
8530
|
+
intents.push(addPoint(e, { kind: "intersection", of: [diag1, diag2] }));
|
|
8531
|
+
}
|
|
8532
|
+
}
|
|
8533
|
+
out.push({ ruleId: "quad-diagonals", clauseIds: [c.id], intents });
|
|
8534
|
+
}
|
|
8535
|
+
return out;
|
|
8536
|
+
}
|
|
8537
|
+
};
|
|
8538
|
+
|
|
8539
|
+
// src/stamps/geometry-2d/ai/rules/twoCirclesTangent.ts
|
|
8540
|
+
var PREFILTER64 = /hai\s+[Đđ]ư[ờơ]ng\s*tròn[^.]{0,70}?tiếp\s*xúc/u;
|
|
8541
|
+
var CIRCLE3 = "\\(\\s*([A-Z]['\u2032]?)(?:\\s*[;,]\\s*(?:[Rr]['\u2032]?|\\d+(?:[.,]\\d+)?\\s*[a-z]*))?\\s*\\)";
|
|
8542
|
+
var TWO_TANGENT = new RegExp(
|
|
8543
|
+
"hai\\s+[\u0110\u0111]\u01B0[\u1EDD\u01A1]ng\\s*tr\xF2n\\s*" + CIRCLE3 + "\\s*(?:[;,]\\s*)?v\xE0\\s*" + CIRCLE3 + "\\s*ti\u1EBFp\\s*x\xFAc\\s+(ngo\xE0i|trong|nhau)?\\s*(?:nhau\\s+)?(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Za-z])",
|
|
8544
|
+
"u"
|
|
8545
|
+
);
|
|
8546
|
+
var norm3 = (s) => s.replace(/′/g, "'");
|
|
8547
|
+
var twoCirclesTangentRule = {
|
|
8548
|
+
id: "two-circles-tangent",
|
|
8549
|
+
// TRÊN chord/externalPoint, cạnh twoCirclesMeet (74): tiếp điểm A là gốc nhiều
|
|
8550
|
+
// phái sinh → dựng sớm.
|
|
8551
|
+
priority: 74,
|
|
8552
|
+
languages: ["vi"],
|
|
8553
|
+
patterns: [PREFILTER64],
|
|
8554
|
+
match(ctx) {
|
|
8555
|
+
const m = TWO_TANGENT.exec(ctx.problem);
|
|
8556
|
+
if (!m) return [];
|
|
8557
|
+
const c1 = norm3(m[1]);
|
|
8558
|
+
const c2 = norm3(m[2]);
|
|
8559
|
+
const mode = m[3];
|
|
8560
|
+
const a = m[4];
|
|
8561
|
+
if (mode === "trong") return [];
|
|
8562
|
+
if (c1 === c2) return [];
|
|
8563
|
+
if (a === c1 || a === c2) return [];
|
|
8564
|
+
const r2 = 2;
|
|
8565
|
+
const declId = ctx.clauses.find((c) => /tiếp\s*xúc/u.test(c.text))?.id;
|
|
8566
|
+
return [
|
|
8567
|
+
{
|
|
8568
|
+
ruleId: "two-circles-tangent",
|
|
8569
|
+
clauseIds: declId === void 0 ? [] : [declId],
|
|
8570
|
+
intents: [
|
|
8571
|
+
// A = gốc; O bên trái cách r1, O' bên phải cách r2 → tiếp xúc ngoài tại A.
|
|
8572
|
+
addPoint(a, { kind: "free", at: [0, 0] }),
|
|
8573
|
+
addPoint(c1, { kind: "free", at: [-3, 0] }),
|
|
8574
|
+
addPoint(c2, { kind: "free", at: [r2, 0] }),
|
|
8575
|
+
drawCircle(`${c1}_t`, "centerThrough", { center: c1, through: a }),
|
|
8576
|
+
drawCircle(`${c2}_t`, "centerThrough", { center: c2, through: a })
|
|
8577
|
+
]
|
|
8578
|
+
}
|
|
8579
|
+
];
|
|
8580
|
+
}
|
|
8581
|
+
};
|
|
8582
|
+
|
|
8583
|
+
// src/stamps/geometry-2d/ai/rules/commonTangent.ts
|
|
8584
|
+
var PREFILTER65 = /[Tt]iếp\s*tuyến\s+chung/u;
|
|
8585
|
+
var TANGENT_GLUE = /[Tt]iếp\s*tuyến\s+chung\s+(?:(?:ngoài|trong)\s+)?([A-Z])([A-Z])(?![A-Za-z])/u;
|
|
8586
|
+
var TANGENT_NAMED_BEFORE2 = /([A-Z])([A-Z])(?![A-Za-z])\s+là\s+tiếp\s*tuyến\s+chung/u;
|
|
8587
|
+
var INTERNAL = /tiếp\s*tuyến\s+chung\s+trong/u;
|
|
8588
|
+
var CIRCLE_G = /\(\s*([A-Z]['′]?)(?:\s*[;,]\s*(?:[Rr]['′]?|\d+(?:[.,]\d+)?\s*[a-z]*))?\s*\)/gu;
|
|
8589
|
+
var CIRC2 = "\\(\\s*([A-Z]['\u2032]?)(?:\\s*[;,]\\s*(?:[Rr]['\u2032]?|\\d+(?:[.,]\\d+)?\\s*[a-z]*))?\\s*\\)";
|
|
8590
|
+
var TANGENT_TOUCH = new RegExp(
|
|
8591
|
+
"ti\u1EBFp\\s*x\xFAc\\s+(?:v\u1EDBi\\s+)?" + CIRC2 + "\\s*(?:,|v\xE0)\\s*" + CIRC2 + "\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+|t\u01B0\u01A1ng\\s+\u1EE9ng\\s+|theo\\s+th\u1EE9\\s+t\u1EF1\\s+)?(?:t\u1EA1i|\u1EDF)\\s+(?:c\xE1c\\s+)?(?:\u0111i\u1EC3m\\s+)?([A-Z]['\u2032]?)(?![A-Za-z])\\s*(?:,|v\xE0)\\s*([A-Z]['\u2032]?)(?![A-Za-z])",
|
|
8592
|
+
"u"
|
|
8593
|
+
);
|
|
8594
|
+
var norm4 = (s) => s.replace(/′/g, "'");
|
|
8595
|
+
function membership(problem, pt) {
|
|
8596
|
+
const re = new RegExp(
|
|
8597
|
+
pt + "\\s*(?:\u2208|thu\u1ED9c|n\u1EB1m\\s+tr\xEAn)\\s*(?:[\u0110\u0111]\u01B0[\u1EDD\u01A1]ng\\s*tr\xF2n\\s*)?(?:\\(\\s*)?(?:t\xE2m\\s+)?([A-Z]['\u2032]?)",
|
|
8598
|
+
"u"
|
|
8599
|
+
);
|
|
8600
|
+
const m = re.exec(problem);
|
|
8601
|
+
return m ? norm4(m[1]) : void 0;
|
|
8602
|
+
}
|
|
8603
|
+
var commonTangentRule = {
|
|
8604
|
+
id: "common-tangent",
|
|
8605
|
+
// Cạnh twoCirclesTangent (74): 2 đtròn + tiếp điểm là gốc nhiều phái sinh.
|
|
8606
|
+
priority: 73,
|
|
8607
|
+
languages: ["vi"],
|
|
8608
|
+
patterns: [PREFILTER65],
|
|
8609
|
+
match(ctx) {
|
|
8610
|
+
const p = ctx.problem;
|
|
8611
|
+
let t1;
|
|
8612
|
+
let t2;
|
|
8613
|
+
let c1;
|
|
8614
|
+
let c2;
|
|
8615
|
+
const mt = TANGENT_TOUCH.exec(p);
|
|
8616
|
+
if (mt) {
|
|
8617
|
+
c1 = norm4(mt[1]);
|
|
8618
|
+
c2 = norm4(mt[2]);
|
|
8619
|
+
t1 = norm4(mt[3]);
|
|
8620
|
+
t2 = norm4(mt[4]);
|
|
8621
|
+
} else {
|
|
8622
|
+
const mg = TANGENT_GLUE.exec(p);
|
|
8623
|
+
if (mg) {
|
|
8624
|
+
t1 = mg[1];
|
|
8625
|
+
t2 = mg[2];
|
|
8626
|
+
} else {
|
|
8627
|
+
const mb = TANGENT_NAMED_BEFORE2.exec(p);
|
|
8628
|
+
if (mb) {
|
|
8629
|
+
t1 = mb[1];
|
|
8630
|
+
t2 = mb[2];
|
|
8631
|
+
}
|
|
8632
|
+
}
|
|
8633
|
+
}
|
|
8634
|
+
if (!t1 || !t2 || t1 === t2) return [];
|
|
8635
|
+
const m1 = c1 ? void 0 : membership(p, t1);
|
|
8636
|
+
const m2 = c2 ? void 0 : membership(p, t2);
|
|
8637
|
+
if (c1 && c2) ; else if (m1 && m2 && m1 !== m2) {
|
|
8638
|
+
c1 = m1;
|
|
8639
|
+
c2 = m2;
|
|
8640
|
+
} else {
|
|
8641
|
+
const seen = [];
|
|
8642
|
+
CIRCLE_G.lastIndex = 0;
|
|
8643
|
+
let cm;
|
|
8644
|
+
while ((cm = CIRCLE_G.exec(p)) !== null) {
|
|
8645
|
+
const nm = norm4(cm[1]);
|
|
8646
|
+
if (!seen.includes(nm)) seen.push(nm);
|
|
8647
|
+
if (seen.length === 2) break;
|
|
8648
|
+
}
|
|
8649
|
+
if (seen.length === 2) {
|
|
8650
|
+
[c1, c2] = seen;
|
|
8651
|
+
}
|
|
8652
|
+
}
|
|
8653
|
+
if (!c1 || !c2 || c1 === c2) return [];
|
|
8654
|
+
if (t1 === c1 || t1 === c2 || t2 === c1 || t2 === c2) return [];
|
|
8655
|
+
const variant = INTERNAL.test(p) ? "internal" : "external";
|
|
8656
|
+
const r1 = 3;
|
|
8657
|
+
const r2 = 2;
|
|
8658
|
+
const declId = ctx.clauses.find((c) => /tiếp\s*tuyến\s+chung/u.test(c.text))?.id;
|
|
8659
|
+
const intents = [
|
|
8660
|
+
addPoint(c1, { kind: "free", at: [0, 0] }),
|
|
8661
|
+
addPoint(c2, { kind: "free", at: [12, 0] }),
|
|
8662
|
+
drawCircle(c1, "centerRadius", { center: c1, radius: r1 }),
|
|
8663
|
+
drawCircle(c2, "centerRadius", { center: c2, radius: r2 }),
|
|
8664
|
+
addPoint(t1, { kind: "commonTangentPoint", circles: [c1, c2], on: 0, variant, side: 0 }),
|
|
8665
|
+
addPoint(t2, { kind: "commonTangentPoint", circles: [c1, c2], on: 1, variant, side: 0 }),
|
|
8666
|
+
connect(t1, t2, "segment")
|
|
8667
|
+
];
|
|
8668
|
+
return [
|
|
8669
|
+
{
|
|
8670
|
+
ruleId: "common-tangent",
|
|
8671
|
+
clauseIds: declId === void 0 ? [] : [declId],
|
|
8672
|
+
intents
|
|
8673
|
+
}
|
|
8674
|
+
];
|
|
8675
|
+
}
|
|
8676
|
+
};
|
|
8677
|
+
|
|
8678
|
+
// src/stamps/geometry-2d/ai/rules/twoCirclesCenterRadiusMeet.ts
|
|
8679
|
+
var CENTER_RADIUS = new RegExp(
|
|
8680
|
+
"([A-Z])(?![A-Za-z])\\s+l\xE0m\\s+t\xE2m\\s*,?\\s*(?:v\u1EBD|d\u1EF1ng|k\u1EBB)\\s+" + CIRCLE_KW + "\\s+b\xE1n\\s*k\xEDnh\\s+([A-Z])([A-Z])(?![A-Za-z])",
|
|
8681
|
+
"gu"
|
|
8682
|
+
);
|
|
8683
|
+
var MEET = new RegExp(
|
|
8684
|
+
"[Hh]ai\\s+" + CIRCLE_KW + "\\s*(?:n\xE0y\\s+)?c\u1EAFt\\s+nhau\\s+(?:(?:\u1EDF|t\u1EA1i)\\s+)?(?:\u0111i\u1EC3m\\s+th\u1EE9\\s+hai\\s+)?(?:l\xE0|t\u1EA1i)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Za-z])",
|
|
8685
|
+
"u"
|
|
8686
|
+
);
|
|
8687
|
+
var PREFILTER66 = /làm\s+tâm[^.]{0,40}?bán\s*kính/u;
|
|
8688
|
+
var twoCirclesCenterRadiusMeetRule = {
|
|
8689
|
+
id: "two-circles-center-radius-meet",
|
|
8690
|
+
// Cạnh twoCirclesMeet (74): 2 đtròn + giao điểm là gốc phái sinh.
|
|
8691
|
+
priority: 73,
|
|
8692
|
+
languages: ["vi"],
|
|
8693
|
+
patterns: [PREFILTER66],
|
|
8694
|
+
match(ctx) {
|
|
8695
|
+
const p = ctx.problem;
|
|
8696
|
+
CENTER_RADIUS.lastIndex = 0;
|
|
8697
|
+
const circles = [];
|
|
8698
|
+
let cm;
|
|
8699
|
+
while ((cm = CENTER_RADIUS.exec(p)) !== null) {
|
|
8700
|
+
const center = cm[1];
|
|
8701
|
+
const a = cm[2];
|
|
8702
|
+
const b = cm[3];
|
|
8703
|
+
if (a !== center || b === center) continue;
|
|
8704
|
+
circles.push({ center, through: b });
|
|
8705
|
+
}
|
|
8706
|
+
if (circles.length !== 2) return [];
|
|
8707
|
+
const [k1, k2] = circles;
|
|
8708
|
+
if (k1.center === k2.center) return [];
|
|
8709
|
+
if (k1.through !== k2.through) return [];
|
|
8710
|
+
const mm = MEET.exec(p);
|
|
8711
|
+
if (!mm) return [];
|
|
8712
|
+
const d = mm[1];
|
|
8713
|
+
if (d === k1.center || d === k2.center || d === k1.through) return [];
|
|
8714
|
+
const clauseIds = ctx.clauses.filter((c) => /làm\s+tâm/u.test(c.text) || /cắt\s+nhau/u.test(c.text)).map((c) => c.id);
|
|
8715
|
+
const intents = [
|
|
8716
|
+
drawCircle(k1.center, "centerThrough", { center: k1.center, through: k1.through }),
|
|
8717
|
+
drawCircle(k2.center, "centerThrough", { center: k2.center, through: k2.through }),
|
|
8718
|
+
addPoint(d, { kind: "circleIntersection", c1: k1.center, c2: k2.center, which: 1 })
|
|
8719
|
+
];
|
|
8720
|
+
return [
|
|
8721
|
+
{
|
|
8722
|
+
ruleId: "two-circles-center-radius-meet",
|
|
8723
|
+
clauseIds,
|
|
8724
|
+
intents
|
|
8725
|
+
}
|
|
8726
|
+
];
|
|
8727
|
+
}
|
|
8728
|
+
};
|
|
8729
|
+
|
|
8730
|
+
// src/stamps/geometry-2d/ai/rules/rightAngleVertexCircles.ts
|
|
8731
|
+
var PREFILTER67 = /góc\s+vuông\s+[a-z][A-Z][a-z]/u;
|
|
8732
|
+
var RIGHT_ANGLE = /góc\s+vuông\s+([a-z])([A-Z])([a-z])(?![\p{L}])/u;
|
|
8733
|
+
var TWO_ON_RAYS = new RegExp(
|
|
8734
|
+
"L\u1EA5y\\s+(?:c\xE1c\\s+|hai\\s+)?\u0111i\u1EC3m\\s+([A-Z])\\s+v\xE0\\s+([A-Z])(?![A-Z])\\s+(?:l\u1EA7n\\s*l\u01B0\u1EE3t\\s+)?tr\xEAn\\s+(?:c\xE1c\\s+|hai\\s+)?tia\\s+[A-Z][a-z]\\s+v\xE0\\s+[A-Z][a-z]",
|
|
8735
|
+
"u"
|
|
8736
|
+
);
|
|
8737
|
+
var CIRCLE_CUTS_RAY = new RegExp(
|
|
8738
|
+
"[Vv]\u1EBD\\s+[\u0110\u0111]\u01B0[\u1EDD\u01A1]ng\\s*tr\xF2n\\s*\\(\\s*([A-Z])\\s*[;,]\\s*([A-Z])([A-Z])\\s*\\)\\s*c\u1EAFt\\s+tia\\s+([A-Z])([a-z])\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
8739
|
+
"gu"
|
|
8740
|
+
);
|
|
8741
|
+
var rightAngleVertexCirclesRule = {
|
|
8742
|
+
id: "right-angle-vertex-circles",
|
|
8743
|
+
priority: 76,
|
|
8744
|
+
// dựng bộ khung góc + điểm sớm (trên twoCirclesMeet/tangent 74).
|
|
8745
|
+
languages: ["vi"],
|
|
8746
|
+
patterns: [PREFILTER67],
|
|
8747
|
+
match(ctx) {
|
|
8748
|
+
const ra = RIGHT_ANGLE.exec(ctx.problem);
|
|
8749
|
+
if (!ra) return [];
|
|
8750
|
+
const o = ra[2];
|
|
8751
|
+
const xRay = ra[1];
|
|
8752
|
+
const yRay = ra[3];
|
|
8753
|
+
let pI = "";
|
|
8754
|
+
let pK = "";
|
|
8755
|
+
for (const c of ctx.clauses) {
|
|
8756
|
+
const t = TWO_ON_RAYS.exec(c.text);
|
|
8757
|
+
if (t) {
|
|
8758
|
+
pI = t[1];
|
|
8759
|
+
pK = t[2];
|
|
8760
|
+
break;
|
|
8761
|
+
}
|
|
8762
|
+
}
|
|
8763
|
+
if (!pI || !pK || pI === pK || pI === o || pK === o) return [];
|
|
8764
|
+
const xEnd = xRay.toUpperCase() === o ? `${o}x` : xRay.toUpperCase();
|
|
8765
|
+
const yEnd = yRay.toUpperCase() === o ? `${o}y` : yRay.toUpperCase();
|
|
8766
|
+
const used = /* @__PURE__ */ new Set([o, pI, pK]);
|
|
8767
|
+
const ax = used.has(xEnd) ? "X9" : xEnd;
|
|
8768
|
+
const ay = used.has(yEnd) ? "Y9" : yEnd;
|
|
8769
|
+
const intents = [
|
|
8770
|
+
addPoint(o, { kind: "free", at: [0, 0] }),
|
|
8771
|
+
addPoint(ax, { kind: "free", at: [8, 0] }),
|
|
8772
|
+
addPoint(ay, { kind: "free", at: [0, 8] }),
|
|
8773
|
+
addPoint(pI, { kind: "onSegment", of: `${o}${ax}`, t: 0.35 }),
|
|
8774
|
+
addPoint(pK, { kind: "onSegment", of: `${o}${ay}`, t: 0.35 }),
|
|
8775
|
+
connect(o, ax, "segment"),
|
|
8776
|
+
connect(o, ay, "segment")
|
|
8777
|
+
];
|
|
8778
|
+
for (const c of ctx.clauses) {
|
|
8779
|
+
CIRCLE_CUTS_RAY.lastIndex = 0;
|
|
8780
|
+
for (const m of c.text.matchAll(CIRCLE_CUTS_RAY)) {
|
|
8781
|
+
const center = m[1];
|
|
8782
|
+
const rayVertex = m[4];
|
|
8783
|
+
const rayDir = m[5];
|
|
8784
|
+
const hit = m[6];
|
|
8785
|
+
if (![pI, pK, o].includes(center)) continue;
|
|
8786
|
+
if (hit === center || hit === o) continue;
|
|
8787
|
+
const rayAxisEnd = rayDir.toUpperCase() === xRay.toUpperCase() ? ax : rayDir.toUpperCase() === yRay.toUpperCase() ? ay : "";
|
|
8788
|
+
if (!rayAxisEnd || rayVertex !== o) continue;
|
|
8789
|
+
const otherCenter = center === pI ? pK : pI;
|
|
8790
|
+
const circ = `${center}_c`;
|
|
8791
|
+
intents.push(drawCircle(circ, "centerThrough", { center, through: otherCenter }));
|
|
8792
|
+
intents.push(
|
|
8793
|
+
addPoint(hit, { kind: "secondIntersection", line: `${o}${rayAxisEnd}`, circle: circ, other: o })
|
|
8794
|
+
);
|
|
8795
|
+
}
|
|
8796
|
+
}
|
|
8797
|
+
return [{ ruleId: "right-angle-vertex-circles", clauseIds: ctx.clauses.map((c) => c.id), intents }];
|
|
8798
|
+
}
|
|
8799
|
+
};
|
|
8800
|
+
|
|
8801
|
+
// src/stamps/geometry-2d/ai/rules/namedLine.ts
|
|
8802
|
+
var LC = String.raw`([a-z]{1,2}[0-9]?)(?!\p{L})`;
|
|
8803
|
+
var REF2 = String.raw`(?:${DUONG_KW}\s*thẳng\s+|đoạn(?:\s+thẳng)?\s+|cạnh\s+|tia\s+)?([A-Z]\s*[A-Z])(?![A-Z])`;
|
|
8804
|
+
var PT3 = String.raw`([A-Z])(?![A-Za-z])`;
|
|
8805
|
+
var VERB = String.raw`(?:[Cc]ho|[Vv]ẽ|[Kk]ẻ|[Dd]ựng)\s+(?:một\s+)?${DUONG_KW}\s*thẳng\s+`;
|
|
8806
|
+
var DECL = String.raw`(?:(?:và|[Cc]ho|[Vv]ẽ|[Kk]ẻ|[Dd]ựng)\s+)?(?:một\s+)?${DUONG_KW}\s*thẳng\s+`;
|
|
8807
|
+
var PERP = new RegExp(
|
|
8808
|
+
DECL + LC + String.raw`\s*(?:⊥\s*|vuông\s*góc(?:\s+với)?\s+)(?:với\s+)?` + REF2 + String.raw`(?:\s*,?\s*cắt\s+[A-Z]{2})?\s+(?:tại|ở)\s+(?:điểm\s+)?` + PT3,
|
|
8809
|
+
"gu"
|
|
8810
|
+
);
|
|
8811
|
+
var PARA_AFTER = new RegExp(
|
|
8812
|
+
VERB + LC + String.raw`\s*(?://\s*|song\s*song(?:\s+với)?\s+)(?:với\s+)?` + REF2 + String.raw`\s+(?:đi\s+)?qua\s+(?:điểm\s+)?` + PT3,
|
|
8813
|
+
"gu"
|
|
8814
|
+
);
|
|
8815
|
+
var PARA_BEFORE = new RegExp(
|
|
8816
|
+
String.raw`(?:[Qq]ua|[Tt]ừ)\s+(?:điểm\s+)?` + PT3 + String.raw`[^.]{0,12}?` + VERB + LC + String.raw`\s*(?://\s*|song\s*song(?:\s+với)?\s+)(?:với\s+)?` + REF2,
|
|
8817
|
+
"gu"
|
|
8818
|
+
);
|
|
8819
|
+
var THRU_TWO = new RegExp(
|
|
8820
|
+
VERB + LC + String.raw`\s+(?:đi\s+)?qua\s+(?:hai\s+điểm\s+)?` + PT3 + String.raw`\s+và\s+(?:điểm\s+)?` + PT3,
|
|
8821
|
+
"gu"
|
|
8822
|
+
);
|
|
8823
|
+
var SECANT_LINE = new RegExp(
|
|
8824
|
+
DECL + LC + String.raw`[^.]{0,40}?cắt[^.]{0,30}?(?:tại|ở)\s+(?:2\s+|hai\s+)?(?:điểm\s+)?` + PT3 + String.raw`\s+và\s+(?:điểm\s+)?` + PT3,
|
|
8825
|
+
"gu"
|
|
8826
|
+
);
|
|
8827
|
+
var FREE_DECL = new RegExp(
|
|
8828
|
+
String.raw`(?:[Cc]ho|[Vv]ẽ|[Kk]ẻ|[Dd]ựng)\s+(?:một\s+)?${DUONG_KW}\s*thẳng\s+` + LC + // KHÔNG theo sau bởi ràng buộc: ⊥/vuông góc, //|song song, (đi)?qua, cắt,
|
|
8829
|
+
// tiếp xúc. Cho phép ranh giới câu / "và" / "," / "(".
|
|
8830
|
+
String.raw`(?!\s*(?:⊥|//|vuông\s*góc|song\s*song|(?:đi\s+)?qua|cắt|tiếp\s*xúc|tại|ở)\b)`,
|
|
8831
|
+
"gu"
|
|
8832
|
+
);
|
|
8833
|
+
function lineReferenced(problem, name) {
|
|
8834
|
+
const esc = escapeRe(name);
|
|
8835
|
+
const N = String.raw`(?<![\p{L}\d])${esc}(?![\p{L}\d])`;
|
|
8836
|
+
const REFS = [
|
|
8837
|
+
// trước "d": "⊥ d", "song song d", "thuộc/trên/nằm trên d", "cắt … d", "của d"
|
|
8838
|
+
new RegExp(String.raw`(?:⊥|vuông\s*góc(?:\s+với)?|//|song\s*song(?:\s+với)?|thuộc|trên|cắt|tiếp\s*tuyến\s+(?:của|với)?)\s+(?:${N})`, "u"),
|
|
8839
|
+
// sau "d": "d cắt", "d tại", "d lấy", "d, "
|
|
8840
|
+
new RegExp(N + String.raw`\s+(?:cắt|tại|ở|lấy)`, "u")
|
|
8841
|
+
];
|
|
8842
|
+
return REFS.some((re) => re.test(problem));
|
|
8843
|
+
}
|
|
8844
|
+
var PREFILTER68 = new RegExp(
|
|
8845
|
+
String.raw`${DUONG_KW}\s*thẳng\s+[a-z]{1,2}[0-9]?(?![\p{L}])`,
|
|
8846
|
+
"u"
|
|
8847
|
+
);
|
|
8848
|
+
var namedLineRule = {
|
|
8849
|
+
id: "named-line",
|
|
8850
|
+
// Cao (63) như tangentLineNamedAtPoint — đường <d> phải dựng TRƯỚC điểm-trên-d
|
|
8851
|
+
// / intersection tham chiếu d.
|
|
8852
|
+
priority: 63,
|
|
8853
|
+
languages: ["vi"],
|
|
8854
|
+
patterns: [PREFILTER68],
|
|
8855
|
+
match(ctx) {
|
|
8856
|
+
const out = [];
|
|
8857
|
+
const push = (clauseId, intents) => {
|
|
8858
|
+
out.push({ ruleId: "named-line", clauseIds: [clauseId], intents });
|
|
8859
|
+
};
|
|
8860
|
+
for (const c of ctx.clauses) {
|
|
8861
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8862
|
+
PERP.lastIndex = 0;
|
|
8863
|
+
for (const m of c.text.matchAll(PERP)) {
|
|
8864
|
+
const line = m[1];
|
|
8865
|
+
const to = m[2].replace(/\s+/g, "");
|
|
8866
|
+
const anchor = m[3];
|
|
8867
|
+
if (seen.has(line)) continue;
|
|
8868
|
+
seen.add(line);
|
|
8869
|
+
push(c.id, [drawLine(line, "perpThrough", { through: anchor, to })]);
|
|
8870
|
+
}
|
|
8871
|
+
PARA_AFTER.lastIndex = 0;
|
|
8872
|
+
for (const m of c.text.matchAll(PARA_AFTER)) {
|
|
8873
|
+
const line = m[1];
|
|
8874
|
+
const to = m[2].replace(/\s+/g, "");
|
|
8875
|
+
const anchor = m[3];
|
|
8876
|
+
if (seen.has(line) || to.includes(anchor)) continue;
|
|
8877
|
+
seen.add(line);
|
|
8878
|
+
push(c.id, [drawLine(line, "parallelThrough", { through: anchor, to })]);
|
|
8879
|
+
}
|
|
8880
|
+
PARA_BEFORE.lastIndex = 0;
|
|
8881
|
+
for (const m of c.text.matchAll(PARA_BEFORE)) {
|
|
8882
|
+
const anchor = m[1];
|
|
8883
|
+
const line = m[2];
|
|
8884
|
+
const to = m[3].replace(/\s+/g, "");
|
|
8885
|
+
if (seen.has(line) || to.includes(anchor)) continue;
|
|
8886
|
+
seen.add(line);
|
|
8887
|
+
push(c.id, [drawLine(line, "parallelThrough", { through: anchor, to })]);
|
|
8888
|
+
}
|
|
8889
|
+
THRU_TWO.lastIndex = 0;
|
|
8890
|
+
for (const m of c.text.matchAll(THRU_TWO)) {
|
|
8891
|
+
const line = m[1];
|
|
8892
|
+
const a = m[2];
|
|
8893
|
+
const b = m[3];
|
|
8894
|
+
if (seen.has(line) || a === b) continue;
|
|
8895
|
+
seen.add(line);
|
|
8896
|
+
push(c.id, [drawLine(line, "lineThrough", { points: [a, b] })]);
|
|
8897
|
+
}
|
|
8898
|
+
SECANT_LINE.lastIndex = 0;
|
|
8899
|
+
for (const m of c.text.matchAll(SECANT_LINE)) {
|
|
8900
|
+
const line = m[1];
|
|
8901
|
+
const a = m[2];
|
|
8902
|
+
const b = m[3];
|
|
8903
|
+
if (seen.has(line) || a === b) continue;
|
|
8904
|
+
seen.add(line);
|
|
8905
|
+
push(c.id, [drawLine(line, "lineThrough", { points: [a, b] })]);
|
|
8906
|
+
}
|
|
8907
|
+
FREE_DECL.lastIndex = 0;
|
|
8908
|
+
for (const m of c.text.matchAll(FREE_DECL)) {
|
|
8909
|
+
const line = m[1];
|
|
8910
|
+
if (seen.has(line) || !lineReferenced(ctx.problem, line)) continue;
|
|
8911
|
+
seen.add(line);
|
|
8912
|
+
const p1 = `${line}_p1`;
|
|
8913
|
+
const p2 = `${line}_p2`;
|
|
8914
|
+
push(c.id, [
|
|
8915
|
+
addPoint(p1, { kind: "free" }),
|
|
8916
|
+
addPoint(p2, { kind: "free" }),
|
|
8917
|
+
drawLine(line, "lineThrough", { points: [p1, p2] })
|
|
8918
|
+
]);
|
|
8919
|
+
}
|
|
8920
|
+
}
|
|
8921
|
+
return out;
|
|
8922
|
+
}
|
|
8923
|
+
};
|
|
8924
|
+
|
|
8925
|
+
// src/stamps/geometry-2d/ai/rules/bisectorsMeet.ts
|
|
8926
|
+
var ANG_HEAD = "(?:\u2220\\s*|g\xF3c\\s+)([A-Z])([A-Z])([A-Z])(?![A-Z])";
|
|
8927
|
+
var ANG_TAIL = "(?:\u2220\\s*|g\xF3c\\s+)?([A-Z])([A-Z])([A-Z])(?![A-Z])";
|
|
8928
|
+
var RE30 = new RegExp(
|
|
8929
|
+
"[Pp]h\xE2n\\s*gi\xE1c\\s+" + ANG_HEAD + "\\s+v\xE0\\s+" + ANG_TAIL + "\\s+(?:c\u1EAFt|giao)\\s+nhau\\s+(?:t\u1EA1i|\u1EDF)\\s+(?:\u0111i\u1EC3m\\s+)?([A-Z])(?![A-Z])",
|
|
8930
|
+
"gu"
|
|
8931
|
+
);
|
|
8932
|
+
var PREFILTER69 = /[Pp]hân\s*giác[^.]{0,80}?(?:cắt|giao)\s+nhau\s+(?:tại|ở)/u;
|
|
8933
|
+
var bisectorsMeetRule = {
|
|
8934
|
+
id: "bisectorsMeet",
|
|
8935
|
+
// Trên intersection (45) để các tia phân giác (draw-line) + điểm giao gom cùng
|
|
8936
|
+
// 1 match; intent-layer order-retry topo lo thứ tự build. 49 = ngang
|
|
8937
|
+
// twoPerpLinesMeet (cùng họ "2 đường đặt-tên giao nhau").
|
|
8938
|
+
priority: 49,
|
|
8939
|
+
languages: ["vi"],
|
|
8940
|
+
patterns: [PREFILTER69],
|
|
8941
|
+
match(ctx) {
|
|
8942
|
+
const out = [];
|
|
8943
|
+
for (const c of ctx.clauses) {
|
|
8944
|
+
RE30.lastIndex = 0;
|
|
8945
|
+
for (const m of c.text.matchAll(RE30)) {
|
|
8946
|
+
const a1 = [m[1], m[2], m[3]];
|
|
8947
|
+
const a2 = [m[4], m[5], m[6]];
|
|
8948
|
+
const p = m[7];
|
|
8949
|
+
const n1 = `bis${a1[0]}${a1[1]}${a1[2]}`;
|
|
8950
|
+
const n2 = `bis${a2[0]}${a2[1]}${a2[2]}`;
|
|
8951
|
+
const intents = [
|
|
8952
|
+
drawLine(n1, "angleBisector", { p1: a1[0], vertex: a1[1], p2: a1[2] }),
|
|
8953
|
+
drawLine(n2, "angleBisector", { p1: a2[0], vertex: a2[1], p2: a2[2] })
|
|
8954
|
+
];
|
|
8955
|
+
const angleLetters = /* @__PURE__ */ new Set([...a1, ...a2]);
|
|
8956
|
+
if (n1 !== n2 && !angleLetters.has(p)) {
|
|
8957
|
+
intents.push(addPoint(p, { kind: "intersection", of: [n1, n2] }));
|
|
8958
|
+
}
|
|
8959
|
+
out.push({ ruleId: "bisectorsMeet", clauseIds: [c.id], intents });
|
|
8960
|
+
}
|
|
7157
8961
|
}
|
|
8962
|
+
return out;
|
|
7158
8963
|
}
|
|
7159
|
-
|
|
8964
|
+
};
|
|
8965
|
+
|
|
8966
|
+
// src/stamps/geometry-2d/ai/rules/hexagon.ts
|
|
8967
|
+
var HEX = "([A-Z])([A-Z])([A-Z])([A-Z])([A-Z])([A-Z])(?![A-Z])";
|
|
8968
|
+
var HEXAGON_DECL = new RegExp("[Ll]\u1EE5c\\s+gi\xE1c\\s+" + HEX, "gu");
|
|
8969
|
+
var PREFILTER70 = /[Ll]ục\s+giác/u;
|
|
8970
|
+
var HEX_RADIUS = 5;
|
|
8971
|
+
var HEX_APOTHEM = HEX_RADIUS * Math.cos(Math.PI / 6);
|
|
8972
|
+
var HEX_THETAS = Array.from(
|
|
8973
|
+
{ length: 6 },
|
|
8974
|
+
(_, i) => Math.PI / 2 - i * Math.PI / 3
|
|
8975
|
+
);
|
|
8976
|
+
function hexVertexCoord(i) {
|
|
8977
|
+
const t = HEX_THETAS[i];
|
|
8978
|
+
return [
|
|
8979
|
+
+(HEX_RADIUS * Math.cos(t)).toFixed(4),
|
|
8980
|
+
+(HEX_RADIUS * Math.sin(t)).toFixed(4)
|
|
8981
|
+
];
|
|
7160
8982
|
}
|
|
7161
|
-
var
|
|
7162
|
-
|
|
7163
|
-
|
|
8983
|
+
var CENTER4 = "(?:\\(\\s*([A-Z])\\s*\\)|t\xE2m\\s+([A-Z]))";
|
|
8984
|
+
var HEX_CIRCUMSCRIBES = new RegExp(
|
|
8985
|
+
"^[\\s,]*ngo\u1EA1i\\s*ti\u1EBFp\\s+(?:(?:" + CIRCLE_KW + ")\\s*)?" + CENTER4,
|
|
8986
|
+
"u"
|
|
8987
|
+
);
|
|
8988
|
+
var HEX_INSCRIBED_BY = new RegExp(
|
|
8989
|
+
"(?:" + CIRCLE_KW + ")\\s*" + CENTER4 + "\\s*n\u1ED9i\\s*ti\u1EBFp\\s+$",
|
|
8990
|
+
"u"
|
|
8991
|
+
);
|
|
8992
|
+
function detectInscribedCircle(text, start, afterEnd) {
|
|
8993
|
+
const a = HEX_CIRCUMSCRIBES.exec(text.slice(afterEnd));
|
|
8994
|
+
if (a) return a[1] ?? a[2] ?? "O";
|
|
8995
|
+
const b = HEX_INSCRIBED_BY.exec(text.slice(0, start));
|
|
8996
|
+
if (b) return b[1] ?? b[2] ?? "O";
|
|
8997
|
+
return void 0;
|
|
8998
|
+
}
|
|
8999
|
+
var hexagonRule = {
|
|
9000
|
+
id: "hexagon",
|
|
9001
|
+
priority: 100,
|
|
9002
|
+
// ngang quad (đa giác cơ sở) — gate riêng prefilter "lục giác".
|
|
7164
9003
|
languages: ["vi"],
|
|
7165
|
-
patterns: [
|
|
7166
|
-
/(?:[Gg]óc|∠)[^.;\n]{0,18}90/u,
|
|
7167
|
-
/(?:[Gg]óc|∠)\s*[A-Z]{3}\s*vuông/u,
|
|
7168
|
-
/[A-Z]{2}\s*(?:⊥|vuông\s*góc)\s*[A-Z]{2}/u
|
|
7169
|
-
],
|
|
9004
|
+
patterns: [PREFILTER70],
|
|
7170
9005
|
match(ctx) {
|
|
7171
|
-
const
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
(
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
9006
|
+
const out = [];
|
|
9007
|
+
for (const c of ctx.clauses) {
|
|
9008
|
+
HEXAGON_DECL.lastIndex = 0;
|
|
9009
|
+
for (const m of c.text.matchAll(HEXAGON_DECL)) {
|
|
9010
|
+
const labels = [m[1], m[2], m[3], m[4], m[5], m[6]];
|
|
9011
|
+
const start = m.index ?? 0;
|
|
9012
|
+
const afterEnd = start + m[0].length;
|
|
9013
|
+
const vertices = labels.map(
|
|
9014
|
+
(lbl, i) => addPoint(lbl, { kind: "free", at: hexVertexCoord(i) })
|
|
9015
|
+
);
|
|
9016
|
+
const center = detectInscribedCircle(c.text, start, afterEnd);
|
|
9017
|
+
const intents = [...vertices];
|
|
9018
|
+
if (center !== void 0) {
|
|
9019
|
+
const centerName = escapeRe(center) === center ? center : "O";
|
|
9020
|
+
intents.unshift(
|
|
9021
|
+
addPoint(centerName, {
|
|
9022
|
+
kind: "circumcenter",
|
|
9023
|
+
of: [labels[0], labels[2], labels[4]]
|
|
9024
|
+
})
|
|
9025
|
+
);
|
|
9026
|
+
intents.push(
|
|
9027
|
+
drawCircle(centerName, "centerRadius", {
|
|
9028
|
+
center: centerName,
|
|
9029
|
+
radius: HEX_APOTHEM
|
|
9030
|
+
})
|
|
9031
|
+
);
|
|
9032
|
+
}
|
|
9033
|
+
intents.push(markShape("polygon", labels));
|
|
9034
|
+
out.push({ ruleId: "hexagon", clauseIds: [c.id], intents });
|
|
7188
9035
|
}
|
|
7189
|
-
|
|
9036
|
+
}
|
|
7190
9037
|
return out;
|
|
7191
9038
|
}
|
|
7192
9039
|
};
|
|
7193
9040
|
|
|
7194
9041
|
// src/stamps/geometry-2d/ai/rules/registry.ts
|
|
7195
9042
|
var RULES = [
|
|
9043
|
+
namedLineRule,
|
|
9044
|
+
collinearPointsRule,
|
|
9045
|
+
quadDiagonalsRule,
|
|
9046
|
+
twoCirclesTangentRule,
|
|
9047
|
+
commonTangentRule,
|
|
9048
|
+
twoCirclesCenterRadiusMeetRule,
|
|
9049
|
+
rightAngleVertexCirclesRule,
|
|
7196
9050
|
circumcircleCutsLineRule,
|
|
7197
9051
|
lineConcurrencyRule,
|
|
7198
9052
|
parallelThroughCutsCircleRule,
|
|
7199
9053
|
lineThroughCutsTwoLinesRule,
|
|
7200
9054
|
rightAngleViewingRule,
|
|
7201
9055
|
triangleRule,
|
|
9056
|
+
inscribedSquareRule,
|
|
9057
|
+
hexagonRule,
|
|
7202
9058
|
quadRule,
|
|
7203
9059
|
connectRule,
|
|
7204
9060
|
midpointRule,
|
|
@@ -7211,6 +9067,7 @@ var RULES = [
|
|
|
7211
9067
|
circleTriangleRule,
|
|
7212
9068
|
tangentFromExtRule,
|
|
7213
9069
|
externalPointRule,
|
|
9070
|
+
externalPointAtRadiusRule,
|
|
7214
9071
|
arcMidpointRule,
|
|
7215
9072
|
excenterRule,
|
|
7216
9073
|
parallelPerpRule,
|
|
@@ -7250,6 +9107,7 @@ var RULES = [
|
|
|
7250
9107
|
tangentAtCutsLinesRule,
|
|
7251
9108
|
diameterEndpointRule,
|
|
7252
9109
|
circleThroughTwoCutsSidesRule,
|
|
9110
|
+
circleCenterRadiusCutsSidesRule,
|
|
7253
9111
|
circumcircleNamedDistribRule,
|
|
7254
9112
|
circumcirclePairMeetRule,
|
|
7255
9113
|
tangentCircleAtPointRule,
|
|
@@ -7278,7 +9136,8 @@ var RULES = [
|
|
|
7278
9136
|
twoCirclesMeetRule,
|
|
7279
9137
|
radiusRule,
|
|
7280
9138
|
perpNamedLineRule,
|
|
7281
|
-
parallelSidePointsRule
|
|
9139
|
+
parallelSidePointsRule,
|
|
9140
|
+
bisectorsMeetRule
|
|
7282
9141
|
];
|
|
7283
9142
|
var ALL_RULES = [...RULES].sort(
|
|
7284
9143
|
(a, b) => b.priority - a.priority
|
|
@@ -7293,20 +9152,329 @@ function runRules(ctx) {
|
|
|
7293
9152
|
}
|
|
7294
9153
|
|
|
7295
9154
|
// src/stamps/geometry-2d/ai/deterministic/normalizeText.ts
|
|
7296
|
-
var TRIANGLE_SYMBOL = /[
|
|
9155
|
+
var TRIANGLE_SYMBOL = /[Δ∆]\s*(?=[A-Z])/gu;
|
|
7297
9156
|
var CIRCLE_SYNONYM = /vòng\s+tròn/giu;
|
|
9157
|
+
var CIRCLE_PAREN_RESTORE = /(đường\s*tròn\s+)([A-Z])(['′]?)\s*;\s*([Rr])(['′]?)(?![A-Za-z'′])/gu;
|
|
7298
9158
|
var QUAD_SYMBOL = /[◊▱□]\s*(?=[A-Z])/gu;
|
|
9159
|
+
var MIDPOINT_TYPO = /trung\s*điển(?!\p{L})/gu;
|
|
7299
9160
|
var SQRT_NOISE = /√/gu;
|
|
7300
9161
|
var PRIME_VARIANT = /([A-Za-z0-9])[’′´]/gu;
|
|
9162
|
+
var DIAERESIS_O = /(?<!\p{Ll})Ö(?!\p{Ll})/gu;
|
|
9163
|
+
var INTERSECT_PAREN = /(\([A-Z]{3}\))\s+N\s+([A-Z]{2})(\s*=\s*\{)/gu;
|
|
7301
9164
|
var GLUE_WORD_LABEL = /(cắt|tâm|điểm|cạnh|tia|dây|cung|qua|của|và|tại|trên|đến)([A-Z])/gu;
|
|
7302
9165
|
var GLUE_LABEL_WORD = /([A-Z])(sao|nằm|lần|thuộc|cắt|của|đến|trên|tại|và)/gu;
|
|
9166
|
+
var GLUE_RAYTOKEN_WORD = /([A-Z][a-z])(tại|cắt|và|của|đến|trên|thuộc)(?![\p{L}])/gu;
|
|
9167
|
+
var GLUE_VOCAB = [
|
|
9168
|
+
"\u0111\u01B0\u1EDDng",
|
|
9169
|
+
"ngo\u1EA1i",
|
|
9170
|
+
"trung",
|
|
9171
|
+
"ph\xE2n",
|
|
9172
|
+
"\u0111i\u1EC3m",
|
|
9173
|
+
"gi\xE1c",
|
|
9174
|
+
"tr\xF2n",
|
|
9175
|
+
"c\u1EA1nh",
|
|
9176
|
+
"k\xEDnh",
|
|
9177
|
+
"n\u1ED9i",
|
|
9178
|
+
"ti\u1EBFp",
|
|
9179
|
+
"cung",
|
|
9180
|
+
"d\xE2y",
|
|
9181
|
+
"tam",
|
|
9182
|
+
"t\u1EE9",
|
|
9183
|
+
"g\xF3c",
|
|
9184
|
+
"vu\xF4ng",
|
|
9185
|
+
"c\xE2n",
|
|
9186
|
+
"nh\u1ECDn",
|
|
9187
|
+
"n\u1EEDa",
|
|
9188
|
+
"\u0111o\u1EA1n",
|
|
9189
|
+
"h\xECnh",
|
|
9190
|
+
"qua",
|
|
9191
|
+
"c\u1EAFt",
|
|
9192
|
+
"l\u1EA1i",
|
|
9193
|
+
"c\xE1c",
|
|
9194
|
+
"v\xE0",
|
|
9195
|
+
"t\u1EA1i",
|
|
9196
|
+
"m\u1ED9t",
|
|
9197
|
+
"ba",
|
|
9198
|
+
"b\u1EA5t",
|
|
9199
|
+
"k\xEC",
|
|
9200
|
+
"k\u1EF3",
|
|
9201
|
+
"c\xF3",
|
|
9202
|
+
"\u0111i",
|
|
9203
|
+
"v\u1EDBi",
|
|
9204
|
+
"l\u1EA5y",
|
|
9205
|
+
"c\u1EE7a",
|
|
9206
|
+
"l\u1EA7n",
|
|
9207
|
+
"l\u01B0\u1EE3t",
|
|
9208
|
+
"th\u1EE9",
|
|
9209
|
+
"hai",
|
|
9210
|
+
"g\u1ECDi",
|
|
9211
|
+
"k\u1EBB",
|
|
9212
|
+
"v\u1EBD",
|
|
9213
|
+
"cho",
|
|
9214
|
+
"t\xE2m",
|
|
9215
|
+
"b\xE1n",
|
|
9216
|
+
// 'là' (hệ từ) + từ-vựng hình-chiếu/⊥ — tách glue "làtrung"/"làhình"/
|
|
9217
|
+
// "chiếucủa"/"xuốngđường" (OCR HHP dày). Whitelist↔whitelist nên 'là' chỉ tách
|
|
9218
|
+
// khi theo NGAY bởi một từ-khoá hình-học khác (an toàn, regression-gated).
|
|
9219
|
+
"l\xE0",
|
|
9220
|
+
"chi\u1EBFu",
|
|
9221
|
+
"xu\u1ED1ng",
|
|
9222
|
+
"l\xEAn",
|
|
9223
|
+
"\u0111\u1ED1i",
|
|
9224
|
+
"x\u1EE9ng",
|
|
9225
|
+
"song",
|
|
9226
|
+
"th\u1EB3ng"
|
|
9227
|
+
];
|
|
9228
|
+
var GLUE_VOCAB_SORTED = [...GLUE_VOCAB].sort((a, b) => b.length - a.length);
|
|
9229
|
+
var GLUE_ALT = GLUE_VOCAB_SORTED.join("|");
|
|
9230
|
+
var GLUE_VOCAB_RE = new RegExp(`(${GLUE_ALT})(?=${GLUE_ALT})`, "gu");
|
|
9231
|
+
var GLUE_VOCAB_LABEL = new RegExp(
|
|
9232
|
+
`(gi\xE1c|tr\xF2n|ti\u1EBFp|x\xFAc|k\xEDnh|\u0111i\u1EC3m|\u0111o\u1EA1n|c\u1EA1nh|d\xE2y|cung|t\u1EA1i|qua|\u0111i|v\u1EDBi|c\xF3|t\xE2m|th\u1EB3ng|l\xEAn|xu\u1ED1ng)([A-Z])`,
|
|
9233
|
+
"gu"
|
|
9234
|
+
);
|
|
9235
|
+
var CAP_GLUE = /(Cho|Đường|Gọi|Trên|Vẽ|Kẻ|Lấy|Các|Một|Trung|Tâm|Nửa|Hình)(đường|tam|tứ|nửa|hình|điểm|các|cạnh|đoạn|trung|tròn|giác|một|ba|hai|tia|dây|cung)/gu;
|
|
9236
|
+
var GLUE_LABEL_VOCAB = new RegExp(`([A-Z])(${GLUE_ALT})(?![\\p{L}])`, "gu");
|
|
9237
|
+
var GLUE_PAREN_OPEN = /(\p{Ll})\(/gu;
|
|
9238
|
+
var GLUE_PAREN_CLOSE = /\)(\p{L})/gu;
|
|
9239
|
+
function deglueMultiWord(s) {
|
|
9240
|
+
let out = s.replace(CAP_GLUE, "$1 $2");
|
|
9241
|
+
for (let k = 0; k < 8; k++) {
|
|
9242
|
+
const next = out.replace(GLUE_VOCAB_RE, "$1 ");
|
|
9243
|
+
if (next === out) break;
|
|
9244
|
+
out = next;
|
|
9245
|
+
}
|
|
9246
|
+
out = out.replace(GLUE_VOCAB_LABEL, "$1 $2").replace(GLUE_LABEL_VOCAB, "$1 $2").replace(GLUE_PAREN_OPEN, "$1 (").replace(GLUE_PAREN_CLOSE, ") $1");
|
|
9247
|
+
return out;
|
|
9248
|
+
}
|
|
7303
9249
|
function normalizeProblemText(problem) {
|
|
7304
|
-
|
|
9250
|
+
let s = problem.replace(TRIANGLE_SYMBOL, "tam gi\xE1c ").replace(QUAD_SYMBOL, "t\u1EE9 gi\xE1c ").replace(MIDPOINT_TYPO, "trung \u0111i\u1EC3m").replace(DIAERESIS_O, "O").replace(INTERSECT_PAREN, "$1 \u2229 $2$3").replace(CIRCLE_SYNONYM, "\u0111\u01B0\u1EDDng tr\xF2n");
|
|
9251
|
+
if (!/\(\s*[A-Z]/u.test(s)) s = s.replace(CIRCLE_PAREN_RESTORE, "$1($2$3;$4$5)");
|
|
9252
|
+
return deglueMultiWord(
|
|
9253
|
+
s.replace(SQRT_NOISE, " ").replace(PRIME_VARIANT, "$1'").replace(GLUE_WORD_LABEL, "$1 $2").replace(GLUE_LABEL_WORD, "$1 $2").replace(GLUE_RAYTOKEN_WORD, "$1 $2")
|
|
9254
|
+
);
|
|
9255
|
+
}
|
|
9256
|
+
|
|
9257
|
+
// src/stamps/geometry-2d/ai/deterministic/correctUserInput.ts
|
|
9258
|
+
function foldVietnamese(s) {
|
|
9259
|
+
return s.toLowerCase().replace(/đ/g, "d").normalize("NFD").replace(/[̀-ͯ]/g, "");
|
|
9260
|
+
}
|
|
9261
|
+
function levenshtein(a, b) {
|
|
9262
|
+
const m = a.length;
|
|
9263
|
+
const n = b.length;
|
|
9264
|
+
if (m === 0) return n;
|
|
9265
|
+
if (n === 0) return m;
|
|
9266
|
+
let prev = new Array(n + 1);
|
|
9267
|
+
for (let j = 0; j <= n; j++) prev[j] = j;
|
|
9268
|
+
for (let i = 1; i <= m; i++) {
|
|
9269
|
+
const cur = new Array(n + 1);
|
|
9270
|
+
cur[0] = i;
|
|
9271
|
+
for (let j = 1; j <= n; j++) {
|
|
9272
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
9273
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + cost);
|
|
9274
|
+
}
|
|
9275
|
+
prev = cur;
|
|
9276
|
+
}
|
|
9277
|
+
return prev[n];
|
|
9278
|
+
}
|
|
9279
|
+
var CORRECTION_VOCAB = [
|
|
9280
|
+
// circle / line core
|
|
9281
|
+
"\u0111\u01B0\u1EDDng",
|
|
9282
|
+
"tr\xF2n",
|
|
9283
|
+
"b\xE1n",
|
|
9284
|
+
"k\xEDnh",
|
|
9285
|
+
"v\xF2ng",
|
|
9286
|
+
// polygon / triangle (TAM before TÂM, THANG before THẲNG — see collision-winner warning above)
|
|
9287
|
+
"tam",
|
|
9288
|
+
"t\xE2m",
|
|
9289
|
+
"thang",
|
|
9290
|
+
"th\u1EB3ng",
|
|
9291
|
+
"gi\xE1c",
|
|
9292
|
+
"t\u1EE9",
|
|
9293
|
+
"g\xF3c",
|
|
9294
|
+
"c\u1EA1nh",
|
|
9295
|
+
"\u0111o\u1EA1n",
|
|
9296
|
+
"h\xECnh",
|
|
9297
|
+
"ch\u1EEF",
|
|
9298
|
+
"nh\u1EADt",
|
|
9299
|
+
"vu\xF4ng",
|
|
9300
|
+
"c\xE2n",
|
|
9301
|
+
"nh\u1ECDn",
|
|
9302
|
+
"\u0111\u1EC1u",
|
|
9303
|
+
"thoi",
|
|
9304
|
+
"b\xECnh",
|
|
9305
|
+
"h\xE0nh",
|
|
9306
|
+
// chord / arc / point
|
|
9307
|
+
"d\xE2y",
|
|
9308
|
+
"cung",
|
|
9309
|
+
"\u0111i\u1EC3m",
|
|
9310
|
+
"tia",
|
|
9311
|
+
"n\u1EEDa",
|
|
9312
|
+
// tangent / inscribed
|
|
9313
|
+
"ti\u1EBFp",
|
|
9314
|
+
"tuy\u1EBFn",
|
|
9315
|
+
"n\u1ED9i",
|
|
9316
|
+
"ngo\u1EA1i",
|
|
9317
|
+
"x\xFAc",
|
|
9318
|
+
// cevians / projection
|
|
9319
|
+
"trung",
|
|
9320
|
+
"tr\u1EF1c",
|
|
9321
|
+
"cao",
|
|
9322
|
+
"ph\xE2n",
|
|
9323
|
+
"chi\u1EBFu",
|
|
9324
|
+
"\u0111\u1ED1i",
|
|
9325
|
+
"x\u1EE9ng",
|
|
9326
|
+
// verbs / connectors phổ biến trong đề
|
|
9327
|
+
"cho",
|
|
9328
|
+
"g\u1ECDi",
|
|
9329
|
+
"v\u1EBD",
|
|
9330
|
+
"k\u1EBB",
|
|
9331
|
+
"l\u1EA5y",
|
|
9332
|
+
"qua",
|
|
9333
|
+
"c\u1EAFt",
|
|
9334
|
+
"tr\xEAn",
|
|
9335
|
+
"d\u01B0\u1EDBi",
|
|
9336
|
+
"\u0111\u1EBFn",
|
|
9337
|
+
// 'đi' ĐÃ GỠ: "di"→"đi" net-harmful trên corpus (regress 7 bài locus "di động"
|
|
9338
|
+
// → "đi động"). Từ TRẦN 2 ký tự đa-nghĩa: rủi ro cao, lợi ích thấp.
|
|
9339
|
+
"song",
|
|
9340
|
+
"v\u1EDBi",
|
|
9341
|
+
"c\u1EE7a",
|
|
9342
|
+
"v\xE0",
|
|
9343
|
+
"l\xE0",
|
|
9344
|
+
"c\xF3",
|
|
9345
|
+
"n\u1EB1m",
|
|
9346
|
+
"thu\u1ED9c",
|
|
9347
|
+
"t\u1EA1i",
|
|
9348
|
+
"m\u1ED9t",
|
|
9349
|
+
"hai",
|
|
9350
|
+
"ba",
|
|
9351
|
+
"c\xE1c",
|
|
9352
|
+
"l\u1EA7n",
|
|
9353
|
+
"l\u01B0\u1EE3t",
|
|
9354
|
+
"th\u1EE9",
|
|
9355
|
+
"b\u1EA5t",
|
|
9356
|
+
"k\xEC",
|
|
9357
|
+
"k\u1EF3",
|
|
9358
|
+
"trong",
|
|
9359
|
+
"ngo\xE0i",
|
|
9360
|
+
"l\xEAn",
|
|
9361
|
+
"xu\u1ED1ng"
|
|
9362
|
+
];
|
|
9363
|
+
var FOLDED_VOCAB = (() => {
|
|
9364
|
+
const m = /* @__PURE__ */ new Map();
|
|
9365
|
+
for (const w of CORRECTION_VOCAB) {
|
|
9366
|
+
const f = foldVietnamese(w);
|
|
9367
|
+
if (!m.has(f)) m.set(f, w);
|
|
9368
|
+
}
|
|
9369
|
+
return m;
|
|
9370
|
+
})();
|
|
9371
|
+
var LETTERS_ONLY = /^\p{L}+$/u;
|
|
9372
|
+
var UPPER_LABEL = /^\p{Lu}{1,4}$/u;
|
|
9373
|
+
var HAS_UPPER = /\p{Lu}/u;
|
|
9374
|
+
function classifyToken(token) {
|
|
9375
|
+
if (!LETTERS_ONLY.test(token)) return "protected";
|
|
9376
|
+
if (UPPER_LABEL.test(token)) return "protected";
|
|
9377
|
+
return HAS_UPPER.test(token) ? "upper" : "lower";
|
|
9378
|
+
}
|
|
9379
|
+
var DEFAULT_CORRECT_CONFIG = {
|
|
9380
|
+
structure: true,
|
|
9381
|
+
accents: true,
|
|
9382
|
+
// Tầng 3 (fuzzy typo) MẶC ĐỊNH TẮT: trên corpus thật nó băm từ TRẦN hợp lệ
|
|
9383
|
+
// ("nhau"→"nhật", "giao"→"giác", "di"→"đi") vì vocab hình-học trùng không-gian
|
|
9384
|
+
// âm với văn xuôi Việt → net-harmful (đo qua diag-all). Vẫn cài đặt đầy đủ + bật
|
|
9385
|
+
// được qua flag (mutation test / opt-in tương lai). accents+structure đủ an toàn.
|
|
9386
|
+
typo: false,
|
|
9387
|
+
maxTypoDistance: 1,
|
|
9388
|
+
minTypoLen: 4
|
|
9389
|
+
};
|
|
9390
|
+
var SYMBOL_MAP = [
|
|
9391
|
+
// "độ" có dấu = độ-góc rõ ràng → luôn đổi. "do" trần mơ hồ (do đó/do dai/lý do)
|
|
9392
|
+
// → CHỈ đổi khi KHÔNG theo sau bởi một từ-chữ (cuối cụm / trước dấu câu), tránh
|
|
9393
|
+
// băm "2 do dai"→"2° dai". "90 do"/"90 do." vẫn đổi.
|
|
9394
|
+
[/(\d+)\s*độ(?!\p{L})/giu, "$1\xB0"],
|
|
9395
|
+
[/(\d+)\s*do(?!\s*\p{L})/giu, "$1\xB0"]
|
|
9396
|
+
];
|
|
9397
|
+
function applyStructure(s) {
|
|
9398
|
+
let out = s;
|
|
9399
|
+
for (const [re, to] of SYMBOL_MAP) out = out.replace(re, to);
|
|
9400
|
+
return out.replace(/\s+/g, " ").trim();
|
|
9401
|
+
}
|
|
9402
|
+
function applyLeadingCase(canonical, original) {
|
|
9403
|
+
if (/^\p{Lu}/u.test(original)) {
|
|
9404
|
+
return canonical.charAt(0).toUpperCase() + canonical.slice(1);
|
|
9405
|
+
}
|
|
9406
|
+
return canonical;
|
|
9407
|
+
}
|
|
9408
|
+
function fuzzyLookup(folded, cfg) {
|
|
9409
|
+
if (folded.length < cfg.minTypoLen) return null;
|
|
9410
|
+
let best = null;
|
|
9411
|
+
let bestDist = cfg.maxTypoDistance + 1;
|
|
9412
|
+
let tie = false;
|
|
9413
|
+
for (const [key, canonical] of FOLDED_VOCAB) {
|
|
9414
|
+
if (canonical === key) continue;
|
|
9415
|
+
if (Math.abs(key.length - folded.length) > cfg.maxTypoDistance) continue;
|
|
9416
|
+
const d = levenshtein(folded, key);
|
|
9417
|
+
if (d < bestDist) {
|
|
9418
|
+
bestDist = d;
|
|
9419
|
+
best = canonical;
|
|
9420
|
+
tie = false;
|
|
9421
|
+
} else if (d === bestDist) {
|
|
9422
|
+
tie = true;
|
|
9423
|
+
}
|
|
9424
|
+
}
|
|
9425
|
+
if (best === null || bestDist > cfg.maxTypoDistance || tie) return null;
|
|
9426
|
+
return best;
|
|
9427
|
+
}
|
|
9428
|
+
function letterFold(token) {
|
|
9429
|
+
return foldVietnamese(token).replace(/[^a-z]+/g, "");
|
|
9430
|
+
}
|
|
9431
|
+
var DISAMBIG = {
|
|
9432
|
+
// "tam giác" giữ "tam"; còn lại (tâm O, tâm đường tròn…) → "tâm".
|
|
9433
|
+
tam: (_p, n) => n === "giac" ? "tam" : "t\xE2m",
|
|
9434
|
+
// "hình thang" giữ "thang"; còn lại (đường thẳng, thẳng hàng…) → "thẳng".
|
|
9435
|
+
thang: (p, _n) => p === "hinh" ? "thang" : "th\u1EB3ng",
|
|
9436
|
+
// "ngoại tiếp" → "ngoại"; còn lại (nằm ngoài, điểm ngoài…) → "ngoài".
|
|
9437
|
+
ngoai: (_p, n) => n === "tiep" ? "ngo\u1EA1i" : "ngo\xE0i"
|
|
9438
|
+
};
|
|
9439
|
+
function correctToken(token, cfg, prevFold = "", nextFold = "") {
|
|
9440
|
+
const klass = classifyToken(token);
|
|
9441
|
+
if (klass === "protected") return token;
|
|
9442
|
+
const folded = foldVietnamese(token);
|
|
9443
|
+
if (folded !== token.toLowerCase()) return token;
|
|
9444
|
+
if (cfg.accents) {
|
|
9445
|
+
const dis = DISAMBIG[folded];
|
|
9446
|
+
if (dis) return applyLeadingCase(dis(prevFold, nextFold), token);
|
|
9447
|
+
const exact = FOLDED_VOCAB.get(folded);
|
|
9448
|
+
if (exact) return applyLeadingCase(exact, token);
|
|
9449
|
+
}
|
|
9450
|
+
if (cfg.typo && klass === "lower") {
|
|
9451
|
+
const near = fuzzyLookup(folded, cfg);
|
|
9452
|
+
if (near) return near;
|
|
9453
|
+
}
|
|
9454
|
+
return token;
|
|
9455
|
+
}
|
|
9456
|
+
function correctUserInput(input, cfg = DEFAULT_CORRECT_CONFIG) {
|
|
9457
|
+
let s = input;
|
|
9458
|
+
if (cfg.structure) s = applyStructure(s);
|
|
9459
|
+
if (!cfg.accents && !cfg.typo) return s;
|
|
9460
|
+
const parts = s.split(/(\s+)/);
|
|
9461
|
+
const wordIdx = [];
|
|
9462
|
+
for (let i = 0; i < parts.length; i++) {
|
|
9463
|
+
if (parts[i] !== "" && !/\s/.test(parts[i])) wordIdx.push(i);
|
|
9464
|
+
}
|
|
9465
|
+
const out = parts.slice();
|
|
9466
|
+
for (let w = 0; w < wordIdx.length; w++) {
|
|
9467
|
+
const i = wordIdx[w];
|
|
9468
|
+
const prevFold = w > 0 ? letterFold(parts[wordIdx[w - 1]]) : "";
|
|
9469
|
+
const nextFold = w < wordIdx.length - 1 ? letterFold(parts[wordIdx[w + 1]]) : "";
|
|
9470
|
+
out[i] = correctToken(parts[i], cfg, prevFold, nextFold);
|
|
9471
|
+
}
|
|
9472
|
+
return out.join("");
|
|
7305
9473
|
}
|
|
7306
9474
|
|
|
7307
9475
|
// src/stamps/geometry-2d/ai/deterministic/runDeterministicIntents.ts
|
|
7308
9476
|
function collectDeterministic(rawProblem) {
|
|
7309
|
-
const problem = normalizeProblemText(rawProblem);
|
|
9477
|
+
const problem = normalizeProblemText(correctUserInput(rawProblem));
|
|
7310
9478
|
const clauses = segmentClauses(problem);
|
|
7311
9479
|
const drawableClauses = clauses.filter((c) => c.hasGeometry);
|
|
7312
9480
|
let drawableProblem = problem;
|
|
@@ -7355,7 +9523,7 @@ var SHAPE_TRI = /tam giác\s+([A-Z]{3})(?![A-Z])/gu;
|
|
|
7355
9523
|
var SHAPE_QUAD = /(?:tứ giác|hình\s+(?:vuông|chữ nhật|bình hành|thoi|thang))\s+([A-Z]{4})(?![A-Z])/gu;
|
|
7356
9524
|
function collectExpectedNames(problem) {
|
|
7357
9525
|
const names = /* @__PURE__ */ new Set();
|
|
7358
|
-
const
|
|
9526
|
+
const add2 = (re, groups) => {
|
|
7359
9527
|
re.lastIndex = 0;
|
|
7360
9528
|
let m;
|
|
7361
9529
|
while ((m = re.exec(problem)) !== null) {
|
|
@@ -7365,9 +9533,9 @@ function collectExpectedNames(problem) {
|
|
|
7365
9533
|
}
|
|
7366
9534
|
}
|
|
7367
9535
|
};
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
9536
|
+
add2(NAMED_INTRO, [[1, 2]]);
|
|
9537
|
+
add2(NAMED_LA, [[1, 2]]);
|
|
9538
|
+
add2(NAMED_LANLUOT, [[1, 2], [3, 4]]);
|
|
7371
9539
|
for (const re of [SHAPE_TRI, SHAPE_QUAD]) {
|
|
7372
9540
|
re.lastIndex = 0;
|
|
7373
9541
|
let m;
|
|
@@ -7618,6 +9786,12 @@ function rewriteCircleRefs(intent, rename) {
|
|
|
7618
9786
|
if (newC1 !== c.c1 || newC2 !== c.c2) {
|
|
7619
9787
|
return { ...intent, constraint: { ...c, c1: newC1, c2: newC2 } };
|
|
7620
9788
|
}
|
|
9789
|
+
} else if (c.kind === "commonTangentPoint") {
|
|
9790
|
+
const n0 = rename.get(c.circles[0]) ?? c.circles[0];
|
|
9791
|
+
const n1 = rename.get(c.circles[1]) ?? c.circles[1];
|
|
9792
|
+
if (n0 !== c.circles[0] || n1 !== c.circles[1]) {
|
|
9793
|
+
return { ...intent, constraint: { ...c, circles: [n0, n1] } };
|
|
9794
|
+
}
|
|
7621
9795
|
} else if (c.kind === "intersection") {
|
|
7622
9796
|
const newOf = c.of.map((r) => rename.get(r) ?? r);
|
|
7623
9797
|
if (newOf[0] !== c.of[0] || newOf[1] !== c.of[1]) {
|
|
@@ -7685,14 +9859,14 @@ function repairCircleIntersections(points, shapes) {
|
|
|
7685
9859
|
const targetD = (lower + upper) / 2;
|
|
7686
9860
|
let dx = mover.x - anchor[0];
|
|
7687
9861
|
let dy = mover.y - anchor[1];
|
|
7688
|
-
const
|
|
7689
|
-
if (
|
|
9862
|
+
const len2 = Math.hypot(dx, dy);
|
|
9863
|
+
if (len2 < 1e-9) {
|
|
7690
9864
|
dx = 1;
|
|
7691
9865
|
dy = 0;
|
|
7692
9866
|
}
|
|
7693
|
-
const
|
|
7694
|
-
mover.x = anchor[0] + targetD * dx /
|
|
7695
|
-
mover.y = anchor[1] + targetD * dy /
|
|
9867
|
+
const norm5 = Math.hypot(dx, dy) || 1;
|
|
9868
|
+
mover.x = anchor[0] + targetD * dx / norm5;
|
|
9869
|
+
mover.y = anchor[1] + targetD * dy / norm5;
|
|
7696
9870
|
}
|
|
7697
9871
|
}
|
|
7698
9872
|
|
|
@@ -7880,156 +10054,6 @@ var IntentBuilderError = class extends Error {
|
|
|
7880
10054
|
}
|
|
7881
10055
|
};
|
|
7882
10056
|
|
|
7883
|
-
// src/stamps/geometry-2d/ai/intent-builders/shared.ts
|
|
7884
|
-
var SQRT3 = 1.7320508075688772;
|
|
7885
|
-
function triangleCanonical(variant) {
|
|
7886
|
-
switch (variant) {
|
|
7887
|
-
case "equilateral":
|
|
7888
|
-
return [[0, 0], [4, 0], [2, 2 * SQRT3]];
|
|
7889
|
-
case "isoceles-AB":
|
|
7890
|
-
return [[0, 0], [4, 0], [2, 3]];
|
|
7891
|
-
case "isoceles-BC":
|
|
7892
|
-
return [[0, 3], [-2, 0], [2, 0]];
|
|
7893
|
-
case "isoceles-CA":
|
|
7894
|
-
return [[0, 0], [2, 3], [4, 0]];
|
|
7895
|
-
case "right-at-A":
|
|
7896
|
-
return [[0, 0], [4, 0], [0, 3]];
|
|
7897
|
-
case "right-at-B":
|
|
7898
|
-
return [[0, 0], [4, 0], [4, 3]];
|
|
7899
|
-
case "right-at-C":
|
|
7900
|
-
return [[0, 0], [6, 0], [3, 3]];
|
|
7901
|
-
case "any":
|
|
7902
|
-
default:
|
|
7903
|
-
return [[0, 0], [5, 0], [2, 3]];
|
|
7904
|
-
}
|
|
7905
|
-
}
|
|
7906
|
-
function squareCanonical() {
|
|
7907
|
-
return [[0, 0], [4, 0], [4, 4], [0, 4]];
|
|
7908
|
-
}
|
|
7909
|
-
function rectangleCanonical(variant) {
|
|
7910
|
-
if (variant === "tall") return [[0, 0], [2.5, 0], [2.5, 4], [0, 4]];
|
|
7911
|
-
return [[0, 0], [4, 0], [4, 2.5], [0, 2.5]];
|
|
7912
|
-
}
|
|
7913
|
-
function rhombusCanonical() {
|
|
7914
|
-
return [[-2, 0], [0, -1.5], [2, 0], [0, 1.5]];
|
|
7915
|
-
}
|
|
7916
|
-
function trapezoidCanonical(variant) {
|
|
7917
|
-
switch (variant) {
|
|
7918
|
-
case "right":
|
|
7919
|
-
return [[0, 0], [5, 0], [3, 3], [0, 3]];
|
|
7920
|
-
case "isoceles":
|
|
7921
|
-
return [[0, 0], [5, 0], [4, 3], [1, 3]];
|
|
7922
|
-
case "general":
|
|
7923
|
-
default:
|
|
7924
|
-
return [[0, 0], [5, 0], [3.5, 3], [1, 3]];
|
|
7925
|
-
}
|
|
7926
|
-
}
|
|
7927
|
-
function parallelogramCanonical() {
|
|
7928
|
-
return [[0, 0], [4, 0], [5, 3], [1, 3]];
|
|
7929
|
-
}
|
|
7930
|
-
function quadrilateralCanonical() {
|
|
7931
|
-
return [[0, 0], [5, 0], [4.5, 3.5], [0.5, 3]];
|
|
7932
|
-
}
|
|
7933
|
-
function addPoint2(s, p) {
|
|
7934
|
-
if (s.pointNames.has(p.name)) {
|
|
7935
|
-
if (p.kind !== "free") {
|
|
7936
|
-
const idx = s.points.findIndex((q) => q.name === p.name);
|
|
7937
|
-
if (idx >= 0 && s.points[idx].kind === "free") s.points[idx] = p;
|
|
7938
|
-
}
|
|
7939
|
-
return;
|
|
7940
|
-
}
|
|
7941
|
-
s.points.push(p);
|
|
7942
|
-
s.pointNames.add(p.name);
|
|
7943
|
-
}
|
|
7944
|
-
var FREE_DEFAULT_SPREAD = [
|
|
7945
|
-
[3, 3],
|
|
7946
|
-
[-3, 3],
|
|
7947
|
-
[3, -3],
|
|
7948
|
-
[-3, -3],
|
|
7949
|
-
[4, 0],
|
|
7950
|
-
[-4, 0],
|
|
7951
|
-
[0, 4],
|
|
7952
|
-
[0, -4]
|
|
7953
|
-
];
|
|
7954
|
-
function defaultFreeCoord(s) {
|
|
7955
|
-
let count = 0;
|
|
7956
|
-
for (const p of s.points) if (p.kind === "free") count++;
|
|
7957
|
-
return [...FREE_DEFAULT_SPREAD[count % FREE_DEFAULT_SPREAD.length]];
|
|
7958
|
-
}
|
|
7959
|
-
function addShape(s, sh) {
|
|
7960
|
-
if (s.shapeNames.has(sh.name)) return;
|
|
7961
|
-
s.shapes.push(sh);
|
|
7962
|
-
s.shapeNames.add(sh.name);
|
|
7963
|
-
}
|
|
7964
|
-
function uniqueShapeName(s, base) {
|
|
7965
|
-
if (!s.shapeNames.has(base)) return base;
|
|
7966
|
-
let i = 2;
|
|
7967
|
-
while (s.shapeNames.has(`${base}${i}`)) i++;
|
|
7968
|
-
return `${base}${i}`;
|
|
7969
|
-
}
|
|
7970
|
-
function uniquePointName(s, base) {
|
|
7971
|
-
if (!s.pointNames.has(base)) return base;
|
|
7972
|
-
let i = 2;
|
|
7973
|
-
while (s.pointNames.has(`${base}${i}`)) i++;
|
|
7974
|
-
return `${base}${i}`;
|
|
7975
|
-
}
|
|
7976
|
-
function ensureSegment(s, a, b) {
|
|
7977
|
-
const k1 = `${a}-${b}`;
|
|
7978
|
-
const k2 = `${b}-${a}`;
|
|
7979
|
-
const existing = s.segmentByEnds.get(k1) ?? s.segmentByEnds.get(k2);
|
|
7980
|
-
if (existing) return existing;
|
|
7981
|
-
const name = uniqueShapeName(s, `${a}${b}`);
|
|
7982
|
-
addShape(s, { name, kind: "segment", p1: a, p2: b });
|
|
7983
|
-
s.segmentByEnds.set(k1, name);
|
|
7984
|
-
return name;
|
|
7985
|
-
}
|
|
7986
|
-
function resolveLineRefWithFallback(s, ref, anchor) {
|
|
7987
|
-
if (/^[A-Za-z]$/.test(ref) && /^[A-Za-z]$/.test(anchor) && ref !== anchor && s.pointNames.has(ref) && s.pointNames.has(anchor)) {
|
|
7988
|
-
const [a, b] = ref < anchor ? [ref, anchor] : [anchor, ref];
|
|
7989
|
-
return ensureSegment(s, a, b);
|
|
7990
|
-
}
|
|
7991
|
-
return resolveSegmentRef(s, ref);
|
|
7992
|
-
}
|
|
7993
|
-
function resolveSegmentRef(s, ref) {
|
|
7994
|
-
if (s.shapeNames.has(ref)) return ref;
|
|
7995
|
-
if (/^[A-Z][A-Z]$/u.test(ref)) {
|
|
7996
|
-
return ensureSegment(s, ref[0], ref[1]);
|
|
7997
|
-
}
|
|
7998
|
-
if (ref.length === 2 && s.pointNames.has(ref[0]) && s.pointNames.has(ref[1])) {
|
|
7999
|
-
return ensureSegment(s, ref[0], ref[1]);
|
|
8000
|
-
}
|
|
8001
|
-
const dashMatch = ref.match(/^([A-Za-z][A-Za-z0-9'_]*)[-_]?([A-Za-z][A-Za-z0-9'_]*)$/);
|
|
8002
|
-
if (dashMatch && s.pointNames.has(dashMatch[1]) && s.pointNames.has(dashMatch[2])) {
|
|
8003
|
-
return ensureSegment(s, dashMatch[1], dashMatch[2]);
|
|
8004
|
-
}
|
|
8005
|
-
const sub = splitKnownPair(s, ref);
|
|
8006
|
-
if (sub) return ensureSegment(s, sub[0], sub[1]);
|
|
8007
|
-
return ref;
|
|
8008
|
-
}
|
|
8009
|
-
function splitKnownPair(s, ref) {
|
|
8010
|
-
for (let i = ref.length - 1; i >= 1; i--) {
|
|
8011
|
-
const a = ref.slice(0, i);
|
|
8012
|
-
const b = ref.slice(i);
|
|
8013
|
-
if (s.pointNames.has(a) && s.pointNames.has(b)) return [a, b];
|
|
8014
|
-
}
|
|
8015
|
-
return null;
|
|
8016
|
-
}
|
|
8017
|
-
function parseEnds(ref) {
|
|
8018
|
-
if (ref.length === 2) return [ref[0], ref[1]];
|
|
8019
|
-
const m = ref.match(/^([A-Za-z][A-Za-z0-9'_]*)[-_]?([A-Za-z][A-Za-z0-9'_]*)$/);
|
|
8020
|
-
if (m) return [m[1], m[2]];
|
|
8021
|
-
return null;
|
|
8022
|
-
}
|
|
8023
|
-
var SHAPE_VARIANTS = {
|
|
8024
|
-
triangle: ["any", "equilateral", "isoceles-AB", "isoceles-BC", "isoceles-CA", "right-at-A", "right-at-B", "right-at-C"],
|
|
8025
|
-
square: ["standard"],
|
|
8026
|
-
rectangle: ["standard", "wide", "tall"],
|
|
8027
|
-
rhombus: ["standard"],
|
|
8028
|
-
trapezoid: ["right", "isoceles", "general"],
|
|
8029
|
-
parallelogram: ["standard"],
|
|
8030
|
-
quadrilateral: ["any"]
|
|
8031
|
-
};
|
|
8032
|
-
|
|
8033
10057
|
// src/stamps/geometry-2d/ai/intent-builders/draw-shape.ts
|
|
8034
10058
|
var buildDrawShape = (s, intent) => {
|
|
8035
10059
|
const labels = intent.labels;
|
|
@@ -8302,6 +10326,25 @@ var buildPointAtDistance = (s, intent) => {
|
|
|
8302
10326
|
addPoint2(s, { name: intent.name, kind: "pointAtDistance", from: c.from, through: c.through, distance: c.distance });
|
|
8303
10327
|
};
|
|
8304
10328
|
|
|
10329
|
+
// src/stamps/geometry-2d/ai/intent-builders/add-point/commonTangentPoint.ts
|
|
10330
|
+
var buildCommonTangentPoint = (s, intent) => {
|
|
10331
|
+
const c = intent.constraint;
|
|
10332
|
+
if (c.kind !== "commonTangentPoint") return;
|
|
10333
|
+
const isCircle = (name) => {
|
|
10334
|
+
const sh = s.shapes.find((x) => x.name === name);
|
|
10335
|
+
return !!sh && CIRCLE_KINDS.has(sh.kind);
|
|
10336
|
+
};
|
|
10337
|
+
if (!isCircle(c.circles[0]) || !isCircle(c.circles[1])) return;
|
|
10338
|
+
addPoint2(s, {
|
|
10339
|
+
name: intent.name,
|
|
10340
|
+
kind: "commonTangentPoint",
|
|
10341
|
+
circles: [c.circles[0], c.circles[1]],
|
|
10342
|
+
on: c.on,
|
|
10343
|
+
variant: c.variant,
|
|
10344
|
+
side: c.side
|
|
10345
|
+
});
|
|
10346
|
+
};
|
|
10347
|
+
|
|
8305
10348
|
// src/stamps/geometry-2d/ai/intent-builders/add-point/externalToCircle.ts
|
|
8306
10349
|
var buildExternalToCircle = (s, intent) => {
|
|
8307
10350
|
const c = intent.constraint;
|
|
@@ -8371,6 +10414,7 @@ var ADD_POINT_BUILDERS = {
|
|
|
8371
10414
|
reflectPoint: buildReflectPoint,
|
|
8372
10415
|
reflectLine: buildReflectLine,
|
|
8373
10416
|
pointAtDistance: buildPointAtDistance,
|
|
10417
|
+
commonTangentPoint: buildCommonTangentPoint,
|
|
8374
10418
|
externalToCircle: buildExternalToCircle,
|
|
8375
10419
|
onCircle: buildOnCircle,
|
|
8376
10420
|
mixtilinearPoint: buildMixtilinearPoint
|
|
@@ -8995,6 +11039,8 @@ function constraintKey(c) {
|
|
|
8995
11039
|
return `externalToCircle:${c.circle}`;
|
|
8996
11040
|
case "onCircle":
|
|
8997
11041
|
return `onCircle:${c.circle}:${c.theta ?? ""}`;
|
|
11042
|
+
case "commonTangentPoint":
|
|
11043
|
+
return `commonTangentPoint:${c.circles.join(",")}:${c.on}:${c.variant}:${c.side}`;
|
|
8998
11044
|
}
|
|
8999
11045
|
}
|
|
9000
11046
|
function samePrefix(a, b, depth) {
|
|
@@ -9022,11 +11068,11 @@ function verifyGeometry(intents, dsl) {
|
|
|
9022
11068
|
const other2 = apex === "A" ? pC : apex === "B" ? pC : pB;
|
|
9023
11069
|
const v1 = [other1[0] - apexP[0], other1[1] - apexP[1]];
|
|
9024
11070
|
const v2 = [other2[0] - apexP[0], other2[1] - apexP[1]];
|
|
9025
|
-
const
|
|
9026
|
-
if (Math.abs(
|
|
11071
|
+
const dot2 = v1[0] * v2[0] + v1[1] * v2[1];
|
|
11072
|
+
if (Math.abs(dot2) > 1e-6) {
|
|
9027
11073
|
issues.push({
|
|
9028
11074
|
axis: "wrong",
|
|
9029
|
-
detail: `triangle ${A}${B}${C} variant=right-at-${apex} kh\xF4ng vu\xF4ng (dot=${
|
|
11075
|
+
detail: `triangle ${A}${B}${C} variant=right-at-${apex} kh\xF4ng vu\xF4ng (dot=${dot2.toFixed(3)})`
|
|
9030
11076
|
});
|
|
9031
11077
|
}
|
|
9032
11078
|
}
|
|
@@ -9074,7 +11120,7 @@ function buildAndTranspile(intents) {
|
|
|
9074
11120
|
return second.ok ? second : first;
|
|
9075
11121
|
}
|
|
9076
11122
|
function tryDeterministicFigure(rawProblem) {
|
|
9077
|
-
const problem = normalizeProblemText(rawProblem);
|
|
11123
|
+
const problem = normalizeProblemText(correctUserInput(rawProblem));
|
|
9078
11124
|
const det = runDeterministicIntents(problem);
|
|
9079
11125
|
if (!det.ok) return { ok: false, reason: det.reason, coverage: det.coverage };
|
|
9080
11126
|
const built = buildAndTranspile(
|
|
@@ -9149,7 +11195,7 @@ function describeDeterministicMiss(miss) {
|
|
|
9149
11195
|
// src/stamps/geometry-2d/ai/deterministic/partialFigure.ts
|
|
9150
11196
|
var MAX_SALVAGE_ITERS = 6;
|
|
9151
11197
|
function tryPartialFigure(rawProblem) {
|
|
9152
|
-
const problem = normalizeProblemText(rawProblem);
|
|
11198
|
+
const problem = normalizeProblemText(correctUserInput(rawProblem));
|
|
9153
11199
|
const part = tryPartialDeterministic(problem);
|
|
9154
11200
|
if (part.detIntents.length === 0) return null;
|
|
9155
11201
|
const intents = resolveCircleNameCollisions(normalizeIntents(part.detIntents, problem));
|
|
@@ -9533,6 +11579,16 @@ var AddPointIntentZ = z.object({
|
|
|
9533
11579
|
z.object({ kind: z.literal("segmentLength"), p1: LabelZ, p2: LabelZ, scale: z.number().positive().optional(), offset: z.number().optional() }),
|
|
9534
11580
|
z.object({ kind: z.literal("literal"), value: z.number().positive(), scale: z.number().positive().optional(), offset: z.number().optional() })
|
|
9535
11581
|
])
|
|
11582
|
+
}),
|
|
11583
|
+
// Tiếp điểm tiếp tuyến CHUNG 2 đường tròn (spec mục A). circles = 2 tên đtròn;
|
|
11584
|
+
// on = tiếp điểm trên đtròn 0 hay 1; variant = ngoài/trong; side = chọn 1 trong
|
|
11585
|
+
// 2 tiếp tuyến cùng loại.
|
|
11586
|
+
z.object({
|
|
11587
|
+
kind: z.literal("commonTangentPoint"),
|
|
11588
|
+
circles: z.tuple([LabelZ, LabelZ]),
|
|
11589
|
+
on: z.union([z.literal(0), z.literal(1)]),
|
|
11590
|
+
variant: z.enum(["external", "internal"]),
|
|
11591
|
+
side: z.union([z.literal(0), z.literal(1)])
|
|
9536
11592
|
})
|
|
9537
11593
|
])
|
|
9538
11594
|
});
|
|
@@ -9583,8 +11639,8 @@ var DrawLineIntentZ = z.object({
|
|
|
9583
11639
|
});
|
|
9584
11640
|
var MarkShapeIntentZ = z.object({
|
|
9585
11641
|
op: z.literal("mark-shape"),
|
|
9586
|
-
shape: z.enum(["triangle", "quadrilateral"]),
|
|
9587
|
-
labels: z.array(LabelZ).min(3).max(
|
|
11642
|
+
shape: z.enum(["triangle", "quadrilateral", "polygon"]),
|
|
11643
|
+
labels: z.array(LabelZ).min(3).max(8)
|
|
9588
11644
|
});
|
|
9589
11645
|
var IntentZ = z.discriminatedUnion("op", [
|
|
9590
11646
|
DrawShapeIntentZ,
|
|
@@ -9896,7 +11952,7 @@ ho\u1EB7c
|
|
|
9896
11952
|
- quadrilateral: any
|
|
9897
11953
|
|
|
9898
11954
|
## Constraint kinds (cho add-point)
|
|
9899
|
-
midpoint, perpFoot, centroid, circumcenter, incenter, orthocenter, intersection, onSegment, free, secondIntersection, circleIntersection, tangencyPoint, tangentPoint, angleBisectorFoot, externalAngleBisectorFoot, arcMidpoint, reflectPoint, reflectLine, excenter, rightAngleViewing, pointAtDistance
|
|
11955
|
+
midpoint, perpFoot, centroid, circumcenter, incenter, orthocenter, intersection, onSegment, free, secondIntersection, circleIntersection, tangencyPoint, tangentPoint, angleBisectorFoot, externalAngleBisectorFoot, arcMidpoint, reflectPoint, reflectLine, excenter, rightAngleViewing, pointAtDistance, commonTangentPoint
|
|
9900
11956
|
|
|
9901
11957
|
## Style enum (cho connect)
|
|
9902
11958
|
segment, line, ray, perpBisector
|