@jjlmoya/utils-tabletop 1.3.0 → 1.5.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/package.json +1 -1
- package/src/category/TabletopCategorySEO.astro +2 -7
- package/src/category/i18n/en.ts +4 -4
- package/src/category/i18n/ru.ts +2 -2
- package/src/category/i18n/zh.ts +8 -8
- package/src/category/index.ts +2 -1
- package/src/entries.ts +14 -1
- package/src/index.ts +1 -0
- package/src/pages/[locale]/[slug].astro +7 -6
- package/src/tests/locale_completeness.test.ts +1 -1
- package/src/tests/no_em_dash.test.ts +47 -0
- package/src/tests/seo_length.test.ts +1 -0
- package/src/tests/title_quality.test.ts +1 -1
- package/src/tests/tool_validation.test.ts +1 -1
- package/src/tool/board-game-timer/modules/ColorHelper.ts +2 -2
- package/src/tool/board-game-timer/modules/DuelViewManager.ts +4 -4
- package/src/tool/board-game-timer/modules/GameRunner.ts +1 -1
- package/src/tool/board-game-timer/modules/MultiplayerViewManager.ts +2 -2
- package/src/tool/board-game-timer/modules/SetupPanelManager.ts +1 -1
- package/src/tool/board-game-timer/modules/TimerEngine.ts +1 -1
- package/src/tool/board-game-timer/types.ts +1 -1
- package/src/tool/decision-wheel/audio.ts +78 -0
- package/src/tool/decision-wheel/bibliography.astro +16 -0
- package/src/tool/decision-wheel/bibliography.ts +8 -0
- package/src/tool/decision-wheel/client.ts +226 -0
- package/src/tool/decision-wheel/component.astro +48 -0
- package/src/tool/decision-wheel/components/SegmentEditor.astro +22 -0
- package/src/tool/decision-wheel/components/SpinHistory.astro +17 -0
- package/src/tool/decision-wheel/components/SpinResult.astro +13 -0
- package/src/tool/decision-wheel/components/WheelCanvas.astro +23 -0
- package/src/tool/decision-wheel/decision-wheel.css +632 -0
- package/src/tool/decision-wheel/drawing.ts +144 -0
- package/src/tool/decision-wheel/entry.ts +48 -0
- package/src/tool/decision-wheel/i18n/de.ts +97 -0
- package/src/tool/decision-wheel/i18n/en.ts +251 -0
- package/src/tool/decision-wheel/i18n/es.ts +93 -0
- package/src/tool/decision-wheel/i18n/fr.ts +93 -0
- package/src/tool/decision-wheel/i18n/id.ts +93 -0
- package/src/tool/decision-wheel/i18n/it.ts +93 -0
- package/src/tool/decision-wheel/i18n/ja.ts +93 -0
- package/src/tool/decision-wheel/i18n/ko.ts +93 -0
- package/src/tool/decision-wheel/i18n/nl.ts +93 -0
- package/src/tool/decision-wheel/i18n/pl.ts +93 -0
- package/src/tool/decision-wheel/i18n/pt.ts +93 -0
- package/src/tool/decision-wheel/i18n/ru.ts +75 -0
- package/src/tool/decision-wheel/i18n/sv.ts +93 -0
- package/src/tool/decision-wheel/i18n/tr.ts +93 -0
- package/src/tool/decision-wheel/i18n/zh.ts +93 -0
- package/src/tool/decision-wheel/index.ts +9 -0
- package/src/tool/decision-wheel/logic.test.ts +163 -0
- package/src/tool/decision-wheel/logic.ts +145 -0
- package/src/tool/decision-wheel/seo.astro +16 -0
- package/src/tool/decision-wheel/types.ts +14 -0
- package/src/tool/decision-wheel/ui.ts +121 -0
- package/src/tool/dice-roller-simulator/client.ts +2 -2
- package/src/tool/dice-roller-simulator/i18n/ru.ts +8 -8
- package/src/tool/dice-roller-simulator/modules/history.ts +1 -1
- package/src/tool/fantasy-runes-translator/bibliography.astro +16 -0
- package/src/tool/fantasy-runes-translator/bibliography.ts +16 -0
- package/src/tool/fantasy-runes-translator/client.ts +205 -0
- package/src/tool/fantasy-runes-translator/component.astro +95 -0
- package/src/tool/fantasy-runes-translator/entry.ts +56 -0
- package/src/tool/fantasy-runes-translator/fantasy-runes-translator.css +712 -0
- package/src/tool/fantasy-runes-translator/i18n/de.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/en.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/es.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/fr.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/id.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/it.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/ja.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/ko.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/nl.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/pl.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/pt.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/ru.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/sv.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/tr.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/zh.ts +139 -0
- package/src/tool/fantasy-runes-translator/index.ts +9 -0
- package/src/tool/fantasy-runes-translator/logic.ts +174 -0
- package/src/tool/fantasy-runes-translator/seo.astro +16 -0
- package/src/tool/fantasy-runes-translator/types.ts +26 -0
- package/src/tool/first-player-selector/bibliography.astro +16 -0
- package/src/tool/first-player-selector/bibliography.ts +12 -0
- package/src/tool/first-player-selector/client.ts +197 -0
- package/src/tool/first-player-selector/component.astro +77 -0
- package/src/tool/first-player-selector/entry.ts +42 -0
- package/src/tool/first-player-selector/first-player-selector.css +303 -0
- package/src/tool/first-player-selector/i18n/de.ts +226 -0
- package/src/tool/first-player-selector/i18n/en.ts +226 -0
- package/src/tool/first-player-selector/i18n/es.ts +226 -0
- package/src/tool/first-player-selector/i18n/fr.ts +227 -0
- package/src/tool/first-player-selector/i18n/id.ts +226 -0
- package/src/tool/first-player-selector/i18n/it.ts +227 -0
- package/src/tool/first-player-selector/i18n/ja.ts +227 -0
- package/src/tool/first-player-selector/i18n/ko.ts +227 -0
- package/src/tool/first-player-selector/i18n/nl.ts +226 -0
- package/src/tool/first-player-selector/i18n/pl.ts +227 -0
- package/src/tool/first-player-selector/i18n/pt.ts +226 -0
- package/src/tool/first-player-selector/i18n/ru.ts +227 -0
- package/src/tool/first-player-selector/i18n/sv.ts +226 -0
- package/src/tool/first-player-selector/i18n/tr.ts +226 -0
- package/src/tool/first-player-selector/i18n/zh.ts +227 -0
- package/src/tool/first-player-selector/index.ts +9 -0
- package/src/tool/first-player-selector/modules/AnimationEffects.ts +256 -0
- package/src/tool/first-player-selector/modules/SelectionEngine.ts +249 -0
- package/src/tool/first-player-selector/modules/SoundEffects.ts +87 -0
- package/src/tool/first-player-selector/modules/TouchTracker.ts +148 -0
- package/src/tool/first-player-selector/seo.astro +16 -0
- package/src/tool/first-player-selector/types.ts +21 -0
- package/src/tool/rpg-initiative-tracker/bibliography.astro +16 -0
- package/src/tool/rpg-initiative-tracker/bibliography.ts +12 -0
- package/src/tool/rpg-initiative-tracker/client.ts +201 -0
- package/src/tool/rpg-initiative-tracker/component.astro +23 -0
- package/src/tool/rpg-initiative-tracker/components/AddForm.astro +49 -0
- package/src/tool/rpg-initiative-tracker/components/CombatantList.astro +16 -0
- package/src/tool/rpg-initiative-tracker/components/TurnControls.astro +28 -0
- package/src/tool/rpg-initiative-tracker/entry.ts +50 -0
- package/src/tool/rpg-initiative-tracker/i18n/de.ts +266 -0
- package/src/tool/rpg-initiative-tracker/i18n/en.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/es.ts +264 -0
- package/src/tool/rpg-initiative-tracker/i18n/fr.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/id.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/it.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/ja.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/ko.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/nl.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/pl.ts +264 -0
- package/src/tool/rpg-initiative-tracker/i18n/pt.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/ru.ts +264 -0
- package/src/tool/rpg-initiative-tracker/i18n/sv.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/tr.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/zh.ts +263 -0
- package/src/tool/rpg-initiative-tracker/index.ts +9 -0
- package/src/tool/rpg-initiative-tracker/logic.test.ts +121 -0
- package/src/tool/rpg-initiative-tracker/logic.ts +122 -0
- package/src/tool/rpg-initiative-tracker/rpg-initiative-tracker.css +617 -0
- package/src/tool/rpg-initiative-tracker/seo.astro +16 -0
- package/src/tool/rpg-initiative-tracker/types.ts +15 -0
- package/src/tool/score-tracker/bibliography.astro +16 -0
- package/src/tool/score-tracker/bibliography.ts +12 -0
- package/src/tool/score-tracker/client.ts +248 -0
- package/src/tool/score-tracker/component.astro +34 -0
- package/src/tool/score-tracker/components/Leaderboard.astro +19 -0
- package/src/tool/score-tracker/components/PlayerSetup.astro +32 -0
- package/src/tool/score-tracker/components/RoundScoring.astro +22 -0
- package/src/tool/score-tracker/components/ScoreHistory.astro +25 -0
- package/src/tool/score-tracker/entry.ts +57 -0
- package/src/tool/score-tracker/i18n/de.ts +278 -0
- package/src/tool/score-tracker/i18n/en.ts +278 -0
- package/src/tool/score-tracker/i18n/es.ts +251 -0
- package/src/tool/score-tracker/i18n/fr.ts +278 -0
- package/src/tool/score-tracker/i18n/id.ts +278 -0
- package/src/tool/score-tracker/i18n/it.ts +278 -0
- package/src/tool/score-tracker/i18n/ja.ts +278 -0
- package/src/tool/score-tracker/i18n/ko.ts +278 -0
- package/src/tool/score-tracker/i18n/nl.ts +278 -0
- package/src/tool/score-tracker/i18n/pl.ts +278 -0
- package/src/tool/score-tracker/i18n/pt.ts +278 -0
- package/src/tool/score-tracker/i18n/ru.ts +278 -0
- package/src/tool/score-tracker/i18n/sv.ts +278 -0
- package/src/tool/score-tracker/i18n/tr.ts +278 -0
- package/src/tool/score-tracker/i18n/zh.ts +278 -0
- package/src/tool/score-tracker/index.ts +9 -0
- package/src/tool/score-tracker/logic.test.ts +83 -0
- package/src/tool/score-tracker/logic.ts +122 -0
- package/src/tool/score-tracker/score-tracker.css +1029 -0
- package/src/tool/score-tracker/seo.astro +16 -0
- package/src/tool/score-tracker/types.ts +17 -0
- package/src/tools.ts +10 -0
- package/src/types.ts +1 -2
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { bibliography } from '../bibliography';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import type { ScoreTrackerUI } from '../entry';
|
|
4
|
+
|
|
5
|
+
const ui: ScoreTrackerUI = {
|
|
6
|
+
title: 'Score Tracker',
|
|
7
|
+
addPlayer: '添加玩家',
|
|
8
|
+
removePlayer: '移除',
|
|
9
|
+
renamePlayer: '重命名',
|
|
10
|
+
startRound: '开始第1轮',
|
|
11
|
+
nextRound: '下一轮',
|
|
12
|
+
resetGame: '重置游戏',
|
|
13
|
+
undoRound: '撤销上一轮',
|
|
14
|
+
playerLabel: '玩家',
|
|
15
|
+
scoreLabel: '分数',
|
|
16
|
+
roundLabel: '轮次',
|
|
17
|
+
totalLabel: '总计',
|
|
18
|
+
rankLabel: '排名',
|
|
19
|
+
leaderboardTitle: '排行榜',
|
|
20
|
+
historyTitle: '分数历史',
|
|
21
|
+
noPlayersHint: '添加玩家以开始计分',
|
|
22
|
+
playerNamePlaceholder: '玩家姓名',
|
|
23
|
+
confirmLabel: '确认',
|
|
24
|
+
cancelLabel: '取消',
|
|
25
|
+
deleteRoundConfirm: '删除最后一轮?',
|
|
26
|
+
playerAdded: '玩家已添加',
|
|
27
|
+
playerRemoved: '玩家已移除',
|
|
28
|
+
roundStarted: '本轮已开始',
|
|
29
|
+
gameReset: '游戏已重置',
|
|
30
|
+
noRoundsYet: '尚无记录轮次',
|
|
31
|
+
currentRoundLabel: '当前轮次',
|
|
32
|
+
scoringTitle: '轮次计分',
|
|
33
|
+
resetAllLabel: '重置全部',
|
|
34
|
+
confirmResetAll: '这将删除所有玩家和分数。你确定吗?',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const content: ToolLocaleContent<ScoreTrackerUI> = {
|
|
38
|
+
slug: 'score-tracker',
|
|
39
|
+
title: 'Score Tracker: Duan Ren Ji Fen Qi He Bang Dan Guan Li Qi',
|
|
40
|
+
description: '追踪任何桌面游戏的分数。添加玩家,逐轮记录分数,并观看排行榜通过自动排名实时更新。',
|
|
41
|
+
ui,
|
|
42
|
+
seo: [
|
|
43
|
+
{ type: 'title', text: '桌面游戏计分:如何无误地追踪分数', level: 2 },
|
|
44
|
+
{ type: 'paragraph', html: '在桌面游戏中计分看似简单,直到你玩到第五轮时才发现有人算错了。无论你玩的是 Wingspan、Terraforming Mars、Catan 还是任何胜利点游戏,准确的计分是公平游戏与争论之间的分水岭。数字计分器消除了算术错误,显示实时排名,并保留每一轮的完整历史记录,以便你验证最终总数。' },
|
|
45
|
+
{
|
|
46
|
+
type: 'stats',
|
|
47
|
+
items: [
|
|
48
|
+
{ value: '无限', label: '支持的玩家数' },
|
|
49
|
+
{ value: '实时', label: '排行榜更新' },
|
|
50
|
+
{ value: '无限', label: '记录的轮次' },
|
|
51
|
+
],
|
|
52
|
+
columns: 3,
|
|
53
|
+
},
|
|
54
|
+
{ type: 'title', text: '常见计分错误及如何避免', level: 2 },
|
|
55
|
+
{ type: 'paragraph', html: '最常见的计分错误是忘记添加轮末加分或把一列数字加错。当您使用数字方式追踪分数时,累计总数会在每次输入后自动更新。这意味着您可以在数字看起来不对劲的瞬间发现错误,而不是在游戏结束时才发现,那时已经没人记得正确的数值了。轮次历史功能让您可以审核任何之前的轮次并修正错误,而无需重新开始整场游戏。' },
|
|
56
|
+
{
|
|
57
|
+
type: 'tip',
|
|
58
|
+
title: '快速计分技巧',
|
|
59
|
+
html: '每轮结束后立即输入分数,趁大家还记得结果。对于有隐藏角色的游戏,使用描述性标签(如颜色或派系名称)代替真实姓名。和孩子一起玩时,让他们自己按按钮,以保持他们对计分过程的参与感。',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: 'title',
|
|
63
|
+
text: '逐轮计分 vs 仅计总分',
|
|
64
|
+
level: 3,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: 'proscons',
|
|
68
|
+
title: '逐轮计分 vs 仅计最终分数',
|
|
69
|
+
items: [
|
|
70
|
+
{
|
|
71
|
+
pro: '您可以查看游戏每个阶段谁领先以及势头如何变化。',
|
|
72
|
+
con: '需要在每轮后输入数据,而不是只在结束时输入一次。',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
pro: '错误很容易定位,因为您可以将每轮的记录与记忆进行核对。',
|
|
76
|
+
con: '在游戏过程中会稍微多花一点时间。',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
pro: '玩家可以核对自己的轮次分数,减少争议并建立信任。',
|
|
80
|
+
con: '最好由一个人管理设备或玩家轮流操作。',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: 'title',
|
|
86
|
+
text: '具有不寻常计分系统的游戏',
|
|
87
|
+
level: 3,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
type: 'comparative',
|
|
91
|
+
items: [
|
|
92
|
+
{
|
|
93
|
+
title: '胜利点游戏',
|
|
94
|
+
description: '大多数德式游戏使用在游戏过程中获得的胜利点。挑战在于追踪多种 VP 来源:资源、成就、终局奖励。数字追踪器让您可以在获得分数时即时输入并查看累计总数。',
|
|
95
|
+
icon: 'mdi:chess-queen',
|
|
96
|
+
highlight: true,
|
|
97
|
+
points: [
|
|
98
|
+
'追踪多个计分类别',
|
|
99
|
+
'终局奖励计算',
|
|
100
|
+
'即时平局裁决',
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
title: '含负分计分的游戏',
|
|
105
|
+
description: '有些游戏会对玩家的特定行为处以负分惩罚。数字计分器能自然地处理负值,将其标红以便惩罚一目了然。这在吃墩游戏或带有惩罚机制的推理游戏中特别有用。',
|
|
106
|
+
icon: 'mdi:minus-circle-outline',
|
|
107
|
+
highlight: false,
|
|
108
|
+
points: [
|
|
109
|
+
'自动处理负值',
|
|
110
|
+
'可视化惩罚指示器',
|
|
111
|
+
'准确的累计总数',
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
title: '战役游戏',
|
|
116
|
+
description: '遗产和战役游戏会跨多次游戏会话继承分数。没有追踪器,您需要在游戏之夜之间保留纸质笔记。数字工具能让您的战役分数井井有条,并让您回顾自己的决定如何影响了整体排名。',
|
|
117
|
+
icon: 'mdi:book-open-variant',
|
|
118
|
+
highlight: false,
|
|
119
|
+
points: [
|
|
120
|
+
'持久性战役追踪',
|
|
121
|
+
'跨会话比较',
|
|
122
|
+
'逐会话细分',
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
columns: 3,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: 'title',
|
|
130
|
+
text: '计分术语表',
|
|
131
|
+
level: 3,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
type: 'glossary',
|
|
135
|
+
items: [
|
|
136
|
+
{
|
|
137
|
+
term: '胜利点(VP)',
|
|
138
|
+
definition: '大多数现代桌面游戏的标准计分单位。玩家通过行动获得 VP,游戏结束时拥有最多 VP 的人获胜。',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
term: '逐轮计分',
|
|
142
|
+
definition: '在每轮结束时(而非仅在游戏结束时)记录分数。这样可以创建详细的历史记录,并使错误检查变得简单。',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
term: '排行榜',
|
|
146
|
+
definition: '根据总分数显示每位玩家位置的实时排名。当输入新分数时,排行榜会自动重新排序。',
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
term: '负分计分',
|
|
150
|
+
definition: '玩家可能因惩罚或失败行动而失分,从而导致负总数的计分系统。数字追踪器无需手动减法即可处理此问题。',
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
type: 'diagnostic',
|
|
156
|
+
variant: 'info',
|
|
157
|
+
title: '如何处理有争议的分数',
|
|
158
|
+
icon: 'mdi:information-outline',
|
|
159
|
+
badge: '游戏提示',
|
|
160
|
+
html: '当玩家对分数提出质疑时,不要立即更改。首先检查轮次历史以确认输入的记录。如果录入有误,使用 +/- 按钮进行修正,总数会自动更新。如果轮次记录正确但玩家不同意,历史记录提供了每个人都可以一起查看的客观凭据。',
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
faq: [
|
|
164
|
+
{
|
|
165
|
+
question: '我可以追踪多少名玩家?',
|
|
166
|
+
answer: '没有限制。添加您的游戏支持的所有玩家,并在游戏过程中随时重命名他们。',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
question: '我能撤销错误吗?',
|
|
170
|
+
answer: '是的。使用「撤销上一轮」按钮移除最近的一轮分数,这将自动重新计算所有总数和排名。',
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
question: '计分器会保存我的数据吗?',
|
|
174
|
+
answer: '计分器完全在您的浏览器中运行。数据在当前会话期间持续保存。刷新页面将重置所有分数。',
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
question: '排行榜如何工作?',
|
|
178
|
+
answer: '排行榜按总分数降序排列玩家。当您输入或修改每轮的分数时,排名会自动更新。',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
question: '我可以追踪负分吗?',
|
|
182
|
+
answer: '是的。+/- 按钮允许正值和负值调整,使其适用于有惩罚分或扣分机制的游戏。',
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
bibliography,
|
|
186
|
+
howTo: [
|
|
187
|
+
{
|
|
188
|
+
name: '添加玩家',
|
|
189
|
+
text: '使用「添加玩家」输入框输入玩家姓名。您可以在游戏过程中随时添加或移除玩家。',
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: '开始计分',
|
|
193
|
+
text: '点击「开始轮次」开始一轮新的计分。使用 +/- 按钮调整每位玩家在该轮中的分数。',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: '查看排名',
|
|
197
|
+
text: '排行榜会自动更新。浏览「分数历史」选项卡以回顾之前的轮次记录。',
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
schemas: [
|
|
201
|
+
{
|
|
202
|
+
'@context': 'https://schema.org',
|
|
203
|
+
'@type': 'SoftwareApplication',
|
|
204
|
+
'name': 'Score Tracker & Scorekeeper',
|
|
205
|
+
'operatingSystem': 'All',
|
|
206
|
+
'applicationCategory': 'UtilitiesApplication',
|
|
207
|
+
'browserRequirements': 'Requires HTML5. Requires JavaScript.',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
'@context': 'https://schema.org',
|
|
211
|
+
'@type': 'FAQPage',
|
|
212
|
+
'mainEntity': [
|
|
213
|
+
{
|
|
214
|
+
'@type': 'Question',
|
|
215
|
+
'name': '我可以追踪多少名玩家?',
|
|
216
|
+
'acceptedAnswer': {
|
|
217
|
+
'@type': 'Answer',
|
|
218
|
+
'text': '没有限制。添加您的游戏支持的所有玩家,并在游戏过程中随时重命名他们。',
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
'@type': 'Question',
|
|
223
|
+
'name': '我能撤销错误吗?',
|
|
224
|
+
'acceptedAnswer': {
|
|
225
|
+
'@type': 'Answer',
|
|
226
|
+
'text': '是的。使用「撤销上一轮」按钮移除最近的一轮分数,这将自动重新计算所有总数和排名。',
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
'@type': 'Question',
|
|
231
|
+
'name': '计分器会保存我的数据吗?',
|
|
232
|
+
'acceptedAnswer': {
|
|
233
|
+
'@type': 'Answer',
|
|
234
|
+
'text': '计分器完全在您的浏览器中运行。数据在当前会话期间持续保存。刷新页面将重置所有分数。',
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
'@type': 'Question',
|
|
239
|
+
'name': '排行榜如何工作?',
|
|
240
|
+
'acceptedAnswer': {
|
|
241
|
+
'@type': 'Answer',
|
|
242
|
+
'text': '排行榜按总分数降序排列玩家。当您输入或修改每轮的分数时,排名会自动更新。',
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
'@type': 'Question',
|
|
247
|
+
'name': '我可以追踪负分吗?',
|
|
248
|
+
'acceptedAnswer': {
|
|
249
|
+
'@type': 'Answer',
|
|
250
|
+
'text': '是的。+/- 按钮允许正值和负值调整,使其适用于有惩罚分或扣分机制的游戏。',
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
'@context': 'https://schema.org',
|
|
257
|
+
'@type': 'HowTo',
|
|
258
|
+
'name': '如何使用计分器',
|
|
259
|
+
'step': [
|
|
260
|
+
{
|
|
261
|
+
'@type': 'HowToStep',
|
|
262
|
+
'name': '添加玩家',
|
|
263
|
+
'text': '使用「添加玩家」输入框输入玩家姓名。您可以在游戏过程中随时添加或移除玩家。',
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
'@type': 'HowToStep',
|
|
267
|
+
'name': '开始计分',
|
|
268
|
+
'text': '点击「开始轮次」开始一轮新的计分。使用 +/- 按钮调整每位玩家在该轮中的分数。',
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
'@type': 'HowToStep',
|
|
272
|
+
'name': '查看排名',
|
|
273
|
+
'text': '排行榜会自动更新。浏览「分数历史」选项卡以回顾之前的轮次记录。',
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ToolDefinition } from '../../types';
|
|
2
|
+
import { scoreTracker } from './entry';
|
|
3
|
+
export * from './entry';
|
|
4
|
+
export const SCORE_TRACKER_TOOL: ToolDefinition = {
|
|
5
|
+
entry: scoreTracker,
|
|
6
|
+
Component: () => import('./component.astro'),
|
|
7
|
+
SEOComponent: () => import('./seo.astro'),
|
|
8
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
9
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
createScoreState,
|
|
4
|
+
addPlayer,
|
|
5
|
+
removePlayer,
|
|
6
|
+
renamePlayer,
|
|
7
|
+
startNewRound,
|
|
8
|
+
updateRoundScore,
|
|
9
|
+
deleteLastRound,
|
|
10
|
+
resetGame,
|
|
11
|
+
} from './logic';
|
|
12
|
+
|
|
13
|
+
describe('score-tracker logic', () => {
|
|
14
|
+
it('creates an initial empty state', () => {
|
|
15
|
+
const state = createScoreState();
|
|
16
|
+
expect(state.players).toEqual([]);
|
|
17
|
+
expect(state.rounds).toEqual([]);
|
|
18
|
+
expect(state.currentRound).toBe(0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('adds a player', () => {
|
|
22
|
+
const state = addPlayer(createScoreState(), 'Alice');
|
|
23
|
+
expect(state.players).toHaveLength(1);
|
|
24
|
+
expect(state.players[0].name).toBe('Alice');
|
|
25
|
+
expect(state.players[0].total).toBe(0);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('removes a player', () => {
|
|
29
|
+
const s1 = addPlayer(createScoreState(), 'Alice');
|
|
30
|
+
const s2 = addPlayer(s1, 'Bob');
|
|
31
|
+
const s3 = removePlayer(s2, s2.players[0].id);
|
|
32
|
+
expect(s3.players).toHaveLength(1);
|
|
33
|
+
expect(s3.players[0].name).toBe('Bob');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('renames a player', () => {
|
|
37
|
+
const s1 = addPlayer(createScoreState(), 'Alice');
|
|
38
|
+
const s2 = renamePlayer(s1, s1.players[0].id, 'Alice B.');
|
|
39
|
+
expect(s2.players[0].name).toBe('Alice B.');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('starts a new round', () => {
|
|
43
|
+
const s1 = addPlayer(createScoreState(), 'Alice');
|
|
44
|
+
const s2 = addPlayer(s1, 'Bob');
|
|
45
|
+
const s3 = startNewRound(s2);
|
|
46
|
+
expect(s3.rounds).toHaveLength(1);
|
|
47
|
+
expect(s3.currentRound).toBe(1);
|
|
48
|
+
expect(s3.rounds[0].scores[s2.players[0].id]).toBe(0);
|
|
49
|
+
expect(s3.rounds[0].scores[s2.players[1].id]).toBe(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('updates round score and recalculates totals', () => {
|
|
53
|
+
let s = addPlayer(createScoreState(), 'Alice');
|
|
54
|
+
s = addPlayer(s, 'Bob');
|
|
55
|
+
s = startNewRound(s);
|
|
56
|
+
s = updateRoundScore(s, 0, s.players[0].id, 5);
|
|
57
|
+
expect(s.rounds[0].scores[s.players[0].id]).toBe(5);
|
|
58
|
+
expect(s.players[0].total).toBe(5);
|
|
59
|
+
expect(s.players[1].total).toBe(0);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('deletes the last round', () => {
|
|
63
|
+
let s = addPlayer(createScoreState(), 'Alice');
|
|
64
|
+
s = startNewRound(s);
|
|
65
|
+
s = updateRoundScore(s, 0, s.players[0].id, 5);
|
|
66
|
+
expect(s.players[0].total).toBe(5);
|
|
67
|
+
s = deleteLastRound(s);
|
|
68
|
+
expect(s.rounds).toHaveLength(0);
|
|
69
|
+
expect(s.currentRound).toBe(0);
|
|
70
|
+
expect(s.players[0].total).toBe(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('resets the game while keeping players', () => {
|
|
74
|
+
let s = addPlayer(createScoreState(), 'Alice');
|
|
75
|
+
s = startNewRound(s);
|
|
76
|
+
s = updateRoundScore(s, 0, s.players[0].id, 5);
|
|
77
|
+
s = resetGame(s);
|
|
78
|
+
expect(s.rounds).toHaveLength(0);
|
|
79
|
+
expect(s.currentRound).toBe(0);
|
|
80
|
+
expect(s.players).toHaveLength(1);
|
|
81
|
+
expect(s.players[0].total).toBe(0);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { PlayerScore, ScoreState } from './types';
|
|
2
|
+
|
|
3
|
+
export function createScoreState(): ScoreState {
|
|
4
|
+
return {
|
|
5
|
+
players: [],
|
|
6
|
+
rounds: [],
|
|
7
|
+
currentRound: 0,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createBlankState(): ScoreState {
|
|
12
|
+
return { players: [], rounds: [], currentRound: 0 };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function addPlayer(state: ScoreState, name: string): ScoreState {
|
|
16
|
+
const id = 'player_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6);
|
|
17
|
+
const player: PlayerScore = {
|
|
18
|
+
id,
|
|
19
|
+
name,
|
|
20
|
+
rounds: [],
|
|
21
|
+
total: 0,
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
...state,
|
|
25
|
+
players: [...state.players, player],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function removePlayer(state: ScoreState, playerId: string): ScoreState {
|
|
30
|
+
return {
|
|
31
|
+
...state,
|
|
32
|
+
players: state.players.filter(p => p.id !== playerId),
|
|
33
|
+
rounds: state.rounds.map(r => {
|
|
34
|
+
const newScores = { ...r.scores };
|
|
35
|
+
delete newScores[playerId];
|
|
36
|
+
return { ...r, scores: newScores };
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function renamePlayer(state: ScoreState, playerId: string, newName: string): ScoreState {
|
|
42
|
+
return {
|
|
43
|
+
...state,
|
|
44
|
+
players: state.players.map(p =>
|
|
45
|
+
p.id === playerId ? { ...p, name: newName } : p
|
|
46
|
+
),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function startNewRound(state: ScoreState): ScoreState {
|
|
51
|
+
const roundNumber = state.rounds.length + 1;
|
|
52
|
+
const scores: Record<string, number> = {};
|
|
53
|
+
state.players.forEach(p => { scores[p.id] = 0; });
|
|
54
|
+
return {
|
|
55
|
+
...state,
|
|
56
|
+
currentRound: roundNumber,
|
|
57
|
+
rounds: [...state.rounds, { round: roundNumber, scores }],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function updateRoundScore(
|
|
62
|
+
state: ScoreState,
|
|
63
|
+
roundIndex: number,
|
|
64
|
+
playerId: string,
|
|
65
|
+
delta: number
|
|
66
|
+
): ScoreState {
|
|
67
|
+
const round = state.rounds[roundIndex];
|
|
68
|
+
if (!round) return state;
|
|
69
|
+
const newScores = {
|
|
70
|
+
...round.scores,
|
|
71
|
+
[playerId]: (round.scores[playerId] || 0) + delta,
|
|
72
|
+
};
|
|
73
|
+
const newRounds = state.rounds.map((r, i) =>
|
|
74
|
+
i === roundIndex ? { ...r, scores: newScores } : r
|
|
75
|
+
);
|
|
76
|
+
const newPlayers = state.players.map(p => {
|
|
77
|
+
if (p.id !== playerId) return p;
|
|
78
|
+
const allRoundScores = newRounds.map(r => r.scores[p.id] || 0);
|
|
79
|
+
return {
|
|
80
|
+
...p,
|
|
81
|
+
rounds: allRoundScores,
|
|
82
|
+
total: allRoundScores.reduce((a, b) => a + b, 0),
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
...state,
|
|
87
|
+
players: newPlayers,
|
|
88
|
+
rounds: newRounds,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function deleteLastRound(state: ScoreState): ScoreState {
|
|
93
|
+
if (state.rounds.length === 0) return state;
|
|
94
|
+
const newRounds = state.rounds.slice(0, -1);
|
|
95
|
+
const newPlayers = state.players.map(p => {
|
|
96
|
+
const allRoundScores = newRounds.map(r => r.scores[p.id] || 0);
|
|
97
|
+
return {
|
|
98
|
+
...p,
|
|
99
|
+
rounds: allRoundScores,
|
|
100
|
+
total: allRoundScores.reduce((a, b) => a + b, 0),
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
...state,
|
|
105
|
+
players: newPlayers,
|
|
106
|
+
rounds: newRounds,
|
|
107
|
+
currentRound: newRounds.length,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function resetGame(state: ScoreState): ScoreState {
|
|
112
|
+
return {
|
|
113
|
+
...state,
|
|
114
|
+
rounds: [],
|
|
115
|
+
currentRound: 0,
|
|
116
|
+
players: state.players.map(p => ({
|
|
117
|
+
...p,
|
|
118
|
+
rounds: [],
|
|
119
|
+
total: 0,
|
|
120
|
+
})),
|
|
121
|
+
};
|
|
122
|
+
}
|