Skip to content

Commit e3b37c7

Browse files
authored
✨ feat: implement strict mode (#57)
* ✨ feat: implement strict mode (win by 2 points after 10-10) * 🐞 fix: lang data not loading for s-checkbox * 🦄 refactor: move strict mode UI logic to ui.js * ✨ feat: serve rule and rotational serves logic for strict mode * ✨ feat: enhance match point logic for strict mode handling * ✨ feat: match history table but with s-table * ✨ feat: center align game board tab * 🦄 refactor: replace strict mode checkbox with switch * 🦄 refactor: remove data-lang-key attribute in updateElementLanguages function * 🦄 refactor: simplify strict mode event handling
1 parent 2714b6b commit e3b37c7

File tree

5 files changed

+147
-23
lines changed

5 files changed

+147
-23
lines changed

index.html

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,21 @@
101101
<div class="form-group">
102102
<!-- Sober已经有下拉输入框力,太好力! -->
103103
<s-picker class="dropdown-input" id="serveRule" data-lang="ui.setup.serveRule" data-lang-prop="label">
104-
<!-- None -->
105-
<s-picker-item data-lang="ui.setup.serveRule.none"></s-picker-item>
106-
<!-- In Turn -->
107-
<s-picker-item data-lang="ui.setup.serveRule.inTurn"></s-picker-item>
104+
<s-picker-item value="0" data-lang="ui.setup.serveRule.none"></s-picker-item>
105+
<s-picker-item value="1" data-lang="ui.setup.serveRule.inTurn"></s-picker-item>
108106
</s-picker>
109107
</div>
110108
<div class="form-group" id="rotationalServesGroup" hidden>
111109
<!-- Balls Each Turn -->
112110
<s-text-field id="rotationalServes" data-lang="ui.setup.rotationalServes" data-lang-prop="label" value="2"></s-text-field>
113111
</div>
112+
<div class="form-group">
113+
<!-- Strict Mode -->
114+
<div style="display: flex; align-items: center; gap: 8px;">
115+
<s-switch id="strictMode"></s-switch>
116+
<div data-lang="ui.setup.strictMode"></div>
117+
</div>
118+
</div>
114119
</div>
115120
<div id="setup-list">
116121
<div class="player-list" id="playerListGroup">
@@ -125,7 +130,7 @@ <h2 id="playerListTitle" data-lang="ui.setup.playerListHint"></h2>
125130

126131
<div id="gameBoardWrapper">
127132
<!-- Tab控制 -->
128-
<s-tab class="game-board" id="gameBoardSelector">
133+
<s-tab class="game-board" id="gameBoardSelector" style="justify-content: center">
129134
<s-tab-item selected="true">
130135
<div slot="text" data-lang="ui.gameBoard.tab.score"></div>
131136
</s-tab-item>
@@ -161,7 +166,18 @@ <h2 data-lang="ui.gameBoard.playerScores"></h2>
161166
<div id="board-history">
162167
<div id="matchHistory">
163168
<h2 data-lang="ui.gameBoard.matchHistory"></h2>
164-
<ul id="historyList"></ul>
169+
<s-table id="historyTable" style="overflow: auto; display: block; width: 60%">
170+
<s-thead>
171+
<s-tr>
172+
<s-th data-lang="ui.gameBoard.matchHistory.match"></s-th>
173+
<s-th data-lang="ui.gameBoard.matchHistory.score"></s-th>
174+
<s-th data-lang="ui.gameBoard.matchHistory.winner"></s-th>
175+
</s-tr>
176+
</s-thead>
177+
<s-tbody>
178+
<!-- Rows will be populated by JS -->
179+
</s-tbody>
180+
</s-table>
165181
</div>
166182
</div>
167183
<!-- Tab:信息 -->

src/lang.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const languages = {
2424
'ui.setup.serveRule.none': 'None',
2525
'ui.setup.serveRule.inTurn': 'In Turn',
2626
'ui.setup.rotationalServes': 'Rotational Serves Number',
27+
'ui.setup.strictMode': 'Strict Mode',
2728
'ui.setup.playerListHint': 'Players:',
2829
'ui.setup.startGame': 'Start Game',
2930
'ui.gameBoard.tab.score': 'Score',
@@ -48,6 +49,9 @@ export const languages = {
4849
'ui.tooltip.rotationalServesError': 'Please enter a valid number of rotational serves number',
4950
'ui.tooltip.repoTip': 'Issues and PRs are welcome!!!',
5051
'ui.tooltip.noScoreToUndo': 'No score to undo',
52+
'ui.gameBoard.matchHistory.match': 'Match',
53+
'ui.gameBoard.matchHistory.score': 'Score',
54+
'ui.gameBoard.matchHistory.winner': 'Winner',
5155
},
5256
'zh-CN': {
5357
'language.LanguageName': '简体中文',
@@ -67,6 +71,7 @@ export const languages = {
6771
'ui.setup.serveRule.none': '无',
6872
'ui.setup.serveRule.inTurn': '轮流',
6973
'ui.setup.rotationalServes': '轮换发球数',
74+
'ui.setup.strictMode': '严格模式',
7075
'ui.setup.playerListHint': '玩家:',
7176
'ui.setup.startGame': '开始比赛',
7277
'ui.gameBoard.tab.score': '计分',
@@ -91,6 +96,9 @@ export const languages = {
9196
'ui.tooltip.rotationalServesError': '请填入有效的轮换发球数',
9297
'ui.tooltip.repoTip': '欢迎 Issue 和 PR!!!',
9398
'ui.tooltip.noScoreToUndo': '没有可撤销的分数',
99+
'ui.gameBoard.matchHistory.match': '比赛',
100+
'ui.gameBoard.matchHistory.score': '比分',
101+
'ui.gameBoard.matchHistory.winner': '获胜者',
94102
},
95103
'zh-TW': {
96104
'language.LanguageName': '繁體中文',
@@ -110,6 +118,7 @@ export const languages = {
110118
'ui.setup.serveRule.none': '無',
111119
'ui.setup.serveRule.inTurn': '輪流',
112120
'ui.setup.rotationalServes': '輪換發球數',
121+
'ui.setup.strictMode': '嚴格模式',
113122
'ui.setup.playerListHint': '玩家:',
114123
'ui.setup.startGame': '開始比賽',
115124
'ui.gameBoard.tab.score': '計分',
@@ -134,6 +143,9 @@ export const languages = {
134143
'ui.tooltip.rotationalServesError': '請輸入有效的輪換發球數',
135144
'ui.tooltip.repoTip': '歡迎提交 Issue 和 PR!!!',
136145
'ui.tooltip.noScoreToUndo': '沒有可撤銷的分數',
146+
'ui.gameBoard.matchHistory.match': '比賽',
147+
'ui.gameBoard.matchHistory.score': '比分',
148+
'ui.gameBoard.matchHistory.winner': '獲勝者',
137149
},
138150
'ja-JP': {
139151
'language.LanguageName': '日本語',
@@ -153,6 +165,7 @@ export const languages = {
153165
'ui.setup.serveRule.none': 'なし',
154166
'ui.setup.serveRule.inTurn': '順番',
155167
'ui.setup.rotationalServes': 'サーブ交代数',
168+
'ui.setup.strictMode': '厳格モード',
156169
'ui.setup.playerListHint': 'プレイヤー:',
157170
'ui.setup.startGame': 'ゲーム開始',
158171
'ui.gameBoard.tab.score': 'スコア',
@@ -177,8 +190,11 @@ export const languages = {
177190
'ui.tooltip.rotationalServesError': '有効なサーブ交代数を入力してください',
178191
'ui.tooltip.repoTip': 'IssueやPRをお待ちしています!!!',
179192
'ui.tooltip.noScoreToUndo': '取り消すスコアはありません',
193+
'ui.gameBoard.matchHistory.match': '試合',
194+
'ui.gameBoard.matchHistory.score': 'スコア',
195+
'ui.gameBoard.matchHistory.winner': '勝者',
180196
},
181-
'el-GR': {
197+
'el-GR': {
182198
'language.LanguageName': 'Ελληνικά',
183199
'ui.title': 'Μετρητής Σκορ Πινγκ-Πονγκ',
184200
'ui.theme.themeName.auto': 'Αυτόματο',
@@ -196,6 +212,7 @@ export const languages = {
196212
'ui.setup.serveRule.none': 'Κανένα',
197213
'ui.setup.serveRule.inTurn': 'Με σειρά',
198214
'ui.setup.rotationalServes': 'Αριθμός περιστροφικών σερβίς',
215+
'ui.setup.strictMode': 'Συμπεριφορά Συμπαγούς',
199216
'ui.setup.playerListHint': 'Παίκτες:',
200217
'ui.setup.startGame': 'Εκκίνηση',
201218
'ui.gameBoard.tab.score': 'Σκορ',
@@ -220,6 +237,9 @@ export const languages = {
220237
'ui.tooltip.rotationalServesError': 'Παρακαλώ εισάγετε εναν εγκυρο αριθμό περιστροφικών σερβις',
221238
'ui.tooltip.repoTip': 'Για κάθε πρόβλημα ή PRs επικοινωνήστε μαζί μας!!!',
222239
'ui.tooltip.noScoreToUndo': 'Δεν υπάρχει σκορ για αναίρεση',
240+
'ui.gameBoard.matchHistory.match': 'Αγώνας',
241+
'ui.gameBoard.matchHistory.score': 'Σκορ',
242+
'ui.gameBoard.matchHistory.winner': 'Νικητής',
223243
},
224244
};
225245
/**

src/listeners.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { addPlayer, addWinBalls, minusWinBalls, startGame, undoLastScore } from './script';
2-
import { e_boardSelectChange, e_gotoGitHub, e_load, e_reloadPage, e_toggleTheme } from './ui';
2+
import { e_boardSelectChange, e_gotoGitHub, e_load, e_reloadPage, e_strictModeChange, e_toggleTheme } from './ui';
33

44
export function listen() {
55
document.addEventListener('DOMContentLoaded', () => {
@@ -25,4 +25,6 @@ export function registerListeners() {
2525

2626
geb('gameBoardSelector').addEventListener('change', e_boardSelectChange);
2727
geb('undoButton').addEventListener('click', undoLastScore);
28+
29+
geb('strictMode').addEventListener('change', e_strictModeChange);
2830
}

src/script.js

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,9 @@ export function startGame() {
6666
hideLoading();
6767
return;
6868
}
69+
const strictMode = document.getElementById('strictMode').checked;
6970
serveRule = parseInt(document.getElementById('serveRule').selectedIndex);
70-
rotationalServes = parseInt(
71-
document.getElementById('rotationalServes').value
72-
);
71+
rotationalServes = parseInt(document.getElementById('rotationalServes').value);
7372
if (isNaN(serveRule) || serveRule < 0 || serveRule > 1) {
7473
showSnackBar(lang('ui.tooltip.serveRuleError'), 'ServeRuleError', 'error');
7574
hideLoading();
@@ -84,6 +83,10 @@ export function startGame() {
8483
hideLoading();
8584
return;
8685
}
86+
if (strictMode) {
87+
serveRule = 1;
88+
rotationalServes = 2;
89+
}
8790
if (serveRule === 1) {
8891
currentServe = 0;
8992
}
@@ -106,13 +109,31 @@ export function startNewMatch() {
106109
}
107110

108111
export function checkMatchPoint(playerName) {
109-
if (currentMatchScores[playerName] === winBalls - 1) {
110-
// Player is at match point
112+
const strictMode = document.getElementById('strictMode').checked;
113+
const playerScore = currentMatchScores[playerName];
114+
// Get the other player's name
115+
const otherPlayer = Object.keys(currentMatchScores).find(p => p !== playerName);
116+
const otherScore = currentMatchScores[otherPlayer];
117+
118+
let isMatchPoint = false;
119+
120+
if (strictMode) {
121+
if (playerScore >= 10 && otherScore >= 10) {
122+
// Both at least 10, match point if leading by 1
123+
isMatchPoint = Math.abs(playerScore - otherScore) === 1 && playerScore > otherScore;
124+
} else {
125+
// Normal match point
126+
isMatchPoint = playerScore === winBalls - 1;
127+
}
128+
} else {
129+
isMatchPoint = playerScore === winBalls - 1;
130+
}
131+
132+
if (isMatchPoint) {
111133
document.getElementById('result').innerText = lang(
112134
'ui.gameBoard.matchPoint',
113135
playerName
114136
);
115-
// Automatically clear the match point message after 1.5 seconds
116137
setTimeout(() => {
117138
document.getElementById('result').innerText = '';
118139
}, 1500);
@@ -126,13 +147,35 @@ export function incrementCurrentMatchScore(playerName) {
126147

127148
currentMatchScores[playerName]++;
128149
rotationalServesCounter++;
129-
if (serveRule === 1 && rotationalServesCounter >= rotationalServes) {
150+
const strictMode = document.getElementById('strictMode').checked;
151+
let currentRotationalServes = rotationalServes;
152+
if (strictMode) {
153+
// If both players have 10 or more, change serve every ball
154+
const scores = Object.values(currentMatchScores);
155+
if (scores[0] >= 10 && scores[1] >= 10) {
156+
currentRotationalServes = 1;
157+
} else {
158+
currentRotationalServes = 2;
159+
}
160+
}
161+
if (serveRule === 1 && rotationalServesCounter >= currentRotationalServes) {
130162
rotationalServesCounter = 0;
131163
currentServe = currentServe === 0 ? 1 : 0;
132164
}
133165
checkMatchPoint(playerName);
134166
updateCurrentMatch();
135-
if (currentMatchScores[playerName] >= winBalls) {
167+
168+
// Get the other player's name
169+
const otherPlayer = players[currentMatch[0]] === playerName ? players[currentMatch[1]] : players[currentMatch[0]];
170+
171+
// Check win condition based on strict mode
172+
const hasWon = strictMode
173+
? (currentMatchScores[playerName] >= winBalls &&
174+
(currentMatchScores[playerName] - currentMatchScores[otherPlayer] >= 2 ||
175+
currentMatchScores[playerName] >= winBalls + 2))
176+
: currentMatchScores[playerName] >= winBalls;
177+
178+
if (hasWon) {
136179
totalScores[playerName]++;
137180
document.getElementById('result').innerText = lang(
138181
'ui.gameBoard.winMessage',
@@ -241,22 +284,35 @@ export function updateMatchOrderList() {
241284
}
242285

243286
export function updateHistoryList() {
244-
const historyList = document.getElementById('historyList');
245-
historyList.innerHTML = '';
287+
const historyTable = document.getElementById('historyTable');
288+
const tbody = historyTable.querySelector('s-tbody');
289+
tbody.innerHTML = '';
246290

247291
matchHistory.forEach((match, index) => {
248-
const li = document.createElement('li');
249-
250292
// Extract the two players from the match string
251293
const matchParts = match.split(' vs ');
252294
const player1 = matchParts[0].trim();
253295
const player2 = matchParts[1].split(':')[0].trim();
254296

255-
// Only show scores for these two players
256297
const scoreLine = `${totalScores[player1]}:${totalScores[player2]}`;
298+
// Extract winner
299+
const afterColon = match.substring(match.lastIndexOf(':') + 1).trim();
300+
const winnerParts = afterColon.split(' ');
301+
const winner = winnerParts.length > 1 ? winnerParts.slice(0, -1).join(' ') : afterColon;
302+
303+
const matchUp = `${player1} vs ${player2}`;
257304

258-
li.textContent = `${match}, ${scoreLine}`;
259-
historyList.appendChild(li);
305+
const tr = document.createElement('s-tr');
306+
const tdMatch = document.createElement('s-td');
307+
tdMatch.textContent = matchUp;
308+
const tdScore = document.createElement('s-td');
309+
tdScore.textContent = scoreLine;
310+
const tdWinner = document.createElement('s-td');
311+
tdWinner.textContent = winner;
312+
tr.appendChild(tdMatch);
313+
tr.appendChild(tdScore);
314+
tr.appendChild(tdWinner);
315+
tbody.appendChild(tr);
260316
});
261317
}
262318

src/ui.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,4 +170,34 @@ export function e_boardSelectChange() {
170170
if (selectedIndex >= 0 && selectedIndex < boards.length) {
171171
boards[selectedIndex].classList.add('active');
172172
}
173+
}
174+
175+
export function e_strictModeChange() {
176+
const strictModeCheckbox = document.getElementById('strictMode');
177+
const winBallsField = document.getElementById('winBalls');
178+
const serveRulePicker = document.getElementById('serveRule');
179+
const rotationalServesField = document.getElementById('rotationalServes');
180+
const rotationalServesGroup = document.getElementById('rotationalServesGroup');
181+
const items = serveRulePicker.querySelectorAll('s-picker-item');
182+
let inTurnValue = items[1]?.getAttribute('value');
183+
184+
if (strictModeCheckbox.checked) {
185+
winBallsField.value = 11;
186+
winBallsField.disabled = true;
187+
// Force serve rule to 'in turn' (second item) and disable
188+
if (items[1]) {
189+
serveRulePicker.value = inTurnValue;
190+
}
191+
serveRulePicker.disabled = true;
192+
// Force rotational serves to 2 and disable
193+
rotationalServesField.value = 2;
194+
rotationalServesField.disabled = true;
195+
rotationalServesGroup.hidden = false;
196+
} else {
197+
winBallsField.disabled = false;
198+
serveRulePicker.disabled = false;
199+
rotationalServesField.disabled = false;
200+
// Show/hide rotational serves group based on serve rule
201+
rotationalServesGroup.hidden = serveRulePicker.value !== inTurnValue;
202+
}
173203
}

0 commit comments

Comments
 (0)