كيفية إنشاء ساعة شطرنج باستخدام JavaScript و setInterval
مقدمة: لماذا نحتاج إلى ساعة شطرنج؟
قد تستمر بعض مباريات الشطرنج وقتاً طويلاً، خصوصاً عندما يتردد اللاعبان في اتخاذ القرار. وهنا تأتي أهمية ساعة الشطرنج، إذ تمنح كل لاعب زمناً محدداً للتفكير، وتضيف إلى المباراة طابعاً تنافسياً أكثر إثارة. الفكرة الأساسية بسيطة: يجب على كل لاعب إنهاء المباراة أو تحقيق أفضلية قبل نفاد وقته، لأن انتهاء الوقت قبل حسم المواجهة يعني الخسارة.
في هذا الدليل العملي، سنبني ساعة شطرنج أساسية باستخدام JavaScript، مع الاعتماد على الدالة setInterval() لتنفيذ العدّ التنازلي بشكل متكرر كل ثانية.

ما هي الدالة setInterval() في JavaScript؟
تُستخدم الدالة setInterval() لتكرار تنفيذ وظيفة برمجية كل مدة زمنية محددة تُقاس بالمللي ثانية. وهي مناسبة جداً في المشاريع التي تحتاج إلى تحديث مستمر، مثل المؤقتات، والساعات، والعدادات الزمنية.
كما يمكن تخزين القيمة المرجعة من setInterval() داخل متغير، ثم إيقاف التكرار لاحقاً باستخدام clearInterval().
مثال مبسط على عمل setInterval()
let count = 1;
// Assign a timed event to variable timerId.
const timerId = setInterval(() => {
console.log(`Executing function for ${count} seconds.`);
// Increment the count variable by one.
count++;
if (count === 11) {
// Stop event by calling clearInterval on timerId.
clearInterval(timerId);
console.log(`Timing event cleared.`);
}
}, 1000); // Execute event every second (1000 milliseconds = 1 second).

فكرة مشروع ساعة الشطرنج
قبل كتابة الكود، من المفيد فهم المتطلبات الأساسية للمشروع. نحن نحتاج إلى:
- مؤقتين منفصلين للاعبين، بحيث يعدّ كل واحد منهما تنازلياً حتى الصفر.
- زر لبدء التشغيل.
- زر لإعادة الضبط.
- آلية للتبديل بين ساعة اللاعب الأول والثاني بعد كل نقلة.
هذا التصميم يصلح للاستخدام على أجهزة سطح المكتب والهواتف المحمولة، وهو ما يجعل المشروع مناسباً أيضاً كتطبيق تدريبي على أسلوب mobile-first.

تهيئة ملفات المشروع
للحفاظ على تنظيم الملفات، أنشئ المجلدات التالية: css وjs وaudio.
$ mkdir css js audio
بعد ذلك أنشئ الملفات الأساسية للمشروع:
$ touch index.html css/style.css js/script.js
بناء هيكل الصفحة باستخدام HTML
أضف الكود التالي إلى ملف index.html لإنشاء واجهة الساعة وزري التشغيل وإعادة الضبط:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0">
<link rel="stylesheet" href="css/style.css">
<title>chess clock</title>
</head>
<body>
<main>
<div class="player">
<div class="player__tile player-1">
<div class="player__digits">
<span id="min1">10</span> : <span id="sec1">00</span>
</div>
</div>
<div class="player__tile player-2">
<div class="player__digits">
<span id="min2">10</span> : <span id="sec2">00</span>
</div>
</div>
</div>
<div class="timer__buttons">
<button class="timer__start-bttn bttn" type="button">START</button>
<button class="timer__reset-bttn bttn" type="button">RESET</button>
</div>
</main>
<footer>
<p>Press spacebar or click on timer after a move to switch player's clock.</p>
</footer>
<script src="js/script.js"></script>
</body>
</html>
في هذه المرحلة ستظهر الصفحة بشكل بسيط جداً، لأننا لم نضف التنسيقات بعد.

تنسيق واجهة ساعة الشطرنج باستخدام CSS
الخطوة التالية هي تحسين المظهر البصري للمشروع. الكود التالي يعتمد على أسلوب mobile-first، ثم يضيف تعديلات عبر media queries للشاشات الأكبر أو لوضعية العرض الأفقية.
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
background-color: #14A7FF;
}
body {
font-size: 100%;
font-family: monospace, monospace;
}
main {
width: 100%;
padding: 0 10px;
box-sizing: border-box;
}
.player {
margin: 1em 0 5px 0;
display: flex;
flex-direction: column;
}
.player__tile {
width: 100%;
height: 300px;
display: flex;
margin: 0 auto;
color: #000000;
max-width: 400px;
border-radius: 8px;
align-items: center;
justify-content: center;
background-color: #FFFFFF;
box-shadow: inset 3px 3px 0 #000, inset -3px 3px 0 black, inset -3px -3px 0 black, inset 3px -3px 0 black;
}
.player-2 {
color: #FFFFFF;
margin-top: 5px;
background-color: #2D2C2C;
}
.player__digits {
font-size: 6rem;
font-weight: bold;
}
.timer__buttons {
margin-bottom: 1em;
}
.timer__start-bttn,
.timer__reset-bttn {
width: 100%;
display: block;
color: #020202;
min-height: 50px;
max-width: 400px;
font-size: 1.5rem;
font-weight: bold;
border-radius: 8px;
letter-spacing: 2px;
margin: 0 auto 5px auto;
border: 4px solid #000000;
}
.timer__start-bttn {
color: #FFFFFF;
background-color: #0071D5;
}
.timer__start-bttn:hover {
color: #000000;
background-color: #FFFFFF;
}
.timer__reset-bttn:hover {
color: #FFFFFF;
background-color: #0071D5;
}
footer p {
text-align: center;
}
/* Media queries for mobile first develoment. */
/* Media queries for landscape mode on mobile devices */
@media only screen and (orientation: landscape) and (max-width: 850px) {
.player {
max-width: 610px;
flex-direction: row;
margin: 5px auto 0 auto;
}
.player__tile {
max-width: 300px;
max-height: 250px;
margin: 0 3px 5px 3px;
}
.player__digits {
font-size: 5rem;
}
.timer__buttons {
display: flex;
margin: 0 auto;
max-width: 610px;
}
.timer__start-bttn,
.timer__reset-bttn {
display: block;
max-width: 300px;
margin: 0 3px 5px 3px;
}
}
/* Media queries for portrait mode */
@media only screen and (orientation: portrait) and (min-width: 400px) {
.player__tile {
height: 400px;
}
.player__digits {
font-size: 6rem;
}
}
/* Screen wider than 850px wide will use these settings. */
@media only screen and (min-width: 850px) {
.player {
margin: 1em auto 10px auto;
max-width: 810px;
flex-direction: row;
}
.player__tile {
height: 400px;
}
.player-2 {
margin-top: 0;
}
.player__digits {
font-size: 7rem;
}
.timer__buttons {
display: flex;
margin: 0 auto;
max-width: 810px;
}
.timer__start-bttn,
.timer__reset-bttn {
padding: .7em;
font-size: 1.8rem;
}
}
بعد إضافة هذا التنسيق، ستصبح الواجهة أوضح وأكثر قابلية للاستخدام على مختلف أحجام الشاشات.

إضافة منطق التشغيل باستخدام JavaScript
الآن ننتقل إلى الجزء الأهم، وهو تشغيل الساعة فعلياً. سنبني المشروع خطوة بخطوة عبر مجموعة من الدوال، ثم نربطها بأزرار الواجهة وأحداث النقر ولوحة المفاتيح.
الدوال الأساسية التي نحتاجها
ابدأ بتحرير ملف js/script.js:
$ vim js/script.js
ثم جهّز الهيكل العام للدوال والفئة البرمجية:
// Add a leading zero to numbers less than 10.
const padZero = () => {
// code
}
// Warn the player if time drops below thirty seconds.
const timeWarning = () => {
// code
}
// Create a class for the timer.
class Timer {
// code
}
// Swap player's timer after a move (player1 = 1, player2 = 2).
const swapPlayer = () => {
// code
}
// Start timer countdown to zero.
const startTimer = () => {
// code
let timerId = setInterval(function () {
// code
}, 1000)
}
تعريف المتغيرات المبدئية
نحتاج إلى بعض المتغيرات التي تتحكم في حالة اللعبة، مثل ما إذا كانت الساعة تعمل حالياً، ومن هو اللاعب النشط، بالإضافة إلى العناصر المرتبطة بالواجهة والأصوات.
let playing = false;
let currentPlayer = 1;
const panel = document.querySelector('.player');
const buttons = document.querySelectorAll('.bttn');
// Sound effects for project.
const timesUp = new Audio('audio/460133__eschwabe3__robot-affirmative.wav');
const click = new Audio('audio/561660__mattruthsound.wav');
// Add a leading zero to numbers less than 10.
const padZero = (number) => {
if (number < 10) {
return '0' + number;
}
return number;
}
الدالة padZero() تحسن عرض الوقت، بحيث يظهر 09 بدلاً من 9، وهو تنسيق شائع في المؤقتات الرقمية.
تنبيه بصري عند قرب انتهاء الوقت
من الجيد تنبيه اللاعب عندما يصبح الوقت المتبقي قصيراً. في هذا المثال، سنحوّل لون الأرقام إلى الأحمر عندما ينخفض الوقت إلى أقل من 30 ثانية.
// Warn player if time drops below one minute and thirty seconds.
const timeWarning = (player, min, sec) => {
// Change the numbers to red below 0 minutes and 30 seconds
if (min < 1 && sec <= 30) {
if (player === 1) {
document.querySelector('.player-1 .player__digits').style.color = '#CC0000';
} else {
document.querySelector('.player-2 .player__digits').style.color = '#CC0000';
}
}
}

إنشاء فئة Timer لإدارة وقت كل لاعب
بدلاً من التعامل مع كل ساعة بشكل عشوائي، يمكننا استخدام فئة class بسيطة تمثل مؤقت اللاعب. هذا يجعل الكود أوضح وأسهل للتوسعة لاحقاً.
// Create a class for the timer.
class Timer {
constructor(player, minutes) {
this.player = player;
this.minutes = minutes;
}
getMinutes(timeId) {
return document.getElementById(timeId).textContent;
}
}
// Create an instance of the timer for each player.
let p1time = new Timer('min1', document.getElementById('min1').textContent);
let p2time = new Timer('min2', document.getElementById('min2').textContent);
هذه الفئة تخزن معرّف اللاعب وقيمة الدقائق، وتوفّر دالة لجلب قيمة الوقت الحالية مباشرة من الواجهة.
التبديل بين اللاعبين بعد كل نقلة
بعد أن ينهي اللاعب نقلته، يجب أن تتوقف ساعته ويبدأ مؤقت الخصم. تنفذ الدالة swapPlayer() هذا السلوك باستخدام معامل شرطي مختصر.
// Swap player's timer after a move (player1 = 1, player2 = 2).
const swapPlayer = () => {
if (!playing) return;
// Toggle the current player.
currentPlayer = currentPlayer === 1 ? 2 : 1;
// Play the click sound.
click.play();
}
إذا كانت قيمة playing تساوي false، فهذا يعني أن اللعبة لم تبدأ بعد أو انتهت، لذلك تخرج الدالة مباشرة دون تنفيذ أي تبديل.
بناء العدّ التنازلي عبر setInterval()
الآن نصل إلى قلب المشروع. الدالة startTimer() تشغّل مؤقتاً متكرراً كل ثانية، وتطرح ثانية واحدة من اللاعب النشط. وعندما يصل الوقت إلى الصفر، يتم إيقاف المؤقت وتشغيل صوت التنبيه.
// Start timer countdown to zero.
const startTimer = () => {
playing = true;
let p1sec = 60;
let p2sec = 60;
let timerId = setInterval(function () {
// Player 1.
if (currentPlayer === 1) {
if (playing) {
buttons[0].disabled = true;
p1time.minutes = parseInt(p1time.getMinutes('min1'), 10);
if (p1sec === 60) {
p1time.minutes = p1time.minutes - 1;
}
p1sec = p1sec - 1;
document.getElementById('sec1').textContent = padZero(p1sec);
document.getElementById('min1').textContent = padZero(p1time.minutes);
timeWarning(1, p1time.minutes, p1sec);
if (p1sec === 0) {
// If minutes and seconds are zero stop timer with the clearInterval method.
if (p1sec === 0 && p1time.minutes === 0) {
// Play a sound effect.
timesUp.play();
// Stop timer.
clearInterval(timerId);
playing = false;
}
p1sec = 60;
}
}
} else {
// Player 2.
if (playing) {
p2time.minutes = parseInt(p2time.getMinutes('min2'), 10);
if (p2sec === 60) {
p2time.minutes = p2time.minutes - 1;
}
p2sec = p2sec - 1;
document.getElementById('sec2').textContent = padZero(p2sec);
document.getElementById('min2').textContent = padZero(p2time.minutes);
timeWarning(2, p2time.minutes, p2sec);
if (p2sec === 0) {
// If minutes and seconds are zero stop timer with the clearInterval method.
if (p2sec === 0 && p2time.minutes === 0) {
// Play a sound effect.
timesUp.play();
// Stop timer.
clearInterval(timerId);
playing = false;
}
p2sec = 60;
}
}
}
}, 1000);
}
لاحظ أننا أضفنا استدعاءً للدالة timeWarning() أثناء التحديث حتى يتغير اللون تلقائياً في اللحظة المناسبة.
كيف يعمل هذا المنطق؟
- يتم ضبط
playingإلىtrueعند بدء التشغيل. - يحدد المتغير
currentPlayerالساعة التي يجب أن تتناقص. - كل ثانية، يتم تحديث عناصر
HTMLبالقيم الجديدة. - عند وصول الدقائق والثواني إلى الصفر، يتم تنفيذ
clearInterval()لإيقاف المؤقت.
ربط الساعة بالأزرار وأحداث التفاعل
حتى تعمل الواجهة فعلياً، نحتاج إلى مستمعات أحداث للأزرار، وإمكانية التبديل بين اللاعبين بالنقر أو بالضغط على مفتاح المسافة.
// Listen for a mouse click or tap on the screen to toggle between timers.
panel.addEventListener('click', swapPlayer);
// Loop through the start and reset buttons.
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', () => {
if (buttons[i].textContent === 'START') {
// Turn the button a gray color to signify a disabled button.
buttons[i].style.color = '#EEEEEE';
buttons[i].style.backgroundColor = '#606060';
startTimer();
} else {
// Reset everything by reloading the page.
location.reload(true);
}
});
}
// Listen for the press of the spacebar on Windows, Linux, and Mac.
document.addEventListener('keypress', event => {
if (event.keyCode === 32 || event.which === 32) {
swapPlayer();
}
});
هناك نقطة مهمة هنا: في بعض النسخ من الكود قد يظهر اسم العنصر كالتالي timerPanel، بينما المتغير الفعلي الذي أنشأناه هو panel. لذلك يجب استخدام الاسم الصحيح حتى لا يحدث خطأ أثناء التنفيذ.
النتيجة النهائية للمشروع
بعد جمع جميع الأجزاء السابقة، ستحصل على ساعة شطرنج بسيطة وعملية تعمل في المتصفح، وتسمح لك ببدء الوقت، والتنقل بين اللاعبين، وإعادة ضبط اللعبة بسهولة.

أفكار تطوير إضافية للمشروع
رغم أن هذا المثال يحقق الفكرة الأساسية، إلا أنه قابل للتطوير بشكل كبير. إليك بعض التحسينات المفيدة:
- إضافة واجهة لتحديد مدة اللعب قبل بدء المباراة.
- دعم وضع الإيقاف المؤقت
Pause. - إضافة أنماط زمنية مختلفة مثل
BlitzوRapid. - تطبيق زيادة زمنية بعد كل نقلة مثل
Increment. - تحسين الوصول لذوي الاحتياجات عبر دعم أكبر للوحة المفاتيح.
أفضل الممارسات البرمجية في هذا المشروع
لماذا هذا المشروع مفيد تعليمياً؟
هذا التطبيق الصغير يجمع بين عدة مفاهيم مهمة في تطوير الواجهات:
- استخدام
setInterval()وclearInterval()لإدارة المهام المتكررة. - التعامل مع
DOMلتحديث النصوص والألوان ديناميكياً. - فهم
event listenersوربطها بالنقر ولوحة المفاتيح. - تنظيم الكود باستخدام
classودوال مستقلة. - اتباع أسلوب تصميم
mobile-firstلتجربة أفضل على الجوال.
ملاحظة تقنية مهمة
هذا التنفيذ مناسب كمشروع تدريبي واضح، لكنه ليس الأكثر دقة في إدارة الوقت على المدى الطويل، لأن setInterval() قد يتأثر أحياناً بأداء المتصفح أو انشغال الخيط الرئيسي. في المشاريع الأكثر احترافية، يمكن الاعتماد على الطوابع الزمنية مثل Date.now() لحساب الزمن المتبقي بدقة أعلى.
الخلاصة التقنية
يُعد بناء ساعة شطرنج باستخدام JavaScript مشروعاً ممتازاً لفهم كيفية عمل التوقيت المتكرر والتفاعل مع واجهة المستخدم في الوقت الحقيقي. استخدام setInterval() هنا مناسب للتطبيقات التعليمية والبسيطة، كما أن تقسيم المشروع إلى HTML وCSS وJavaScript يجعل صيانته وتطويره أسهل. وإذا أردت تحويل هذا النموذج إلى تطبيق أكثر احترافية، فابدأ بتحسين دقة الوقت، وإضافة إعدادات مخصصة، ثم دعم أوضاع لعب متعددة.