เขียนเกม 'เลื่อนตัวเลข' ใน 1 วัน

Coding 'Number Slider' game in a day

ไม่มีสงกรานต์ (13-15 เมษายน 2563)ในปีนี้ เพราะไวรัส Covid-19 ระบาด กทม ถูก lock down เดินทางไปที่ไหนก็ไม่สะดวก จึงเขียนเกมนี้เพื่อความบันเทิงในยามว่าง โดยใช้ HTML5 พร้อมด้วย CSS (Cascade Style Sheet) และ JavaScript และเขียนบทความนี้เผื่อไว้สำหรับนักศึกษา หรือ ผู้ที่เริ่มฝึกการเขียนโปรแกรมที่อยากจะศึกษาขั้นตอนการทำงาน (algorithm) เพื่อนำไปดัดแปลง เพิ่มเติม ในการเขียนโปรแกรมของตนเอง เป็นบันทึกช่วยจำของผู้เขียนด้วย เพราะนาน ๆ ไป ก็จำไม่ได้ว่าเขียน code อะไรไว้บ้าง

Hardware ที่ใช้ในการเขียนเกมนี้ เป็นคอมพิวเตอร์โน้ตบุค CPU Intel Core i5 – 2520M ความถี่ 2.5 GHz หน่วยความจำ 8 GB ฮาร์ดิสก์ขนาด 500 GB ไม่มีการ์ดจอภายนอก ใช้ชิป Intel Graphic 3000 ระบบปฏิบัติการเป็น windows 10 รุ่น Professional 64 bit

ซอฟต์แวร์ที่จะต้องใช้ในการเขียนเกม

การเขียนโปรแกรมเลื่อนตัวเลขนี้ ในคอมพิวเตอร์ของเรา ต้องมี editor สำหรับเขียนโค้ด เลือก editor ที่ตนเองถนัดได้ตามใจชอบ และมี Browser จะเป็น Chrome, FireFox, Opera หรือ Internet Explorer สำหรับผู้เขียน ได้ใช้ VS code เป็น editor และใช้ Chrome เป็นบราวเซอร์

ความรู้พื้นฐานที่ควรมี

ควรมีความรู้เรื่อง การเขียน HTML หรือเขียนเรียนการเขียนโปรแกรม ภาษาใดภาษาหนึ่ง จะช่วยให้เกิดความเข้าใจได้ง่ายขึ้น ถ้าอ่านแล้วไม่ปลอดโปร่ง เกิดความคับข้องใจ สงสัยไปหมดทุกแห่ง คงต้องย้อนกลับไปปูพื้นเรื่องการใช้ HTML สร้างเว็บไซต์ก่อนน่าจะดีกว่า

กำหนดรายละเอียดของเกม

ในเบื้องต้น เราต้องกำหนดรายละเอียดและลักษณะของโปรแกรมเลื่อนตัวเลขก่อนว่า มีหน้าตาอย่าไร มีการทำงานอะไรบ้าง ได้กำหนดไว้เป็นข้อ ๆ ดังนี้

  1. ผู้เล่นสามารถเลือกขนาดของบอร์ดเป็นแบบ 3 x 3 หรือ 4 x 4 หรือ 5 x 5
  2. การเรียงลำดับตัวเลขเมื่อตอนเริ่มต้น จะเรียงลำดับตามที่กำหนดไว้ในโปรแกรม คือ จะสลับตำแหน่งเฉพาะตัวเลื่อน 2 ตำแหน่งสุดท้ายเท่านั้น เช่นกรณี บอร์ดขนาด 4 x 4 จะสลับตำแหน่งระหว่าง 14 กับ 15 ผู้เล่นจะเลือกให้โปรแกรมเรียงตัวเลขแบบสุ่มก็ได้ ทั้งสองกรณี ช่องว่างยังคงอยู่ตรงบริเวณมุมขวาล่างเสมอ
  3. เป้าหมายของเกมส์คือจัดวางตัวเลื่อนให้เรียงลำดับในแนวระดับ จาก 1 ถึง 15 (ในกรณีบอร์ดมีขนาด 4 x 4) โดยเริ่มจากซ้ายมือไปขวามือ จากบนลงล่าง ให้ช่องว่างอยู่มุมล่างขวาสุด
  4. ผู้เล่นเกมส์สามารถเลือกเล่นเกมส์ใหม่ด้วยการใช้เมาส์คลิกที่ปุ่ม New Game การเปลี่ยนขนาดของบอร์ด หรือเพิ่มคุณสมบัติในการสุ่มตัวเลข มีผลทำให้เกิดการเริ่มเกมใหม่เช่นเดียวกัน
  5. การเลื่อนตัวเลขทำได้โดยใช้เมาส์คลิกที่ตัวเลชนั้น โดยที่ตัวเลขนั้นจะเลื่อนมาแทนที่ช่องว่าง ช่องว่างจะเลื่อนไปแทนที่ตัวเลขนั้นสลับกัน ตัวเลขที่จะเลื่อนได้ ต้องมีด้านใดด้านหนึ่งขอตัวเลื่อนอยู่ติดกับช่องว่างนั้น ขณะที่ตัวเลื่อนกำลังเลื่อนมีเสียงประกอบด้วย

หน้าตาของโปรแกรมเลื่อนตัวเลข แบบคร่าว ๆ น่าจะเป็นประมาณนี้

การเขียนโปรแกรมกมเลื่อนตัวเลขนี้ จะใช้รูปแบบการพัฒนาที่แยกส่วนของโปรแกรมออกเป็น 3 ส่วน ที่เรียกว่า MVC design โดยแยกออกเป็นส่วนที่เป็นเนื้อหาหรือโครงสร้างหลัก (Model) ส่วนที่ผู้ใช้มองเห็น (View) และส่วนทีใช้ในการขับเคลื่อนโปรแกรม (Control) เรียกย่อ ๆ ว่า MVC ในกรณีที่มีผู้ร่วมพัฒนาโปรแกรมหลายคน สามารถจัดสรรงานแต่ละส่วนได้อย่างอิสระ ผู้ที่ออกแบบโครงสร้างก็สร้างเนื้อหาหลักของโปรแกรม ผู้มีความเชี่ยวชาญด้านศิลปะ ก็ทำหน้าที่ใส่สีสัน ความสวยงามของหน้าตาของโปรแกรม ส่วนที่ผู้ที่เขียนชุดคำสั่งหรือ code ก็ทำงานของตนในส่วนนี้ไป จากนั้นนำมารวมกันเป็นโปรแกรมที่สมบูรณ์ในขั้นตอนสุดท้าย การเขียนโปรแกรมเกมเลื่อนตัวเลขนี้ จะแยกส่วนที่เป็นโครงสร้างหรือส่วนที่เป็นเนื้อหาของโปรแกรม ไว้ในไฟล์ HTML ส่วนที่ใช้เพื่อการตกแต่งหรือจัดรูปแบบของโปรกรมให้สวยงามจะแยกไว้ในไฟล์ CSS ส่วนที่ทำให้เกิดการเคลื่อนไหวหรือเปลี่ยนแปลงภายในโปรแกรม เช่น ตัวเลื่อนมีการเลื่อนสลับกับช่องว่างเมื่อมีการคลิก หรือเปลี่ยนขนาดตารางให้เป็นแบบอื่น ๆ ตามที่ต้องการ จะเก็บส่วนนี้ไว้ในไฟล์ JS ซึ่งเป็นไฟล์ของภาษาจาวาสคริปต์

เพื่อแยกส่วน View ให้ชัดเจน ตัวเลื่อนที่จะใช้ในเกมนี้ จึงเป็นรูปภาพสี่เหลี่ยมจตุรัส ขนาด 60 x 60 px จำนวน 25 ชิ้น เป็นตัวเลื่อนที่แสดงเลข 1-24 และตัวเลื่อนที่มีเฉพาะพื้นสีขาว แทนช่องว่าง อีก 1 ชิ้น เป็นรูปภาพชนิด jpg หรือ gif ก็ได้ ตั้งชื่อไฟล์เรียงตามตัวเลข ตั้งแต่ 0 ถึง 24 ตามลำดับ

จากนั้น เตรียมสร้างโฟลเดอร์ว่างสำหรับเก็บ source code และ รูปภาพที่เราสร้างขึ้น จะสร้างไว้บน desktop หรือสร้างไว้ในไดร์ C หรือ D ก็ได้ที่เราสามารถหาและใช้งานได้อย่างสะดวก ให้ชื่อโฟลเดอร์นี้เป็น NumberSlider และสร้างโฟลเดอร์ย่อยชื่อ img นำรูปภาพเก็บไว้ในโฟลเดอร์ย่อยนี้

ขั้นตอนที่ 1 สร้างส่วนที่เป็นโครงสร้างหลักของโปรแกรม

เปิด Editor ในที่นี้คือ VS Code สร้างไฟล์ index.html เพื่อใช้ในการแสดงผลพื้นที่หรือบอร์ดที่ใช้ในการเล่นเกม ในโปรแกรมจะเรียกส่วนนี้ว่า แคนวาส (canvas) แสดงข้อความ(text) ปุ่ม(button) checkbox และ radio button ต่าง ๆ ตามที่ได้ออกแบบไว้

 <!Doctype html>
<tml>
<head>
<title>Number Puzzle Game. </title>
<!--  date: 20 June 2020 --->
<!-- Programmed by: Wach R. (wachr0@gmail.com) -->
<meta charset= "UTF-8" />
</head>
<body>
<div>
<h1> Number Puzzle Game</h1> <br/>
<canvas style="border: 1px solid black;" width="310" height="310" ></canvas>
<br/>
<table >
<tr>
<td width=110><td width=110></td><td><button id='newGame'>New Game<button> </td>
</tr>
</table>
<label >Board Size:</label>
<div >
<input type="radio"  value="3" checked>
<label >Size 3x3</label>
<input type="radio"  value="4">
<label >Size 4x4</label>
<input type="radio"  value="5">
<label >Size 5x5</label>
</div>
<br/>
<span >
<input type="checkbox" >
<label >Shuffling tiles when game start.</label>  
</span>
<br/>
<span >
<input  type="checkbox" checked>
<label >Sound played.</label>  
</span>  
</div>
</body>
</html>

หน้าตาของโปรแกรมเกมเลื่อนตัวเลข จะแสดงผลชิดทางด้านขอบซ้ายของบราวเซอร์ และมีหน้าตาเกลี้ยงเกลา ขาดสีสัน สวยงาม เมื่อพิจารณา source code ของไฟล์ index.html สามารถแยกเป็นส่วนสำคัญได้ดังนี้

<!Doctype html>
<html>
<head>
ชื่อเรื่องของเอกสาร อยู่ตรงส่วนนี้
</head>
<body>
  • ชื่อเกมส์
  • แคนวาส
  • ปุ่ม radio button และ check box
</body>
</html>

เพื่อให้ส่วนประกอบถูกจัดวางตามที่ออกแบบไว้ จึงใช้ <div> </div> (ย่อมาจาก division) ใส่แคนวาส <div> </div> อีกชุดหนึ่ง ใส่ radio button ใช้ tag <span> </span> ใส่ checkbox ทั้งสองชุด ส่วนปุ่ม “New Game” นั้น ใส่ไว้ใน tag <table> </table> ตารางนี้มีจำนวน 1 แถว 3 คอลัมน์ คอลัมน์แต่ละคอลัมน์ กว้าง 110 pixel ใส่ปุ่ม button นี้ไว้ในคอลัมน์ที่ 3 เพื่อให้ปุ่มอยู่ทางด้านล่าง ขวามือของแคนวาส

ไฟล์ source code ของขั้นตอนที่ 1 รวมไว้ใน NumberSlider_sourceCode_step01.zip

ชั้นตอนที่ 2 ตกแต่งหน้าตาของโปรแกรมให้สวยงาม

CSS ช่วยในการจัดรูปแบบของการแสดงผลของเอกสาร HTML ได้แก่ การแสดงพื้นฉากหลัง รูปร่าง ขนาด ความหนา ความเอียงและสีของฟอนต์ ที่ใช้แสดงข้อความ จัดรูปแบบหน้าตา สีสันของปุ่มต่าง ๆ radioButton CheckBox เมนู ให้มีความสวยงามในแบบที่เราต้องการ สามารถเขียนประโยคข้อความบอกลักษณะต่าง ๆ เหล่านี้แยกออกเป็นไฟล์ นามสกุล .css ต่างหาก เว็บเพจหลาย ๆ หน้า หรือเอกสาร HTML หลาย ๆ ไฟล์ สามารถนำไฟล์ .css นี้ไปใช้ในการจัดรูปแบบหน้าเว็บเพจร่วมกันได้ ทำให้สะดวกในการแก้ไขหรือปรับปรุง รูปร่างหน้าตาของเว็บเพจหลาย ๆ ไฟล์ ได้โดยมาแก้ไขที่ไฟล์ .css นี้เพียงแห่งเดียว

ประโยคคำสั่งจัดรูปแบบถูกแบ่งออกเป็น 2 ส่วนคือ Selector และ Declaration

Selector คือ ชื่อของtag หรือส่วนประกอบ (Component) หรือ ตัวควบคุม (Control) ที่จะถูกตกแต่งให้มีรูปแบบตามที่เราต้องการ เราอาจกำหนดให้เป็นแบบคลาส (Class selector) โดยใช้จุด . (full stop) นำหน้าชื่อคลาส หรือ เป็นแบบ ID (ID selector) โดยใช้เครื่องหมาย # (hash character) นำหน้าชื่อ Declaration คือส่วนที่บอกถึงรูปแบบที่จะนำไปใช้ในการตกแต่ง ประกอบด้วย 2 ส่วนคือ Property คุณสมบัติของ Selector และ Value ข้อความหรือตัวเลขที่บอกลักษณะหรือขนาดของคุณสมบัตินั้น ๆ

ตัวอย่างเช่น ต้องการจัดรูปแบบส่วนที่เป็นเนื้อหาของเอกสาร HTML ที่เรียกว่า h1 ซึ่งเป็นหัวเรื่องที่มีตัวอักษรขนาดใหญ่สุด มีตัวอักษรสีแดงเข้ม ฟอนต์ของตัวอักษรเป็นแบบ Arial ที่มีความหนามากกว่าปกติด เขียนเป็นประโยคคำสั่ง ดังนี้

h1 {
color: darkred;
font-family: Arial;
font-weight: bolder;
}

แยกให้เห็นแต่ละส่วนของคำสั่งได้ดังนี้

ในส่วนของ Declaration ทั้งค่า property และ value ที่มีหลายค่า จะถูกเก็บไว้ในบล็อกของวงเล็บปีกกา ใช้เครื่องหมายโคลอน ( : ) กั้นระหว่าค่า property และ value แลัวปิดท้ายด้วยเครื่องหมายเซมิโคลอน (;) ท้ายประโยคทุกบรรทัด

สร้างไฟล์ใหม่ชื่อว่า NumberSliderCss.css โดยให้ไฟล์นี้อยู่ในโฟลเดอร์เดียวกันกับ index.html ที่สร้างไว้ก่อนหน้านี้ พิมพ์ข้อความต่อไปนี้ แล้ว save

/* NumberSliderCss.css  */ 
h1{
    color: darkred;
    font-family: Arial;
    font-weight: bolder;
}
div {
    margin: 0px auto;
    width: 340px;
}
canvas {
  border:1px solid darkred;
  display: block;
  margin: 0 auto;
}
label {
    color : brown;
    font-family: Arial;
    font-weight:bold;
    font-size: 20;
} 
button {
  display: inline-block;
  font-size: 14px;
  font-weight: bolder;
  padding: 5px 10px;
  cursor: pointer;
  text-align: center;
  text-decoration: none;
  outline: none;
  color: darkred;
  background-color:khaki; 
  border: none;
  border-radius: 15px;
  box-shadow: 0 5px #999;
}
button:hover {background-color:lightyellow} 
table td {
  width: 110px;
}
.checkbox-tool input[type='checkbox']:after {
    position: relative; 
    width: 10px;
    height: 10px;
    background-color:wheat; /* #d1d3d1;*/
    content: '';
    display: inline-block;
    visibility: visible;
    border: 2px solid white;
}
.checkbox-tool input[type='checkbox']:checked:after {
    background-color: orangered;
} 
.radio-toolbar input[type="radio"] {
    display: none;
  }
.radio-toolbar label {
    display: inline-block;
    background-color: whitesmoke;
    padding: 4px 11px;
    font-family: Arial;
    font-size: 16px;
    color:red;
    cursor: pointer;
    margin-left: 20px;
  }
 .radio-toolbar input[type="radio"]:checked+label{
    background-color: burlywood;
  }

เพื่อให้ tag ในไฟล์ Html ตอบสนองต่อคำสั่งที่กำหนดไว้ใน ไฟล์ Css จึงต้องเข้าไปแก้ คำสั่งใน ไฟล์ Html ข้อความที่มีตัวอักษรเป็นสีแดง เป็นข้อความที่เพิ่มเข้าไปใหม่ เป็นชื่อคลาสต่าง ๆ ที่กำหนดไว้ในไฟล์ css และได้กำหนด Id ของส่วนประกอบแต่ละอัน สำหรับเรียกใช้งานในไฟล์ จาวาสคริปต์ ซึ่งจะได้กล่าวถึงในหัวข้อต่อไป

 <!Doctype html>
<tml>
<head>
<title>Number Puzzle Game. </title>
<!--  date: 20 June 2020 --->
<!-- Programmed by: Wach R. (wachr0@gmail.com) -->
<meta charset= "UTF-8" />
<link rel="stylesheet" href="numberSliderCss.css" >
</head>
<body>
<div>
<h1> Number Puzzle Game</h1> <br/>
<canvas style="border: 1px solid black;" width="310" height="310" ></canvas>
<br/>
<table >
<tr>
<td width=110><td width=110></td><td><button id='newGame'>New Game<button> </td>
</tr>
</table>
<labelfor='boardSize' id='boardSizeHeader' >Board Size:</label>
<div class="radio-toolbar" >
<input type="radio" id="radio1" name="boardSize"  value="3" checked>
<label for="radio1">Size 3x3</label>
<input type="radio" id="radio2"  value="4">
<label for="radio2"Size 4x4</label>
<input type="radio" id="radio3" name="boardSize" value="5">
<label for="radio3"Size 5x5</label>
</div>
<br/>
<span class='checkbox-tool'>
<input type="checkbox" id="chkRandom" >
<label id='lblChk'for='chkRandom'>Shuffling tiles when game start.</label>  
</span>
<br/>
<span class='checkbox-tool'>
<input  type="checkbox" id="chkSound" checked="checked">
<label id='lblSound'for='chkSound' >Sound played.</label>  
</span>  
</div>
</body>
</html>

หน้าตาของโปรแกรมเกมเลื่อนตัวเลข จะมีหน้าตาสวยงามขึ้น ตามที่ได้กำหนดไว้ในไฟล์ Css

ไฟล์ source code ของขั้นตอนที่ 2 รวมไว้ใน NumberSlider_sourceCode_step02.zip

ขั้นตอนที่ 3 ใช้จาวาสคริปต์ แสดงตัวเลื่อนบนจอภาพ

สร้างไฟล์ใหม่ขึ้นมาอีก 1 ไฟล์ ให้ชื่อว่า sliderBoard.js มีนามสกุลชองไฟล์เป็น js เพราะเป็นไฟล์ที่ใช้เก็บคำสั่งที่เขียนด้วยภาษา Javascript

ในชั้นตอนนี้ เป็นการนำรูปภาพตัวเลื่อน มาแสดงผลบนแคนวาส ซึ่งเป็นเสมือนบอร์ดที่ใช้วางตัวเลื่อน ตัวเลื่อนแต่ละอันมีขนาด 60 px บอร์ดของเรามีขนาดต่ำสุดคือ 3 x 3 ขนาดของแคนวาสที่ใช้คือ 180 x 180 พิกเซล ขนาดใหญ่สุดคือ 5 x 5 ขนาดของแคนวาสที่มีพื้นที่มากที่สุด คือ 300 x 300 พิกเซล

มุมบนซ้ายของ canvas คือตำแหน่ง x = 0 และ y = 0 เขียนย่อ ๆ ได้เป็น (x, y) = (0, 0) ค่าของ x เพิ่มขึ้นมาทางขวามือ และ y มีค่าเพิ่มขึ้นมาทางด้านล่างของแคนวาส ตารางย่อยบนแคนวาสแต่ละตารางจะถูกกำหนดด้วยค่า index หรือ rank ประจำตัว เริ่มตั้งแต่ 0 จนถึง (จำนวนตัวเลื่อนทั้งหมดในตาราง -1) กรณีที่บอร์ดมีขนาด 3 x 3 จำนวนตัวเลื่อนเท่ากับ 9 ชิ้น ( เลข 1 ถึง 8 และ ช่องว่าง อีก 1 ชิ้น) index ของตารางย่อยเรียงตั้งแต่ 0 ถึง 8 ดังรูป แต่ละตารางย่อยเป็นสี่เหลี่ยมจตุรัส มีความกว้างคงที่เท่ากับ 60 พิกเซล ตำแหน่งของโคออร์ดิเนตที่มุมบนซ้ายของตารางย่อยแต่ละตารางคำนวณออกมาได้ดังภาพ

ในโปรแกรมจะใช้ฟังก์ชัน computeCoordinate ( ) ในการคำนวณหาจุดโคออร์ดิเนต (x, y) ของแต่ละจุด

function computeCoordinate() {
   let point=[];
   let count = 0;
   for (let y = 0; y < TILE_WIDTH * numberOfTilesPerRow; y = y + TILE_WIDTH) {
      for (let x = 0; x < TILE_WIDTH * numberOfTilesPerColumn; x = x + TILE_WIDTH) {
         point[count] = new Point(x, y);
         count++;
      }
   }
return point;
}

เพื่อเป็นการอุ่นเครื่อง จะทดสอบการทำงานของ JavaScript โดยทำดังนี้

  • load รูปภาพตัวเลื่อนทั้งหมด จำนวน 25 ภาพ มาเก็บไว้ในตัวแปรอะเรย์ชื่อ tileImages [ ]
  • กำหนดความกว้างของแคนวาส (หรือบอร์ดที่ใช้วางตัวเลื่อน) มีขนาด 300 x 300 pixel เพื่อรองรับตัวเลื่อนจำนวน 25 ชิ้น
  • คำนวณจุดโคออร์ดิเนตมุมบนซ้ายของตารางย่อยแต่ละตารางเก็บไว้ในตัวแปร coordinates [ ] เพื่อใช้ในการวาดตัวเลื่อนแต่ละตัว
  • ใช้คำสั่ง drawImage สำหรับวาดรูปตัวเลื่อนทั้งหมด

จะได้ source code สั้น ๆ ดังรูป save คำสั่งทั้งหมดเก็บไว้ในไฟล์ numberSlider.js

 // sliderBoard.js
// date : 20 June 2020
const TILE_WIDTH = 60;
const MAXIMUM_NUMBER_OF_IMAGE =25;
var numberOfTilesPerRow = 5;
var numberOfTilesPerColumn = 5;
var numberOfTiles;
var tileImages = [];
var coordinates=[];
class Point {
    constructor(x,y) {
    this.x = x;
    this.y = y;
    }
}
var board = document.getElementById("canvas")
var ctx = board.getContext("2d");
board.width=300;
board.height=300;
coordinates=computeCoordinate();
createTiles();
function createTiles() {
   for (let i = 0; i < MAXIMUM_NUMBER_OF_IMAGE; i++) {>
        tileImages[i]= new Image();
        tileImages[i].src= './img/'+String(i)+'.gif';
        tileImages[i].onload = function(event) {
        ctx.drawImage(tileImages[i], coordinates[i].x,coordinates[i].y);
   }
}
}
function computeCoordinate() {
   let point=[];  
   let count = 0;
   for (let y = 0; y <TILE_WIDTH * numberOfTilesPerRow; y = y + TILE_WIDTH) {
        for (let x = 0; x <TILE_WIDTH * numberOfTilesPerColumn; x = x + TILE_WIDTH) {
            point[count] = new Point(x, y);
            count++;  
        } 
   }
 return point;
}

ก่อนรันโปรแกรม ต้องแก้ไขไฟล์ index.html โดยเพิ่มประโยคคำสั่งที่ใช้ในการ load ไฟล์ numberSlider.js ตรงใต้ tag </body> ด้วย

      :
      :
<span class='checkbox-tool'>
<input type="checkbox" id="chkSound" checked="checked">
<label id='lblSound'for='chkSound'>Sound played.</label>
</span>
</div>
</body>
<script src="sliderBoard.js"></script>
</html>

เมื่อคลิกที่ไฟล์ index.html ให้ทำงานใน browser ตัวเลื่อนหมายเลข 1 ถึง 24 จะแสดงผลเรียงลำดับ โดยมีตัวเลื่อนที่ว่างอยู่มุมบนซ้ายสุด

source code ของโปรแกรมในขั้นตอนที่ 3 พร้อมรูปภาพทั้งหมดรวบรวมเก็บไว้ใน NumberSlider_sourceCode_step03.zip

ขั้นตอนที่ 4 แสดงภาพตัวเลื่อนตามขนาดของตาราง

    ลำดับการทำงานของโปรแกรมในขั้นตอนที่ 4 จะเป็นดังนี้
  • เมื่อ index.html ทำงาน จะปรากฏข้อความต้อนรับเข้าสู่เกมเลื่อนตัวเลข และมีคำแนะนำให้คลิกที่ปุ่ม New Game เพื่อเริ่มเล่นเกม
  • เมื่อคลิกปุ่ม New Game จะแสดงตาราง ขนาดเท่ากับที่กำหนดไว้ใน radio button จะเริ่มเกมด้วยตารางขนาด 3 x 3 ตัวเลขในตารางจะเรียงตามทีกำหนดไว้ คือสลับตำแหน่งระหว่าง 7 และ 8 โดยมีช่องว่างอยู่มุมขวาล่างสุด
  • ตารางจะจัดวางตัวเลขแบบสุ่ม ถ้าผู้เล่นคลิกที่ checkbox ที่มีข้อความ Shuffling tiles when game start.
  • ในระหว่างการเล่นเกม ถ้าผู้เล่นคลิกเลือกขนาดตาราง ก็จะเป็นการเริ่มเกมใหม่ และจะได้ตารางตามที่ผู้เล่นเลือก

เมื่อพิจารณารายละเอียดโปรแกรม มีตัวแปรที่เพิ่มขึ้นมา และตัวแปรเดิมที่มีการเปลี่ยนแปลงค่า (สีน้ำเงิน คือ ตัวแปรที่มีการเปลี่ยนแปลงค่า สีแดงคือตัวแปรที่เพิ่มเข้า มาใหม่) ตอนเริ่มต้นดังนี้

const TILE_WIDTH = 60;
const MAXIMUM_NUMBER_OF_IMAGE =25;
var numberOfTilesPerRow = 3;
var numberOfTilesPerColumn = 3;
var numberOfTiles;
var Shuffling_Tile = false;
var tileImages = [];
var coordinates=[];
var currentPattern=[];
class Point {
   constructor(x,y) {
      this.x = x;
      this.y = y;
    }
}
var btnNewGame = document.getElementById('newGame');
var chkRandom = document.getElementById('chkRandom');

var board = document.getElementById("canvas");
var ctx = board.getContext("2d");

เมื่อเริ่มต้นโปรแกรม ตารางที่กำหนดไว้คือขนาด 3 x 3 ดังนั้น ตัวแปร numberOfTilesPerrow และ numberOfTilesPerColumn จึงเปลี่ยนค่าเป็น 3 ตัวแปรที่เพิ่มขึ้นมา ได้แก่ shuffling_Tile ไว้สำหรับตรวจสอบว่าผู้เล่นต้องการให้มีการสลับตัวเลื่อนแบบสุ่มหรือไม่ ตอนเริ่มต้นเล่นเกมจะกำหนดค่าให้เป็น false เมื่อเปิดเกมขึ้นมาตารางที่แสดงจะเป็นไปตามมาตรฐานที่กำหนดไว้ ตัวแปร btnNewGame เก็บค่า Id จากปุ่ม Button ที่อยู่ในเอกสาร HTML ตัวแปร chkRandom เกี่ยวข้องกับ checkbox ที่ใช้ตรวจสอบว่าผู้ใช้คลิกเลือกให้มีการสุ่มตัวเลือกเมื่อตอนเริ่มเกมหรือไม่

การตรวจสอบการคลิกที่ปุ่ม New Game และ checkbox จะใช้ฟังก์ชัน addEventListener ในการตรวจสอบ

ตำแหน่งต่าง ๆ ของตัวเลื่อนจะเก็บไว้ในตัวแปร Array ที่ชื่อว่า currentPattern[ ] เช่น ตารางขนาด 3x3 จะให้ช่องว่างอยู่ที่มุมล่างขวา สลับตำแหน่งเลข 7 และ 8 จะเป็นดังนี้

ตารางขนาด 4 x 4 หรือ 5 x 5 สามารถกำหนดค่าเริ่มต้นโดยสลับตัวเลื่อนตัวเลข 2 ตัวสุดท้ายได้ในลักษณะเดียวกัน จะเห็นว่าขนาดอะเรย์ของ currentPattern [ ] จะเปลี่ยนไปตามขนาดของตารางด้วย เพื่อความสะดวกในการกำหนดค่า เราจึงเขียนฟังก์ชัน setDefaultStartingPosition ( numberOfTiles) โดยผ่านค่า จำนวนตัวเลื่อนเข้าไปในฟ้งก์ชัน (ได้แก่ 9 หรือ 16 หรือ 25) ฟังก์ชันจะกำหนดตัวเลขใน curentPattern ให้อย่างอัตโนมัติ ในระหว่างการเล่นเกม ตัวเลขใน currentPattern สามารถเปลี่ยนไปตามการเลื่อนตัวเลข เราจะใช้ตัวแปรนี้ช่วยในการแสดงผลตัวเลขในแต่ละครั้งของการเลื่อนด้วย

    อธิบายการทำงานของโปรแกรมไปตามลำดับดังนี้
  • สร้างตัวแปรใหม่ และเปลี่ยนค่าตัวแปรเก่าบางตัว
  • โปรแกรมเริ่มทำงาน โดยกำหนดให้ความกว้างของแคนวาส เป็น 300 x 300 pixel
    board.width=300;
    board.height=300;
    createTiles();
    showWelcome();
  • สร้างตัวเลื่อนโดยการ Load รูปภาพทั้งหมด 25 ภาพ นำมาเก็บไว้ในตัวแปร tileImages [ ] โดยใช้ฟังก์ชัน createTiles ( ) ในฟังก์ชันนี้เราตัดตรงส่วนที่แสดงรูปภาพบนจอที่ใช้ใน ขั้นตอนที่ 3 ทิ้งไป
    function createTiles() {
        for (let i = 0; i < MAXIMUM_NUMBER_OF_IMAGE; i++) {
           tileImages[i]= new Image();
           tileImages[i].src= './img/'+String(i)+'.gif';
           tileImages[i].onload = function(event) {
           ctx.drawImage(tileImages[i], coordinates[i].x,coordinates[i].y);
        }

        }
    }
    จากนั้นแสดงข้อความยินดีต้อนรับบนจอภาพ และแนะนำให้ผู้ใช้คลิกที่ปุ่ม New Game เพื่อเริ่มเล่นเกม ฟังก์ชัน ShowWelcome() เป็นฟังก์ชันที่ใช้แสดงข้อความบนแคนวาส เท่านั้น เราสามารถเปลี่ยนแปลงข้อความ ขนาด แบบตัวอักษร และสี ให้เป็นไปตามที่เราต้องการ
    function showWelcome() {
          ctx.fillStyle="brown";
          ctx.font= "bolder 32px Tahoma ";
          ctx.fillText("Welcome to",30,70);
          ctx.fillText("Number Slider.",40,110);
          ctx.font= "bolder 18px Tahoma";
          ctx.fillStyle= "blue";
          ctx.fillText("Click 'New Game'",85,200);
          ctx.fillText("to start playing.", 85,240);
    }
    หน้าตาของโปรแกรมตอนเริ่มต้นจะเป็นแบบนี้
  • ตรวจสอบว่าผู้เล่นคลิกที่ปุ่ม New Game โดยใช้ ฟังก์ชัน addEventLlistener( ‘click’, newGame) และตรวจสอบการเปลี่ยนค่าของง checkbox โดยใช้ addEventListener(‘change’, function () ) ถ้ามีการคลิกที่ checkbox จะเปลี่ยนค่า ตัวแปร Shuffling_Tile ให้เป็น true นั่นหมายถึงมีการสลับตำแหน่งตัวเลื่อนแบบสุ่มก่อนการเล่นเกม
    btnNewGame.addEventListener('click',newGame);
    chkRandom.addEventListener('change',function(event) {
         if (event.target.checked){
           Shuffling_Tile=true;
         } else Shuffling_Tile=false;
            newGame();
    });
    ฟังก์ชัน newGame() เริ่มต้นด้วยการคำนวณจำนวนตัวเลื่อนที่ต้องใช้ในโปรแกรม กำหนดขนาดกว้างและยาวของแคนวาสให้เหมาะสมกับขนาดของตาราง กำหนดตำแหน่งตัวเลื่อนลงบนตารางตามรูปแบบมาตรฐานที่ตั้งไว้ ถ้าผู้เล่นคลิกที่ checkbox จะมีการสลับตำแหน่งตัวเลื่อนแบบสุ่ม จากนั้นจึงคำนวณจุดโคออร์ดิเนตของตำแหน่งตัวเลื่อน เพื่อเก็บค่านี้ไว้ให้ฟังก์ชัน drawTiles() ใช้ในการวาดรูปภาพตัวเลื่อนบนแคนวาส
    function newGame(){
         numberOfTiles = numberOfTilesPerRow*numberOfTilesPerColumn;
         board.width = numberOfTilesPerColumn*TILE_WIDTH;
          board.height = numberOfTilesPerRow*TILE_WIDTH;
          currentPattern = setDefaultStartingPositon(numberOfTiles);
          coordinates = computeCoordinate();
          drawTiles();
    }
  • ตรวจสอบว่าผู้เล่นได้เลือกขนาดตารางเป็นแบบใด โดยอาศัย radio button จำนวน 3 รายการ คือ Size 3x3 Size 4x4 และ Size 5x5 สามารถเลือกได้ครั้งละ 1 รายการเท่านั้น คำสั่งที่ใช้ตรวจสอบการเลือกมีดังนี้
    document.querySelectorAll('input[name="boardSize"]').forEach(function(element){
          element.addEventListener("change", function(event) {
          numberOfTilesPerRow = event.target.value;
          numberOfTilesPerColumn=event.target.value;
          newGame();
          });
    });
    เพราะมีรายการ 3 รายการ (ในคำสั่งใช้คำว่า element) จึงตรวจสอบทีละรายการว่ามีรายการใดที่มีการเปลี่ยนแปลงค่าบ้าง ถ้ารายการใดมีการเปลี่ยนค่า ก็จะนำค่าที่เก็บไว้ใน value ของแต่ละ radio button ไปเก็บไว้ในตัวแปร numberOfTilesPerRow และ numberOfTilesPerColumn แล้วเรียกใช้ฟังก์ชัน newGame ( ) เพื่อเริ่มเกมใหม่ให้มีตารางสอดคล้องกับผู้เล่นเลือก

    ค่าที่เก็บไว้ใน value ของปุ่ม radio1 radio2 และ radio3 คือ 3 4 และ 5 ตามลำดับ สามารถดูค่าเหล่านี้ได้ในไฟล์ index.html

  • ฟังก์ชันที่เพิ่มเข้ามาในขั้นตอนที่ 4 ได้แก่ setDefaultStartingPosition(numberOfElements) ใช้รวมกับฟังก์ชัน shuffleTiles( array, numberOfElements) ฟังก์ชัน ShuffleTiles จะทำงานก็ต่อเมื่อ ค่าของตัวแปร Shuffling_Tile มีค่าเป็นจริง

    ังก์ชัน shuffingTiles นั้นใช้คำสั่ง Math.random() ในการสุ่มตัวเลข โดยจะสุ่มทั้งหมด numberOFTiles -1 ครั้ง โดยจะไม่นำตัวเลื่อนที่เป็นช่องว่างมุมล่างขวาสุดเข้าร่วมในการสุ่มด้วย

    ฟังก์ชัน drawTiles ( ) ได้นำคำสั่ง ctx.drawImage ที่ใช้ในชั้นตอนที่ 3 มาเขียนใหม่ โดยในการวาดรูปจะวาดตัวเลื่อนตั้งแต่ ตำแหน่ง index =0 จนถึงตำแหน่ง index = numberOfTiles -1

    function setDefaultStartingPositon(numberOfElements){
    array =[];
          for (let i = 0; i <numberOfElements - 3; i++) {
              array[i] = i + 1;
          }
          array[numberOfElements - 1] = 0;
          array[numberOfElements - 2] = numberOfElements - 2;
          array[numberOfElements - 3] = numberOfElements - 1;
          if (Shuffling_Tile) shuffleTiles(array,numberOfElements);
          return array;
    }
    function shuffleTiles(array,numberOfElements) {
    let count = 0;
    let temp;
          while ( count < numberOfElements -1) {
          // random number from index = 0 to numberOfTiles-2
              let n = Math.floor(Math.random()*(numberOfElements -1));
              temp = array[count];
               array[count] = array[n];
               array[n]= temp;
               count++;
          }
    }

    function drawTiles() {
          for (let i =0; i < numberOfTiles; i++) {
            ctx.drawImage(tileImages[currentPattern[i]], coordinates[i].x, coordinates[i].y);
          }
    }
  • ในชั้นตอนที่ 4 นี้ จะเห็นว่า เราสามารถเลือกตารางสำหรับเล่นเกม และเลือกให้มีการสุ่มตัวเลขหรือไม่ก็ได้ เมื่อคลิกที่ปุ่ม New Game จะเป็นการเริ่มต้นเกมใหม่ แต่ตัวเลื่อนยังคงอยู่กับที่ไม่สามารถเคลื่อนที่ได้ Source code ทั้งหมดในขั้นตอนนี้ รวมไว้ในไฟล์ NumberSlider_sourceCode_step04.zip

    ขั้นตอนที่ 5 คลิกที่ตัวเลื่อนแล้ว ตัวเลื่อนมีการเคลื่อนที่

    เมื่อคลิกที่ตัวเลื่อน จะทำให้ตัวเลื่อนมีการเคลื่อนที่ สลับกับตำแหน่งที่เป็นช่องว่างและเมื่อเลื่อนตำแหน่งตัวเลื่อนเรียงตามลำดับครบแล้ว จะแสดงข้อความว่า เกมสิ้นสุดแล้ว

    เกมเลื่อนตัวเลขจะจบเกมส์ เมื่อตัวเลขถูกวางเรียงกันตามลำดับจากซ้ายไปขวาจนครบทุกตัว โดยมีช่องว่างอยู่ที่มุมขวาล่าง เราจึงสร้างตัวแปรอะเรย์ ชื่อ targetPattern [ ] เพื่อเก็บรูปแบบผลสำเร็จของตัวเลขที่ต้องการเมื่อสิ้นสุดเกม กรณี ตาราง 3 x 3 เมื่อเรียงเสร็จแล้ว จะได้ดังภาพ

    ข้อมูลใน targetPattern เป็นตัวเลขที่เรียงตัวตามตารางและสอดคล้องกับตำแหน่ง(index) ของตาราง เลข 0 จะใช้แทนช่องว่าง

    การกำหนดค่าใน targetPattern จะใช้ฟังก์ชัน designTargetPattern ( ) ช่วยในการกำหนดค่าให้สอดคล้องกับขนาดของ ตารางแบบอัตโนมัติ โดยที่เราไม่ต้องไปกำหนดค่าเริ่มต้นให้แก่ targetPattern เลย

    function designTargetPattern(numberOfElements){
    array=[];
          for (let i = 0; i < numberOfElements – 1; i++) {
               array[i] = i + 1;
          }
          array[numberOfElements – 1] = 0;
          return array;
    }

    การเลื่อนตัวเลื่อนบนตารางทำได้โดยการคลิกเมาส์ที่ตัวเลื่อนนั้น ก่อนอื่นต้องทำให้ตารางหรือ canvas รับรู้การคลิกเมาส์และตอบสนองการคลิกเสียก่อน โดยใช้ฟังก์ชัน addEventListener เมื่อมีการคลิกบนพื้นที่แคนวาส ให้ไปทำคำสั่ง play( )

    board.addEventListener('click',function() {
          play();
    });

    ฟังก์ชัน play( ) มีรายละเอียดดังนี้

    function play(){
    var indexOfClickedTile = getIndexOfClickedTile(coordinates,numberOfTiles,board,event);
    var indexOfEmptyTile = getIndexOfEmptyTile(currentPattern);
          currentPattern[indexOfEmptyTile]=currentPattern[indexOfClickedTile];
         currentPattern[indexOfClickedTile] =0;
         drawTiles();
         if (isGameOver()) {
               var message = "Game Over"
               var x = (board.width -25*message.length)/2;
               var y = board.height /2;
               if (numberOfTilesPerRow==3) {
                  ctx.font="bold 30px Tahoma";
                  x=10;
              } else ctx.font= "bold 40px Tahoma";
               ctx.clearRect(0,y-35,numberOfTilesPerRow*60,50);
               ctx.fillStyle= "blue";
               ctx.fillText("Game Over",x,y);
          }
    }

    เริ่มต้นด้วยตรวจสอบดูว่า ตำแหน่งที่คลิกเมาส์ลงไปนั้น เป็นตำแหน่งใดของตัวเลื่อน ฟังก์ชัน getIndexOfClickedPostion (point, numberOfElements, canvas, event ) เป็นผู้ทำหน้าที่นี้ ฟังก์ชันจะรับค่า point ซึ่งเป็นโคออร์ดิเนตของตำแหน่งต่าง ๆ ของตารางที่เคยคำนวณเก็บไว้แล้ว numberOfElements คือจำนวนตัวเลื่อนทั้งหมด (9, 16 หรือ 25) event คือเหตุการณ์ที่เกิดขึ้นบนแคนวาส ในที่นี้คือการคลิกเมาส์

    function getIndexOfClickedTile( point, numberOfElements,canvas,event) {
    let indexOfClickedTile;
    let mousePosition = getClickedPoint(canvas,event);
    let x = mousePosition.x;
    let y = mousePosition.y;
          for (let i = 0; i < numberOfElements; i++) {
              if ((x >= point[i].x && x < point[i].x+ TILE_WIDTH) && (y >= point[i].y && y < point[i].y+ TILE_WIDTH)){
               indexOfClickedTile = i;
               break;
               }
          }
          return indexOfClickedTile;
    }
    function getClickedPoint(canvas, evt) {
    var rect = canvas.getBoundingClientRect();
    return {
          x: evt.clientX - rect.left,
          y: evt.clientY - rect.top
        };
    }

    ภายในฟ้งก์ชัน getIndexOfClickedPosition จะรับค่า x, y ซึ่งเกิดจากการคลิกเมาส์บน canvas จากฟังก์ชัน getClickedPoint( canvas, event) จากนั้นจะหาว่าจุดที่คลิกเมาส์นั้น ตรงกับตำแหน่งของตัวเลื่อนตัวได โดยตรวจสอบทีละตัวเลื่อน ตัวอย่างเช่น กรณีที่ตารางมีขนาด 3x3 เริ่มต้นด้วยการวนรอบตั้งแต่ตำแหน่งที่ 0 ถึง ตำแหน่งที่ 8 ตรรกะที่ใช้ในการตรวจสอบอยู่ในคำสั่ง

    	(x >= point[i].x && x < point[i].x+ TILE_WIDTH) &&
                    (y >= point[i].y && y < point[i].y+ TILE_WIDTH) 
    

    สมมติว่าเราคลิกบนตารางที่ตำแหน่ง (20, 80) ค่านี้รู้ได้จากคำสั่ง getClickedPoint ฟังก์ชัน getIndexOfClickedPoint จะรับค่า (x, y) นี้มาตรวจสอบตำแหน่งตัวเลื่อนแต่ละตัว โดยการวนรอบโดยเริ่มที่ตำแหน่งที่ 0, 1, 2, … ไปจนกระทั่งพบตำแหน่งที่คลิกเมาส์ ดังตาราง

    การวนรอบจะสิ้นสุดลง ถ้าค่าความจริงในแถวของตารางนั้นมีค่าเป็น true ทุกช่อง จะเห็นว่าเมื่อคลิกเมาส์ที่ตำแหน่ง (20,80) การวนรอบที่ 3 จะให้ค่าความจริงเป็น true ทั้งหมด นั่นคือตำแหน่งของตัวเลื่อนที่ถูกคลิกคือตำแหน่ง (index) = 3 นั่นเอง ฟังก์ชันจะหยุดการวนรอบแล้วส่งค่ากลับเท่ากับ 3 ให้กับตัวแปร indexOfClickedTile

    ต้องการสลับตำแหน่งระหว่างตัวเลื่อนที่ถูกคลิกกับช่องว่าง เราจึงต้องหาตำแหน่งหรือ index ของช่องว่างด้วย ฟังก์ชัน getIndexOfEmptyTile( ) จะค้นดูในตัวแปร currentPattern ว่าช่องว่าง หรือค่า 0 อยู่ตรงตำแหน่งที่เท่าใด แล้วส่งค่า Index นี้คืนกลับให้ตัวแปร indexOfEmptyTile

    function getIndexOfEmptyTile ( array) {
    let index;
          for (let i = 0; i < array.length; i++){
             if (array[i] == 0) {
               index = i;
               break;
            }
          }
          return index;
    }

    เมื่อรู้ค่าตำแหน่งของตัวเลื่อนที่ถูกคลิก และตำแหน่งของช่องว่างแล้ว จึงทำการสลับตัวเลขและช่องว่าง(เลข 0) ระหว่างตัวเลื่อนทั้งสองนี้ ด้วยคำสั่ง

    currentPattern[indexOfEmptyTile] = currentPattern[indexOfClickedTile];
         currentPattern[indexOfClickedTile] = 0;
    

    ข้อมูลตัวเลขในตัวแปร currentPattern จะเปลี่ยนไป จากนั้นใช้คำสั่ง drawTiles วาดรูปตัวเลื่อนโดยอาศัยข้อมูลปัจจุบันของ currentPattern

    ก่อนที่การคลิกเมาส์บนตัวเลื่อนครั้งต่อไปจะเกิดขึ้น ต้องมีการตรวจสอบก่อนว่า ข้อมูลใน currentPattern นั้นเหมือนกันกับข้อมูลใน targetPattern หรือไม่ โดยใช้ฟังก์ชัน isGameOver ช่วยในการตรวจสอบ

    function isGameOver() {
    let count=0;
          for (let index = 0; index < numberOfTiles; index++) {
               if (currentPattern[index] != targetPattern[index])
                    break;
               count++;
          }
          return count == numberOfTiles;
    }

    ถ้าข้อมูลในตัวแปรอะเรย์ทั้งสองเหมือนกันทุกประการ แสดงว่าตัวเลื่อนถูกเลื่อนจนทำให้เลขเรียงกันตามเป้าหมายที่กำหนดแล้ว จะส่งค่า true คืนกลับ พร้อมมีข้อความว่า เกมจบลงแล้ว

    Source code ทั้งหมดในขั้นตอนที่ 5 เก็บไว้ในไฟล์ NumberSlider_Sourcecode_Step05.zip

      ในขั้นตอนนี้ ยังคงมีปัญหาที่ต้องแก้ดังนี้
    1. การสลับตำแหน่งตัวเลื่อนต้องเป็นไปตามกติกา คือต้องสลับได้เฉพาะตัวเลื่อนที่มีด้านใดด้านหนึ่งติดกับช่องว่างเท่านั้น
    2. เมื่อเกมสิ้นสุดแล้ว ยังคงเลื่อนตัวเลื่อนต่อไปได้อีก ซึ่งควรจะหยุดการเลื่อนตัวเลื่อนทั้งหมด จนกว่าจะคลิกที่ปุ่ม New Game
    3. checkbox ของรายการ Sound played ถูกเลือกไว้ แต่ยังไม่มีเสียงให้ได้ยิน

    ขั้นตอนที่ 6 ทำให้การเลื่อนตัวเลื่อนเป็นไปตามกติกา

    กำหนดให้การเลื่อนตัวเลื่อนทำได้เฉพาะ ตัวเลื่อนที่มีด้านใดด้านหนึ่งติดกับช่องว่างเท่านั้น พร้อมใส่เสียงประกอบการเลื่อน และเสียงปรบมือเมื่อเล่นได้จบเกม หลังจากเกมสิ้นสุดลงแล้ว การคลิกที่ตัวเลื่อน หรือที่ส่วนประกอบอื่น ๆ ตัวเลื่อนจะไม่มีการเปลี่ยนตำแหน่งอีกต่อไป จนกว่า จะคลิกที่ปุ่ม New Game

    ในเบี้องต้น เราจะแก้ปัญหา เกมจบแล้ว ผู้เล่นยังสามารถคลิกตัวเลื่อนให้เปลี่ยนตำแหน่ง และใส่เสียงประกอบการเล่น เราจะใส่เสียงประกอบเพียง 2 แห่ง คือเสียงขณะตัวเลื่อนมีการเคลื่อนที่ และเสียงปรบมือเมื่อจบเกม การตรวจสอบตัวเลื่อนให้เคลื่อนที่เฉพาะชิ้นที่อยู่ใกล้กับช่องว่างนั้น เอาไว้ทำในตอนสุดท้าย เพราะในขณะที่ตัวเลื่อนสามารถเปลี่ยนที่สลับกับ ช่องว่างได้อย่างอิสระ ทำให้เราตรวจสอบการทำงานของโปรแกรมตอนจบเกมได้สะดวกกว่า

    เริ่มด้วยการสร้างโฟลเดอร์ชื่อ snd (ย่อมาจาก sound) ภายใต้โฟลเดอร์ NumberSlider นำไฟล์เสียงที่ต้องการ ในที่นี้ได้แก่ hit.mp3 และ clapping_short.mp3 เก็บไว้ในโฟลเดอร์นี้ เพิ่มตัวแปร สำหรับตรวจสอบการจบเกม และเลือกที่จะทำให้เกมมีเสียงในระหว่างการเล่นหรือไม่ ดังนี้

    var Game_Completed = true;
    var Sound_Played=true;

    เมื่อโปรแกรมเริ่มทำงาน ให้ทำการ load ไฟล์เสียงทั้งหมด เก็บไว้ในหน่วยความจำ

    var clappingSound;
    var slidingSound;
    slidingSound = new Audio("./snd/hit.mp3;
    clappingSound = new Audio("./snd/clapping_short.mp3;

    ตรวจสอบว่าผู้เล่น คลิกที่ checkbox รายการ Sound played หรือไม่ โดยใช้ ฟังก์ชัน addEventListener ดูการเปลี่ยนแปลงที่ checkbox นี้ ถ้ามีการคลิกที่ checkbox นี้ จะทำให้ตัวแปร Sound_Played มีค่าเป็นจริง

    chkSound.addEventListener('change',function(event) {
         if (event.target.checked){
              Sound_Played=true;
         } else Sound_Played=false;
    } );

    แก้ไขการเรียกใช้ฟังก์ชัน newGame ของปุ่ม radio button และ checkbox และการเรียกใช้ฟังก์ชัน play เมื่อคลิกที่ตัวเลื่อนบนแคนวาส ถ้าเกมจบลงแล้ว ( Game_Completed = true) จะไม่สามารถเรียกใช้ฟังก์ชันเหล่านี้ได้ ผู้เล่นต้องคลิกที่ปุ่ม New Game เพื่อเริ่มเกมใหม่เท่านั้น

    chkRandom.addEventListener('change',function(event) {
         if (event.target.checked){
              Shuffling_Tile=true;
          } else Shuffling_Tile=false;
          if(!Game_Completed) newGame();
    });
    document.querySelectorAll('input[name="boardSize"]').forEach(function(element){
          element.addEventListener("change", function(event) {
          numberOfTilesPerRow = event.target.value;
          numberOfTilesPerColumn=event.target.value;
         if (!Game_Completed) newGame();
          });
    });
    board.addEventListener('click',function() {
          if (!Game_Completed) play();else alert("Clik 'New Game' for start playing");
    });

    ในฟังก์ชัน newGame เพิ่มบรรทัดคำสั่ง Game_Completed = false; ไว้ที่บรรทัดแรกดังนี้

    function newGame(){
          Game_Completed = false;
          numberOfTiles = numberOfTilesPerRow*numberOfTilesPerColumn;
          board.width = numberOfTilesPerColumn*TILE_WIDTH;
          board.height = numberOfTilesPerRow*TILE_WIDTH;
          currentPattern = setDefaultStartingPositon(numberOfTiles);
          targetPattern = designTargetPattern(numberOfTiles);
          coordinates = computeCoordinate();
          drawTiles();
    }

    ในฟังก์ชัน play เพิ่มคำสั่ง ตรวจสอบการจบเกม และสร้างเสียงขณะตัวเลื่อนมีการสลับที่ และมีเสียงปรบมือเมื่อจบเกม

    function play(){
         if (!Game_Completed) {
              var indexOfClickedTile =           getIndexOfClickedTile(coordinates,numberOfTiles,board,event);
               var indexOfEmptyTile = getIndexOfEmptyTile(currentPattern);
              currentPattern[indexOfEmptyTile] = currentPattern[indexOfClickedTile];
               currentPattern[indexOfClickedTile] = 0;
               if (Sound_Played) slidingSound.play();
               drawTiles();
               if (isGameOver()) {
                    var message = "Game Over";
                    var x = (board.width - 25*message.length)/2;
                    var y = board.height /2;
                   Game_Completed=true;
                   if (numberOfTilesPerRow==3){
                   ctx.font="bold 30px Tahoma";
                    x=10;
                   } else ctx.font= "bold 40px Tahoma";
                    ctx.clearRect(0,y-35,numberOfTilesPerRow*60,50);
                    ctx.fillStyle= "blue";
                    ctx.fillText("Game Over",x,y);
               if(Sound_Played) clappingSound.play();
               }
          }
    }

    ถึงตอนนี้ เราสามารถเล่นเกม โดยมีเสียงขณะเลื่อนตัวเลื่อน และเมื่อจบเกม มีเสียงปรบมือแสดงความยินดี ในขั้นสุดท้าย เราจะตรวจสอบการคลิกเลื่อนตัวเลื่อน โดยจะให้ตัวเลื่อนที่อยู่ใกล้กับช่องว่างเท่านั้นที่สามารถถูกเลื่อนได้ คลิกที่ตัวเลื่อนอื่น ๆ จะไม่มีการขยับ

    การตรวจสอบว่าตัวเลื่อนนั้นสามารถเลื่อนได้หรือไม่ ดูได้จากตำแหน่งโคออร์ดิเนตของตัวเลื่อนที่ถูกคลิก และของช่องว่าง ผลต่างของ x เรียกสั้น ๆ ว่า dx หรือ ผลต่างของ y (dy) จะต้องมีค่าเป็นศูนย์คู่หนึ่งและผลต่างของอีกคู่หนึ่งจะเท่ากับขนาดความกว้างของตัวเลื่อน

    เราสามารถใช้ทฤษฎีสามเหลี่ยมมุมฉากหาความยาวของด้านตรงข้ามมุมฉาก ซึ่งเป็นเส้นตรงที่เชื่อมระหว่างจุดทั้งสองก็จะได้ผลลัพธ์เช่นเดียวกัน ดังที่เขียนคำสั่งไว้ในประโยค comment

    function isValidClicked( clickedPosition, spacePosition, point) {
          let dx = Math.abs(point[clickedPosition].x - point[spacePosition].x);
          let dy = Math.abs(point[clickedPosition].y - point[spacePosition].y);
    // return (Math.sqrt(dx*dx+dy*dy)==TILE_WIDTH);
          return ((dx==0 && dy== TILE_WIDTH)|| (dx == TILE_WIDTH && dy==0));
    }

    เพิ่มคำสั่งตรวจสอบการคลิกตัวเลื่อน ไว้ในฟังก์ชัน play ดังนี้

    function play(){
          if (!Game_Completed) {
               var indexOfClickedTile = getIndexOfClickedTile(coordinates,numberOfTiles,board,event);
               var indexOfEmptyTile = getIndexOfEmptyTile(currentPattern);
               if (isValidClicked(indexOfClickedTile,indexOfEmptyTile ,coordinates)){
                    currentPattern[indexOfEmptyTile] = currentPattern[indexOfClickedTile];
                    currentPattern[indexOfClickedTile] = 0;
                    if (Sound_Played) slidingSound.play();
                       :
                       :
              if(Sound_Played) clappingSound.play();
               }
          }
          }
    }

    โปรแกรมเกมเลื่อนตัวเลชชองเรา ก็เสร็จสิ้นอย่างสมบูรณ์ในขั้นตอนนี้ สามารถนำไปฝากไว้ในเว็บไซต์ แล้วเล่นผ่านอินเทอร์เนต หรือจะเล่นในเครื่องคอมพิวเตอร์ก็ได้เช่นกัน

    source code ของโปรแกรมในขั้นตอนที่ 6 รวบรวมไว้ในไฟล์ NumberSlider_sourceCode_step06.zip

    บางสิ่งที่น่าจะมีเพิ่มเติม

      ขณะที่พัฒนาเกมเลื่อนตัวเลขนี้ มีความคิดบางอย่างเพิ่มขึ้นมา นอกเหนือไปจากที่ออกแบบไว้ ซึ่งน่าจะฝากไว้สำหรับการพัฒนาใน version ต่อ ๆ ไป ดังนี้
    1. น่าจะมีตัวเลือกขนาดของบอร์ด แบบสี่เหลี่ยมผืนผ้าบ้าง เช่น ขนาด 4 x 3 หรือ 6 x 5 หรือบอร์ดหรือขยายขนาดบอร์ดให้ใหญ่กว่าที่มีอยู่ เช่น ขนาด 6 x 6, 7 x 7
    2. ใช้รูปภาพอื่น ๆ เช่น ภาพทิวทัศน์ สามารถนำไปดัดแปลงทำเป็นเกมส์ต่อภาพได้
    3. ถ้าจะเปลี่ยนจากการใช้รูปภาพ แทนตัวเลข เป็นการวาดรูปตัวเลื่อนด้วยคำสั่งภายในโปรแกรมเองน่าจะได้ code ที่สั้นและกระชับกว่านี้
    4. จับเวลาการเล่นเกมในแต่ละเกม และจำนวนครั้งในการเคลื่อนที่ตัวเลื่อน
    5. มีคำแนะนำเคล็ดลับในการเล่นเกม ทำอย่างไรจึงจะใช้เวลา และจำนวนครั้งในการเลื่อนตัวเลื่อนน้อยที่สุด ในทางทฤษฎี จะมีการเรียงตัวเลขบางแบบ ซึ่งไม่สามารถจะเรียงตัวเลขเหล่านั้นให้เรียงลำดับตามที่เราต้องการ จะตรวจสอบได้อย่างไรว่า การจัดวางตัวเลขแบบนี้เป็นการจัดวางที่ไม่สามารถหาผลลัพธ์สุดท้ายได้
    6. แบบเป้าหมายของการสิ้นสุดเกม มีเพียงแบบเดียว คือเรียงลำดับจากซ้ายไปขวาลงมาทีละบรรทัด โดยให้ช่องว่างอยู่มุมล่างขวาสุด เราสามารถสร้างแบบเป้าหมายอื่น ๆ ได้ อีก เช่น ให้การเรียงลำดับตัวเลขเรียงลงมาในแนวดิ่งจากบนลงล่าง ขยับไปทางด้านขวาทีละคอลัมน์ มีช่องว่างอยู่มุมขวาล่างสุด เป็นต้น หรืออาจเรียงตัวเลขแบบอื่น ๆ แล้วแต่จะกำหนดขึ้นมา เพื่อให้เกิดความท้าทายมากขึ้น

    หลังจากนำบทความนี้เผยแพร่ในเว็บ เมื่อเวลาผ่านไป source code ของโปรแกรมนี้อาจมีการ update บ้างเล็กน้อย และได้ทดลองทำ version ที่ไม่ต้องใช้รูปภาพเป็นตัวเลื่อน แยกไว้ในโฟลเดอร์ NumberSliderHTML_Ex ผู้สนใจสามารถ download ต้นฉบับล่าสุดที่ได้รับการปรับปรุงได้จาก github.com/WachRod/NumberSliderHTML

    ในครั้งแรกสุด ผู้เขียนได้พัฒนาเกมเลื่อนตัวเลขนี้ โดยใช้ภาษา Java ได้ทดลองนำ Graphic Use Interface (GUI) ที่เรียกว่า JavaFX มาใช้ในการเชียนโปรแกรม โปรแกรมนี้ทำงานได้ในคอมพิวเตอร์ของตนเองเท่านั้น ไม่สามารถทำงานผ่านเครือข่ายอินเทอร์เน็ตได้ ผู้สนใจสามารถ download ต้นฉบับได้จาก github.com/WachRod/NumberSliderJFX

    ขอให้นักศึกษาและผู้สนใจ เขียนโปรแกรมให้สนุกนะครับ

    Always put on a happy face while you are coding. (Joker didn’t say that)