@jjlmoya/utils-tabletop 1.27.0 → 1.28.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.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -0
  3. package/src/entries.ts +2 -0
  4. package/src/tests/locale_completeness.test.ts +1 -1
  5. package/src/tests/tool_validation.test.ts +1 -1
  6. package/src/tool/mus-scoreboard/bibliography.astro +16 -0
  7. package/src/tool/mus-scoreboard/bibliography.ts +12 -0
  8. package/src/tool/mus-scoreboard/component.astro +169 -0
  9. package/src/tool/mus-scoreboard/controller.ts +178 -0
  10. package/src/tool/mus-scoreboard/dom-views.ts +87 -0
  11. package/src/tool/mus-scoreboard/entry.ts +27 -0
  12. package/src/tool/mus-scoreboard/evaluator.ts +27 -0
  13. package/src/tool/mus-scoreboard/i18n/de.ts +187 -0
  14. package/src/tool/mus-scoreboard/i18n/en.ts +187 -0
  15. package/src/tool/mus-scoreboard/i18n/es.ts +187 -0
  16. package/src/tool/mus-scoreboard/i18n/fr.ts +187 -0
  17. package/src/tool/mus-scoreboard/i18n/id.ts +187 -0
  18. package/src/tool/mus-scoreboard/i18n/it.ts +187 -0
  19. package/src/tool/mus-scoreboard/i18n/ja.ts +187 -0
  20. package/src/tool/mus-scoreboard/i18n/ko.ts +187 -0
  21. package/src/tool/mus-scoreboard/i18n/nl.ts +187 -0
  22. package/src/tool/mus-scoreboard/i18n/pl.ts +187 -0
  23. package/src/tool/mus-scoreboard/i18n/pt.ts +187 -0
  24. package/src/tool/mus-scoreboard/i18n/ru.ts +187 -0
  25. package/src/tool/mus-scoreboard/i18n/sv.ts +187 -0
  26. package/src/tool/mus-scoreboard/i18n/tr.ts +187 -0
  27. package/src/tool/mus-scoreboard/i18n/zh.ts +187 -0
  28. package/src/tool/mus-scoreboard/index.ts +11 -0
  29. package/src/tool/mus-scoreboard/logic.test.ts +82 -0
  30. package/src/tool/mus-scoreboard/logic.ts +122 -0
  31. package/src/tool/mus-scoreboard/online-mus-scoreboard.css +740 -0
  32. package/src/tool/mus-scoreboard/seo.astro +16 -0
  33. package/src/tool/mus-scoreboard/storage.ts +29 -0
  34. package/src/tool/mus-scoreboard/ui.ts +63 -0
  35. package/src/tools.ts +2 -0
@@ -0,0 +1,187 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
2
+ import { bibliography } from '../bibliography';
3
+ import type { MusScoreboardLocaleContent, MusScoreboardUI } from '../entry';
4
+
5
+ const ui: MusScoreboardUI = {
6
+ onboarding: 'Namnge de två paren, ställ in bordets regler och tryck på stenarna när händerna räknas.',
7
+ pairOneLabel: 'Par ett',
8
+ pairTwoLabel: 'Par två',
9
+ pairOneDefault: 'Bord Norr',
10
+ pairTwoDefault: 'Bord Söder',
11
+ targetPointsLabel: 'Poäng per spel',
12
+ targetPointsHint: 'Välj målpoäng för varje spel (30, 40 eller 50 poäng).',
13
+ gamesToWinLabel: 'Spel för att vinna vaca',
14
+ gamesToWinHint: 'Det första paret som når detta antal vinner matchen.',
15
+ deckLabel: 'Bordets kortlek',
16
+ deckHint: 'Spansk kortlek (Baraja española) är det traditionella valet. För fransk kortlek tar du bort 8:or, 9:or och 10:or.',
17
+ spanishDeck: 'Baraja española (Spansk kortlek)',
18
+ frenchDeck: 'Fransk kortlek',
19
+ frenchDeckNotice: 'Glöm inte att ta bort 8:or, 9:or och 10:or från den franska kortleken för att spela med 40 kort. Knektar är Sotas, Damer är Caballos och Kungar är Reyes.',
20
+ pointsOption30: '30 poäng',
21
+ pointsOption40: '40 poäng',
22
+ pointsOption50: '50 poäng',
23
+ gamesOption1: '1 spel',
24
+ gamesOption2: '2 spel',
25
+ gamesOption3: '3 spel',
26
+ startMatch: 'Öppna poängräknaren',
27
+ matchSettings: 'Ställ in par och regler',
28
+ liveMatch: 'Pågående match',
29
+ targetLabel: 'poäng per spel',
30
+ gameLabel: 'spel',
31
+ gamePlural: 'spel',
32
+ vacaLabel: 'spel för vinst',
33
+ scoreLabel: 'Aktuella stenar',
34
+ handLabel: 'Välj vilket par som är hand (Mano)',
35
+ handSelected: 'är hand (Mano).',
36
+ stonesLabel: 'Poängställning',
37
+ stoneSingular: 'sten (piedra)',
38
+ stonePlural: 'stenar (piedras)',
39
+ amarracoSingular: 'amarraco',
40
+ amarracoPlural: 'amarracos',
41
+ pointsToGo: 'kvar till mål',
42
+ addStone: 'sten',
43
+ addAmarraco: 'amarraco',
44
+ closeGame: 'Stäng spel',
45
+ closeGamePrompt: 'Bekräfta att detta par har vunnit spelet och starta nästa?',
46
+ confirmationTitle: 'Bekräfta åtgärd',
47
+ confirmAction: 'Bekräfta',
48
+ cancelAction: 'Fortsätt spela',
49
+ undo: 'Ångra',
50
+ resetMatch: 'Återställ match',
51
+ resetMatchPrompt: 'Återställa den aktuella matchen och rensa poänghistoriken?',
52
+ historyTitle: 'Senaste noteringar',
53
+ noHistory: 'Inga noteringar än.',
54
+ matchReady: 'Bordet är redo.',
55
+ closeGameHint: 'Ett par har nått målpoängen. Bekräfta vinnaren av spelet.',
56
+ waitingForScore: 'Räkna vidare. Återstående poäng för ledarna:',
57
+ gameWon: 'Spel avslutat.',
58
+ vacaWon: 'Vaca vunnen! Starta en ny match när bordet är redo.',
59
+ gameClosed: 'stängde spelet.',
60
+ pointsAdded: 'skrapade ihop',
61
+ matchSaved: 'Poäng sparad på denna enhet.',
62
+ lastAction: 'Senaste åtgärd',
63
+ settingsApplied: 'Nya regler tillämpade. Matchen återställdes.',
64
+ pairNameRequired: 'Ge båda paren ett namn innan du öppnar poängräknaren.',
65
+ visualScoreLabel: 'Mus-poängräknare för bordet med stenar, amarracos och spel',
66
+ };
67
+
68
+ const faq = [
69
+ {
70
+ question: 'Vad håller denna online Mus-poängräknare reda på?',
71
+ answer: 'Den håller reda på två par, stenar (enstaka poäng), amarracos (5-poängsblock), vunna spel (chicos), vaca, vem som är hand (Mano) och poänghistorik.',
72
+ },
73
+ {
74
+ question: 'Varför visas stenar och amarracos separat?',
75
+ answer: 'En sten (Piedra) motsvarar 1 poäng och en Amarraco motsvarar 5 stenar. Att visa dem separat gör det enkelt att jämföra med fysiska märkmarker på bordet.',
76
+ },
77
+ {
78
+ question: 'Kan man spela till 30, 40 eller 50 poäng?',
79
+ answer: 'Ja. Välj 30, 40 eller 50 poäng per spel innan du öppnar räknaren.',
80
+ },
81
+ {
82
+ question: 'Hur avslutas ett färdigt spel?',
83
+ answer: 'När ett par når målpoängen aktiveras knappen för att stänga spelet. Efter bekräftelse läggs 1 spel till och stenarna nollställs.',
84
+ },
85
+ {
86
+ question: 'Sparas matchen om sidan laddas om?',
87
+ answer: 'Ja. Inställningar, parnamn, stenar och vunna spel sparas lokalt i webbläsaren.',
88
+ },
89
+ {
90
+ question: 'Tvingar poängräknaren på lokala Mus regler?',
91
+ answer: 'Nej. Mus har många regionala varianter. Denna digitala räknare fokuserar på exakt poängräkning vid bordet.',
92
+ },
93
+ {
94
+ question: 'Hur anpassar man en fransk kortlek för Mus?',
95
+ answer: 'Ta bort 8:or, 9:or och 10:or från den franska kortleken så att 40 kort återstår. Knektar är Sotas, Damer är Caballos och Kungar är Reyes.',
96
+ },
97
+ ];
98
+
99
+ const howTo = [
100
+ {
101
+ name: 'Ange parens namn',
102
+ text: 'Fyll i namnen på de två paren för att enkelt identifiera dem vid bordet.',
103
+ },
104
+ {
105
+ name: 'Ställ in mål och vaca',
106
+ text: 'Välj 30, 40 eller 50 poäng per spel samt antal vunna spel som krävs för vaca.',
107
+ },
108
+ {
109
+ name: 'Räkna poäng och amarracos',
110
+ text: 'Tryck på sten (+1) eller amarraco (+5) när poäng delas ut i rundorna.',
111
+ },
112
+ {
113
+ name: 'Bekräfta spelet och nollställ stenar',
114
+ text: 'Bekräfta vinnaren när målpoängen nås för att registrera spelet och återställa stenarna.',
115
+ },
116
+ ];
117
+
118
+ const faqSchema: WithContext<FAQPage> = {
119
+ '@context': 'https://schema.org',
120
+ '@type': 'FAQPage',
121
+ mainEntity: faq.map((item) => ({
122
+ '@type': 'Question',
123
+ name: item.question,
124
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
125
+ })),
126
+ };
127
+
128
+ const appSchema: WithContext<SoftwareApplication> = {
129
+ '@context': 'https://schema.org',
130
+ '@type': 'SoftwareApplication',
131
+ name: 'Online Mus Poängräknare',
132
+ operatingSystem: 'All',
133
+ applicationCategory: 'GameApplication',
134
+ description: 'Digital poängräknare för det traditionella spanska kortspelet Mus.',
135
+ };
136
+
137
+ const howToSchema: WithContext<HowTo> = {
138
+ '@context': 'https://schema.org',
139
+ '@type': 'HowTo',
140
+ name: 'Hur du räknar poäng i kortspelet Mus',
141
+ step: howTo.map((item) => ({
142
+ '@type': 'HowToStep',
143
+ name: item.name,
144
+ text: item.text,
145
+ })),
146
+ };
147
+
148
+ const schemas: WithContext<SoftwareApplication | FAQPage | HowTo>[] = [
149
+ faqSchema,
150
+ appSchema,
151
+ howToSchema,
152
+ ];
153
+
154
+ export const content: MusScoreboardLocaleContent = {
155
+ slug: 'online-mus-poangraknare',
156
+ title: 'Online Mus Poängräknare för Live Matcher',
157
+ description: 'Räkna stenar, amarracos, spel och vacas i det spanska kortspelet Mus. Anpassat för spanska och franska kortlekar.',
158
+ ui,
159
+ seo: [
160
+ { type: 'title', text: 'Tydlig och Snabb Poängräkning för dina Mus Matcher', level: 2 },
161
+ { type: 'paragraph', html: 'Mus är ett traditonellt spanskt kortspel fullt av dynamik, taktik, bluffar och spänning. Eftersom poängen räknas snabbt efter varje runda (Grande, Chica, Pares, Juego eller Punto) förhindrar denna digitala räknare felräkningar och diskussioner vid bordet.' },
162
+ { type: 'title', text: 'Välj Målpoäng Innan Första Handen', level: 2 },
163
+ { type: 'paragraph', html: 'Klassiska matcher spelas traditionellt till 40 poäng, men det går även bra att spela till 30 eller 50 poäng. Ställ in målet innan ni delar ut första handen så att räknaren och knapparna för matchavslut stämmer exakt med bordets regler.' },
164
+ {
165
+ type: 'table',
166
+ headers: ['Inställning', 'Effekt', 'När den används'],
167
+ rows: [
168
+ ['30 poäng', 'Kort spel', 'Snabba matcher, turneringar eller lokala regler'],
169
+ ['40 poäng', 'Standardspel', 'Klassisk traditionell match med vänner'],
170
+ ['50 poäng', 'Långt spel', 'Matcher med mer utrymme för strategi och djup'],
171
+ ],
172
+ },
173
+ { type: 'tip', title: 'Stäm av Reglerna vid Bordet', html: 'Kom överens om regeln med 8 kungar och 8 ess (där 3:or fungerar som kungar och 2:or som ess) och vem som ansvarar för bordets fysiska marker eller bönor.' },
174
+ { type: 'title', text: 'Anpassning av Spansk och Fransk Kortlek', level: 2 },
175
+ { type: 'paragraph', html: 'Den spanska kortleken med 40 kort (Oros, Copas, Espadas, Bastos) är originalet för Mus. Med franska kort tar ni enkelt bort 8:or, 9:or och 10:or. Knektar är Sotas, Damer är Caballos och Kungar är Reyes.' },
176
+ { type: 'title', text: 'Digital Poängställning Direkt vid Bordet', level: 2 },
177
+ { type: 'paragraph', html: 'Det stora talet visar den totala poängställningen på ett tydligt sätt. Raden nedanför översätter automatiskt poängen till amarracos (5-poängsblock) och enskilda stenar för snabb avstämning.' },
178
+ { type: 'list', items: ['<strong>Sten (Piedra):</strong> 1 enskild poäng i matchens sammanställning.', '<strong>Amarraco:</strong> 1 märkmarkör eller block motsvarande 5 stenar.', '<strong>Spel (Chico):</strong> Vunnen runda när det avtalade målet nås.', '<strong>Vaca:</strong> Slutgiltig matchvinst vid uppnått antal vunna spel.'] },
179
+ { type: 'title', text: 'Avsluta Spel och Återställa Fel', level: 2 },
180
+ { type: 'paragraph', html: 'När målpoängen nås aktiveras knappen för att stänga spelet med en tydlig bekräftelse. Om en poäng trycks in felaktigt kan du använda Ångra knappen för att direkt återställa det senaste draget.' },
181
+ { type: 'tip', title: 'Använd Ångra vid Felräkning', html: 'Om du av misstag ger poäng till fel par trycker du direkt på Ångra för att rätta till poängställningen utan att förstöra historiken.' },
182
+ ],
183
+ faq,
184
+ bibliography,
185
+ howTo,
186
+ schemas,
187
+ };
@@ -0,0 +1,187 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
2
+ import { bibliography } from '../bibliography';
3
+ import type { MusScoreboardLocaleContent, MusScoreboardUI } from '../entry';
4
+
5
+ const ui: MusScoreboardUI = {
6
+ onboarding: 'İki çiftin adını yazın, masa kurallarını belirleyin ve puanlar sayıldıkça taşlara dokunun.',
7
+ pairOneLabel: 'Birinci çift',
8
+ pairTwoLabel: 'İkinci çift',
9
+ pairOneDefault: 'Kuzey Masası',
10
+ pairTwoDefault: 'Güney Masası',
11
+ targetPointsLabel: 'Oyun başına hedef puan',
12
+ targetPointsHint: 'Her oyun için bitiş çizgisini seçin (30, 40 veya 50 puan).',
13
+ gamesToWinLabel: 'Vaca kazanmak için gereken oyun',
14
+ gamesToWinHint: 'Bu sayıya ilk ulaşan çift maçı kazanır.',
15
+ deckLabel: 'Masa destesi',
16
+ deckHint: '40 kartlık İspanyol destesi (Baraja española) Mus için geleneksel seçimdir. Fransız destesi için 8, 9 ve 10\'ları çıkarın.',
17
+ spanishDeck: 'Baraja española (İspanyol destesi)',
18
+ frenchDeck: 'Fransız destesi (Standart)',
19
+ frenchDeckNotice: '40 kartla oynamak için Fransız destesinden 8, 9 ve 10\'ları çıkarmayı unutmayın. Vale Sota, Kız Caballo ve Papaz Rey yerine geçer.',
20
+ pointsOption30: '30 puan',
21
+ pointsOption40: '40 puan',
22
+ pointsOption50: '50 puan',
23
+ gamesOption1: '1 oyun',
24
+ gamesOption2: '2 oyun',
25
+ gamesOption3: '3 oyun',
26
+ startMatch: 'Skor tahtasını aç',
27
+ matchSettings: 'Çiftleri ve kuralları ayarla',
28
+ liveMatch: 'Canlı maç',
29
+ targetLabel: 'puanlık oyun',
30
+ gameLabel: 'oyun',
31
+ gamePlural: 'oyun',
32
+ vacaLabel: 'oyun kazanan galip',
33
+ scoreLabel: 'Mevcut taşlar',
34
+ handLabel: 'Hangi çiftin el sahibi (Mano) olduğunu seçin',
35
+ handSelected: 'el sahibi (Mano).',
36
+ stonesLabel: 'Puan sayacı',
37
+ stoneSingular: 'taş (piedra)',
38
+ stonePlural: 'taş (piedras)',
39
+ amarracoSingular: 'amarraco',
40
+ amarracoPlural: 'amarracos',
41
+ pointsToGo: 'puan kaldı',
42
+ addStone: 'taş',
43
+ addAmarraco: 'amarraco',
44
+ closeGame: 'Oyunu kapat',
45
+ closeGamePrompt: 'Bu çiftin oyunu kazandığını onaylayıp sonrakine geçilsin mi?',
46
+ confirmationTitle: 'Masa işlemini onayla',
47
+ confirmAction: 'Onayla',
48
+ cancelAction: 'Oynamaya devam et',
49
+ undo: 'Geri al',
50
+ resetMatch: 'Maçı sıfırla',
51
+ resetMatchPrompt: 'Mevcut maçı sıfırlayıp skor geçmişini temizlemek istiyor musunuz?',
52
+ historyTitle: 'Son sayımlar',
53
+ noHistory: 'Henüz sayı girilmedi.',
54
+ matchReady: 'Masa hazır.',
55
+ closeGameHint: 'Bir çift hedef puana ulaştı. Oyun kazananını onaylayın.',
56
+ waitingForScore: 'Saymaya devam edin. Liderin kalan puanı:',
57
+ gameWon: 'Oyun kapatıldı.',
58
+ vacaWon: 'Vaca kazanıldı! Masa hazır olduğunda yeni bir maç başlatın.',
59
+ gameClosed: 'oyunu kapattı.',
60
+ pointsAdded: 'skor elde etti:',
61
+ matchSaved: 'Skor bu cihaza kaydedildi.',
62
+ lastAction: 'Son işlem',
63
+ settingsApplied: 'Yeni kurallar uygulandı. Maç sıfırlandı.',
64
+ pairNameRequired: 'Skor tahtasını açmadan önce her iki çifte de bir isim verin.',
65
+ visualScoreLabel: 'Mus masa oyunları için taşlı ve amarracolu dijital skor tahtası',
66
+ };
67
+
68
+ const faq = [
69
+ {
70
+ question: 'Bu çevrimiçi Mus skor tahtası neleri takip eder?',
71
+ answer: 'İki çifti, taşları (tekli puanlar), amarracoları (5 puanlık bloklar), kazanılan oyunları (chicos), vaca galibiyetini, el sahipliğini (Mano) ve skor geçmişini takip eder.',
72
+ },
73
+ {
74
+ question: 'Taşlar ve amarracolar neden ayrı gösterilir?',
75
+ answer: 'Bir taş (Piedra) 1 puan, Amarraco ise 5 taş değerindedir. Ayrı gösterim masadaki gerçek pullar veya fasulyelerle kolayca eşleşmesini sağlar.',
76
+ },
77
+ {
78
+ question: '30, 40 veya 50 puanlık oyun oynanabilir mi?',
79
+ answer: 'Evet. Skor tahtasını açmadan önce oyun başına 30, 40 veya 50 puan hedefini seçebilirsiniz.',
80
+ },
81
+ {
82
+ question: 'Tamamlanan oyun nasıl kapatılır?',
83
+ answer: 'Bir çift hedef puana ulaştığında oyunu kapat düğmesi etkinleşir. Onaylandıktan sonra 1 oyun eklenir ve taşlar sıfırlanır.',
84
+ },
85
+ {
86
+ question: 'Sayfa yenilendiğinde maç verileri korunur mu?',
87
+ answer: 'Evet. Çift isimleri, taşlar ve kazanılan oyunlar cihazınızın tarayıcısında yerel olarak saklanır.',
88
+ },
89
+ {
90
+ question: 'Bu skor tahtası bölgesel Mus kurallarını zorunlu kılar mı?',
91
+ answer: 'Hayır. Mus oyununun birçok bölgesel kuralı vardır. Bu skor tahtası masadaki puanları doğru şekilde kaydetmeye odaklanır.',
92
+ },
93
+ {
94
+ question: 'Fransız destesi Mus için nasıl uyarlanır?',
95
+ answer: '8, 9 ve 10 numaralı kartları çıkararak 40 kart bırakın. Vale Sota, Kız Caballo, Papaz ise Rey olarak kabul edilir.',
96
+ },
97
+ ];
98
+
99
+ const howTo = [
100
+ {
101
+ name: 'Çift isimlerini girin',
102
+ text: 'Masadaki iki tarafın kolayca tanınması için oyuncu çiftlerinin isimlerini yazın.',
103
+ },
104
+ {
105
+ name: 'Hedef puanı ve vacayı belirleyin',
106
+ text: 'Oyun başına 30, 40 veya 50 puan ile vaca için gereken oyun sayısını seçin.',
107
+ },
108
+ {
109
+ name: 'Taş ve amarracoları sayın',
110
+ text: 'El sonlarında puanlar belirlendikçe taş (+1) veya amarraco (+5) ekleyin.',
111
+ },
112
+ {
113
+ name: 'Oyunu kapatın ve skoru güncelleyin',
114
+ text: 'Hedef puana ulaşıldığında kazananı onaylayarak oyunu kaydedin ve taşları sıfırlayın.',
115
+ },
116
+ ];
117
+
118
+ const faqSchema: WithContext<FAQPage> = {
119
+ '@context': 'https://schema.org',
120
+ '@type': 'FAQPage',
121
+ mainEntity: faq.map((item) => ({
122
+ '@type': 'Question',
123
+ name: item.question,
124
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
125
+ })),
126
+ };
127
+
128
+ const appSchema: WithContext<SoftwareApplication> = {
129
+ '@context': 'https://schema.org',
130
+ '@type': 'SoftwareApplication',
131
+ name: 'Çevrimiçi Mus Skor Tahtası',
132
+ operatingSystem: 'All',
133
+ applicationCategory: 'GameApplication',
134
+ description: 'Geleneksel İspanyol kart oyunu Mus için dijital canlı skor tahtası.',
135
+ };
136
+
137
+ const howToSchema: WithContext<HowTo> = {
138
+ '@context': 'https://schema.org',
139
+ '@type': 'HowTo',
140
+ name: 'Mus iskambil oyununda skor nasıl tutulur',
141
+ step: howTo.map((item) => ({
142
+ '@type': 'HowToStep',
143
+ name: item.name,
144
+ text: item.text,
145
+ })),
146
+ };
147
+
148
+ const schemas: WithContext<SoftwareApplication | FAQPage | HowTo>[] = [
149
+ faqSchema,
150
+ appSchema,
151
+ howToSchema,
152
+ ];
153
+
154
+ export const content: MusScoreboardLocaleContent = {
155
+ slug: 'cevrimici-mus-skor-tahtasi',
156
+ title: 'Canlı Maçlar İçin Çevrimiçi Mus Skor Tahtası',
157
+ description: 'Geleneksel İspanyol kart oyunu Mus için taşları, amarracoları ve vacaları canlı takip edin. 30, 40 ve 50 puan seçenekleriyle.',
158
+ ui,
159
+ seo: [
160
+ { type: 'title', text: 'Mus Masanız İçin Net ve Hızlı Skor Takibi', level: 2 },
161
+ { type: 'paragraph', html: 'Mus, İspanya\'nın en popüler ve köklü geleneksel iskambil oyunlarından biridir. Oyun dinamik, stratejik, heyecanlı ve blöf doludur. Her elde puanlar (Grande, Chica, Pares, Juego veya Punto) hızlıca sayıldığı için bu dijital sayaç masadaki anlaşmazlıkları ve karışıklıkları tamamen önler.' },
162
+ { type: 'title', text: 'İlk Elden Önce Bitiş Çizgisini Belirleyin', level: 2 },
163
+ { type: 'paragraph', html: 'Standart geleneksel oyun 40 puan olsa da, 30 veya 50 puanlık oyunlar da arkadaş grupları arasında sıkça tercih edilir. Dağıtımdan önce hedef puanı ayarlayın, böylece sayaç ve oyunu kapatma düğmeleri masanızın kurallarıyla tam olarak eşleşir.' },
164
+ {
165
+ type: 'table',
166
+ headers: ['Ayar', 'İşlev', 'Kullanım Önerisi'],
167
+ rows: [
168
+ ['30 puan', 'Kısa oyun', 'Hızlı maçlar, turnuva ön elemeleri veya yerel ev kuralları'],
169
+ ['40 puan', 'Standart oyun', 'Arkadaş ortamının geleneksel ve klasik kuralı'],
170
+ ['50 puan', 'Uzun oyun', 'Daha fazla strateji alanı ve oyun derinliği sunan maçlar'],
171
+ ],
172
+ },
173
+ { type: 'tip', title: 'Yerel Kuralları Bastan Konuşun', html: 'Kartları dağıtmadan önce 3\'lerin Papaz, 2\'lerin As sayıldığı 8 Papaz kuralının geçerli olup olmadığını ve masadaki fiziki pulları kimin tutacağını netleştirin.' },
174
+ { type: 'title', text: 'İspanyol ve Fransız Destesi Uyarlaması', level: 2 },
175
+ { type: 'paragraph', html: '40 kartlık geleneksel İspanyol destesi (Oros, Copas, Espadas, Bastos) oyunun aslıdır. Fransız destesinde 8, 9 ve 10\'lar çıkarılarak 40 kartla kolayca oynanabilir. Vale Sota, Kız Caballo ve Papaz Rey yerine geçer.' },
176
+ { type: 'title', text: 'Masa Kenarında Canlı Dijital Skor Takibi', level: 2 },
177
+ { type: 'paragraph', html: 'Büyük sayı toplam puanı anlaşılır bir şekilde gösterir. Altındaki satır bu rakamı otomatik olarak amarracolara (5\'lik bloklar) ve tekli taşlara dönüştürerek masadaki pullarla anında eşleştirir.' },
178
+ { type: 'list', items: ['<strong>Taş (Piedra):</strong> Maç sayımında 1 puanlık birim.', '<strong>Amarraco:</strong> 5 taşa denk gelen sayma pulu veya bloğu.', '<strong>Oyun (Chico):</strong> Hedef puana ulaşılarak kazanılan tur.', '<strong>Vaca:</strong> Belirlenen oyun sayısına ulaşıldığında alınan nihai maç galibiyeti.'] },
179
+ { type: 'title', text: 'Oyunu Kapatma ve Hata Düzeltme', level: 2 },
180
+ { type: 'paragraph', html: 'Hedef puana ulaşıldığında oyunu kapat düğmesi onay uyarısıyla birlikte aktif olur. Yanlış bir puan girilirse Geri al düğmesi ile son işlem anında geri alınabilir.' },
181
+ { type: 'tip', title: 'Hatalarda Geri Al Düğmesini Kullanın', html: 'Yanlış çifte puan eklendiğinde maç geçmişini bozmadan skoru düzeltmek için Geri al düğmesini kullanın.' },
182
+ ],
183
+ faq,
184
+ bibliography,
185
+ howTo,
186
+ schemas,
187
+ };
@@ -0,0 +1,187 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
2
+ import { bibliography } from '../bibliography';
3
+ import type { MusScoreboardLocaleContent, MusScoreboardUI } from '../entry';
4
+
5
+ const ui: MusScoreboardUI = {
6
+ onboarding: '输入两个对局组合的名字,设定本桌规则,在每手牌结算时点击加分。',
7
+ pairOneLabel: '第一组',
8
+ pairTwoLabel: '第二组',
9
+ pairOneDefault: '北桌',
10
+ pairTwoDefault: '南桌',
11
+ targetPointsLabel: '每局目标分数 (Chico)',
12
+ targetPointsHint: '选择每局比赛的决胜分数线 (30、40 或 50 分)。',
13
+ gamesToWinLabel: '赢得大比分 (Vaca) 所需局数',
14
+ gamesToWinHint: '首先达到该局数的组将赢得整场比赛。',
15
+ deckLabel: '桌面所用纸牌',
16
+ deckHint: '40张牌的西班牙纸牌 (Baraja española) 是 Mus 的传统选择;法式扑克牌也可调整后使用。',
17
+ spanishDeck: 'Baraja española (西班牙纸牌)',
18
+ frenchDeck: '法式扑克牌',
19
+ frenchDeckNotice: '使用法式扑克牌时,请抽出 8、9、10,保持 40 张牌。Jack (J) 相当于 Sota,Queen (Q) 相当于 Caballo,King (K) 相当于 Rey。',
20
+ pointsOption30: '30 分',
21
+ pointsOption40: '40 分',
22
+ pointsOption50: '50 分',
23
+ gamesOption1: '1 局',
24
+ gamesOption2: '2 局',
25
+ gamesOption3: '3 局',
26
+ startMatch: '开启记分板',
27
+ matchSettings: '设定组名与规则',
28
+ liveMatch: '实时对局',
29
+ targetLabel: '分局',
30
+ gameLabel: '局',
31
+ gamePlural: '局',
32
+ vacaLabel: '局先胜',
33
+ scoreLabel: '当前筹码 (Piedras)',
34
+ handLabel: '选择本手发牌顺位首位 (Mano)',
35
+ handSelected: '为发牌首位 (Mano)。',
36
+ stonesLabel: '记分筹码',
37
+ stoneSingular: '1分筹码 (Piedra)',
38
+ stonePlural: '1分筹码 (Piedras)',
39
+ amarracoSingular: '5分筹码 (Amarraco)',
40
+ amarracoPlural: '5分筹码 (Amarracos)',
41
+ pointsToGo: '分胜出',
42
+ addStone: '1分 (Piedra)',
43
+ addAmarraco: '5分 (Amarraco)',
44
+ closeGame: '结算本局 (Chico)',
45
+ closeGamePrompt: '确认该组赢得本局 (Chico) 并开始下一局?',
46
+ confirmationTitle: '确认操作',
47
+ confirmAction: '确认',
48
+ cancelAction: '继续比赛',
49
+ undo: '撤销',
50
+ resetMatch: '重置比赛',
51
+ resetMatchPrompt: '确定要重置当前整场比赛并清空记分历史吗?',
52
+ historyTitle: '近期记分记录',
53
+ noHistory: '暂无记分记录。',
54
+ matchReady: '棋牌桌已就绪。',
55
+ closeGameHint: '已有组达到目标分数。请确认本局获胜方。',
56
+ waitingForScore: '继续记分。领先组距离目标尚差:',
57
+ gameWon: '本局结束。',
58
+ vacaWon: '获得大比分胜利 (Vaca)!桌面就绪后可开启新比赛。',
59
+ gameClosed: '赢得了本局。',
60
+ pointsAdded: '获得',
61
+ matchSaved: '比分已保存在本设备中。',
62
+ lastAction: '最近操作',
63
+ settingsApplied: '新规则已生效。',
64
+ pairNameRequired: '请在开启记分板前为两组都输入名称。',
65
+ visualScoreLabel: 'Mus 对局专用数字记分板',
66
+ };
67
+
68
+ const faq = [
69
+ {
70
+ question: '这款在线 Mus 记分板可以记录哪些内容?',
71
+ answer: '可记录两组选手名称、单分筹码 (Piedras)、5分筹码 (Amarracos)、已胜局数 (Chicos)、大比分 (Vaca)、发牌首位 (Mano) 标记以及详细的近期记分历史。',
72
+ },
73
+ {
74
+ question: '为什么把 1分 (Piedra) 和 5分 (Amarraco) 分开显示?',
75
+ answer: '在西班牙 Mus 牌局中,1分称为 Piedra,5分集合称为 Amarraco。分开显示能够方便数字记分板与桌面上实物筹码(如蚕豆或代币)进行直观对照。',
76
+ },
77
+ {
78
+ question: '支持 30 分、40 分或 50 分制吗?',
79
+ answer: '支持。在开启记分板前,您可以自由选择每局 30 分、40 分或 50 分的目标分。',
80
+ },
81
+ {
82
+ question: '一局结束后如何进行结算?',
83
+ answer: '当有一组达到目标分数时,结算本局按钮会亮起。确认获胜方后,系统将为其增加 1 局胜利,并将筹码分数重置为零。',
84
+ },
85
+ {
86
+ question: '断网或刷新页面后数据会丢失吗?',
87
+ answer: '不会。所有比赛设置与分数均安全保存在本地浏览器中。',
88
+ },
89
+ {
90
+ question: '这款记分板会强制执行特定地区的细则吗?',
91
+ answer: '不会。记分板专注于精确记录叫分与比分,具体的明喊与暗号留给玩家在棋牌桌上进行。',
92
+ },
93
+ {
94
+ question: '能否使用普通法式扑克牌进行 Mus 游戏?',
95
+ answer: '可以。只需抽掉 8、9、10 保持 40 张牌。J 对应 Sota,Q 对应 Caballo,K 对应 Rey。在传统规则中,3 可当作 K,2 可当作 A。',
96
+ },
97
+ ];
98
+
99
+ const howTo = [
100
+ {
101
+ name: '输入两组选手名称',
102
+ text: '输入搭档组的名称(如北桌/南桌或选手姓名),以便记分员快速辨识。',
103
+ },
104
+ {
105
+ name: '设定目标分数与大比分 (Vaca) 局数',
106
+ text: '选择每局 30、40 或 50 分,并设定赢得大比分 (Vaca) 所需的局数。',
107
+ },
108
+ {
109
+ name: '按手牌结算记分',
110
+ text: '根据大牌 (Grande)、小牌 (Chica)、对子 (Pares)、点数 (Juego/Punto) 的决胜结果,点击 +1 分 (Piedra) 或 +5 分 (Amarraco) 筹码。',
111
+ },
112
+ {
113
+ name: '确认局胜利并重置分数',
114
+ text: '达到目标分数后确认本局胜者,记录局数并重置下一局筹码。',
115
+ },
116
+ ];
117
+
118
+ const faqSchema: WithContext<FAQPage> = {
119
+ '@context': 'https://schema.org',
120
+ '@type': 'FAQPage',
121
+ mainEntity: faq.map((item) => ({
122
+ '@type': 'Question',
123
+ name: item.question,
124
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
125
+ })),
126
+ };
127
+
128
+ const appSchema: WithContext<SoftwareApplication> = {
129
+ '@context': 'https://schema.org',
130
+ '@type': 'SoftwareApplication',
131
+ name: '在线 Mus 记分板',
132
+ operatingSystem: 'All',
133
+ applicationCategory: 'GameApplication',
134
+ description: '专为西班牙传统扑克游戏 Mus 打造的实战实时数字记分器。',
135
+ };
136
+
137
+ const howToSchema: WithContext<HowTo> = {
138
+ '@context': 'https://schema.org',
139
+ '@type': 'HowTo',
140
+ name: '如何在 Mus 纸牌对局中记分',
141
+ step: howTo.map((item) => ({
142
+ '@type': 'HowToStep',
143
+ name: item.name,
144
+ text: item.text,
145
+ })),
146
+ };
147
+
148
+ const schemas: WithContext<SoftwareApplication | FAQPage | HowTo>[] = [
149
+ faqSchema,
150
+ appSchema,
151
+ howToSchema,
152
+ ];
153
+
154
+ export const content: MusScoreboardLocaleContent = {
155
+ slug: 'online-mus-scoreboard',
156
+ title: '在线 Mus 记分板',
157
+ description: '实时记录西班牙 Mus 纸牌对局中的 Piedras (1分)、Amarracos (5分)、Chicos (局) 与 Vacas (大比分)。支持 30/40/50 分制。',
158
+ ui,
159
+ seo: [
160
+ { type: 'title', text: '为激烈的 Mus 纸牌对局提供清晰准确的数字记分', level: 2 },
161
+ { type: 'paragraph', html: 'Mus 是西班牙最具代表性的传统纸牌游戏,充满心理博弈与同伴默契。每手牌都会在多个环节(Grande、Chica、Pares、Juego、Punto)结算得分,使用数字记分板能有效杜绝计错分的情况。' },
162
+ { type: 'title', text: '在首手发牌前设定胜负目标', level: 2 },
163
+ { type: 'paragraph', html: '标准规则通常为 40 分一局,但在不同地区也流行 30 分或 50 分制。在发牌前设定好规则,让记分器与棋牌桌保持同步。' },
164
+ {
165
+ type: 'table',
166
+ headers: ['设定', '规则效果', '适用场景'],
167
+ rows: [
168
+ ['30 分', '短局制', '快速对局或锦标赛预赛'],
169
+ ['40 分', '标准局制', '最经典与通用的传统对局'],
170
+ ['50 分', '长局制', '更具博弈深度与容错率的高手较量'],
171
+ ],
172
+ },
173
+ { type: 'tip', title: '对局前明确本地规则', html: '在切牌前,请与对家确认是否采用 8张K与8张A(即3当作K、2当作A)的规则,并确定实物筹码保管人。' },
174
+ { type: 'title', text: '西班牙纸牌 (Baraja española) 与法式扑克牌的转换', level: 2 },
175
+ { type: 'paragraph', html: 'Mus 传统采用 40 张牌的西班牙纸牌 (Baraja española)。如果使用普通法式扑克牌,只需移除 8、9、10。J 对应 Sota,Q 对应 Caballo,K 对应 Rey。' },
176
+ { type: 'title', text: '实时对比数字比分与棋牌桌物理筹码', level: 2 },
177
+ { type: 'paragraph', html: '主数字显示当前总得分,下方一行自动换算为 Amarracos (5分筹码) 与单分 Piedras,极大方便了现场核对。' },
178
+ { type: 'list', items: ['<strong>Piedra:</strong> 单分筹码。', '<strong>Amarraco:</strong> 相当于 5 个 Piedra 的记分块。', '<strong>Chico:</strong> 达到目标分数赢下的单局。', '<strong>Vaca:</strong> 赢得预设局数后取得的整场胜利。'] },
179
+ { type: 'title', text: '结算单局胜利与误操作撤销', level: 2 },
180
+ { type: 'paragraph', html: '当得分达到目标后,结算按钮亮起。若点错记分,可点击撤销按钮即刻还原。' },
181
+ { type: 'tip', title: '善用撤销功能修正误记', html: '在喊分记错时,立即点击撤销按钮即可恢复至上一计分状态。' },
182
+ ],
183
+ faq,
184
+ bibliography,
185
+ howTo,
186
+ schemas,
187
+ };
@@ -0,0 +1,11 @@
1
+ import type { ToolDefinition } from '../../types';
2
+ import { musScoreboard } from './entry';
3
+
4
+ export * from './entry';
5
+
6
+ export const MUS_SCOREBOARD_TOOL: ToolDefinition = {
7
+ entry: musScoreboard,
8
+ Component: () => import('./component.astro'),
9
+ SEOComponent: () => import('./seo.astro'),
10
+ BibliographyComponent: () => import('./bibliography.astro'),
11
+ };
@@ -0,0 +1,82 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ addPoints,
4
+ canCloseGame,
5
+ closeGame,
6
+ createMusState,
7
+ decomposeScore,
8
+ getMatchWinner,
9
+ isGameReadyToClose,
10
+ remainingPoints,
11
+ setHand,
12
+ undoLastAction,
13
+ updateConfig,
14
+ } from './logic';
15
+
16
+ describe('mus scoreboard logic', () => {
17
+ it('creates a clean match with the supplied rules', () => {
18
+ const state = createMusState({ pairNames: ['North', 'South'], targetPoints: 30, gamesToWin: 3, deck: 'spanish' });
19
+ expect(state.points).toEqual([0, 0]);
20
+ expect(state.games).toEqual([0, 0]);
21
+ expect(state.hand).toBe(0);
22
+ expect(state.config.targetPoints).toBe(30);
23
+ });
24
+
25
+ it('adds stones and amarracos to one pair', () => {
26
+ const state = addPoints(addPoints(createMusState(), 0, 5), 1, 1);
27
+ expect(state.points).toEqual([5, 1]);
28
+ expect(state.history).toHaveLength(2);
29
+ });
30
+
31
+ it('decomposes a score into amarracos and stones', () => {
32
+ expect(decomposeScore(17)).toEqual({ amarracos: 3, stones: 2 });
33
+ expect(decomposeScore(0)).toEqual({ amarracos: 0, stones: 0 });
34
+ });
35
+
36
+ it('allows closing only when a pair reaches the target', () => {
37
+ const state = addPoints(createMusState(), 0, 5);
38
+ expect(canCloseGame(state, 0)).toBe(false);
39
+ expect(isGameReadyToClose(state)).toBe(false);
40
+ const ready = { ...state, points: [40, 12] as [number, number] };
41
+ expect(canCloseGame(ready, 0)).toBe(true);
42
+ expect(isGameReadyToClose(ready)).toBe(true);
43
+ });
44
+
45
+ it('closes a game, resets points and records the winner', () => {
46
+ const ready = { ...createMusState(), points: [40, 22] as [number, number] };
47
+ const closed = closeGame(ready, 0);
48
+ expect(closed.points).toEqual([0, 0]);
49
+ expect(closed.games).toEqual([1, 0]);
50
+ expect(closed.history.at(-1)?.action).toBe('Close game');
51
+ });
52
+
53
+ it('does not close a game for the wrong pair or after the match is won', () => {
54
+ const ready = { ...createMusState({ pairNames: ['A', 'B'], targetPoints: 30, gamesToWin: 1, deck: 'spanish' }), points: [30, 20] as [number, number] };
55
+ expect(closeGame(ready, 1)).toEqual(ready);
56
+ const matchWon = closeGame(ready, 0);
57
+ expect(getMatchWinner(matchWon)).toBe(0);
58
+ expect(closeGame({ ...matchWon, points: [30, 30] as [number, number] }, 1).games).toEqual([1, 0]);
59
+ });
60
+
61
+ it('undoes the latest score and then returns to a clean match', () => {
62
+ const first = addPoints(createMusState(), 0, 5);
63
+ const second = addPoints(first, 1, 1);
64
+ expect(undoLastAction(second).points).toEqual([5, 0]);
65
+ expect(undoLastAction(undoLastAction(second)).points).toEqual([0, 0]);
66
+ });
67
+
68
+ it('moves mano between pairs and includes it in undo history', () => {
69
+ const moved = setHand(createMusState(), 1);
70
+ expect(moved.hand).toBe(1);
71
+ expect(moved.history.at(-1)?.action).toBe('Hand');
72
+ expect(undoLastAction(moved).hand).toBe(0);
73
+ });
74
+
75
+ it('calculates remaining points and resets when settings change', () => {
76
+ const state = addPoints(createMusState(), 0, 5);
77
+ expect(remainingPoints(state, 0)).toBe(35);
78
+ const changed = updateConfig(state, { pairNames: ['A', 'B'], targetPoints: 50, gamesToWin: 1, deck: 'french' });
79
+ expect(changed.points).toEqual([0, 0]);
80
+ expect(changed.config.targetPoints).toBe(50);
81
+ });
82
+ });