Đây là một thuộc chủ đề người thật việc thật. Chúng tôi viết bài này bởi vì mạng mẽo quá kém nên việc hướng dẫn kèm 1:1 thất bại. Câu chuyện như sau: có một người bạn Đ (thực ra là trước đây bạn này chi tiền ra để chúng tôi kèm về Web nói chung, vài thứ liên quan WordPress nói riêng) gửi cho chúng tôi vài thư mục chứa file mã nguồn game học toán cho trẻ em.
Mã nguồn gồm có những gì?
Các game được bạn ấy thu thập trên mạng, có lẽ từ trang nước ngoài, phát hành dưới dạng mã nguồn mở. Chúng tôi tải về một thư mục trông như hình sau:
Trao đổi qua Facebook Messenger, chúng tôi mô tả lại:
Yêu cầu của bạn Đ như sau:
Chúng tôi hiểu và diễn giải lại yêu cầu, cụ thể:
- Các bài toán ở vế bên trái thêm các hình icon vào cho đẹp, bắt mắt trẻ em
- Khi click vào nút hình máy tính bỏ túi sẽ báo cho biết là kết quả bài toán đúng hay sai. Hiện tại nếu đúng thì thêm điểm vào Correct Answer và đổi nền xanh lá cây dòng tương ứng với phép toán. Sửa chữa thành khi click vào nút sẽ phát âm thanh ngẫu nhiên để tạo sự vui thú cho trẻ em. Ví dụ làm đúng sẽ có các âm thanh phát ra như: bé giỏi lắm, bé rất thông minh, điểm 10 cho bé,… Nếu trẻ làm chưa đúng, có các âm thanh: bé chịu khó học nhé, bé thử lại nào, gần đúng rồi.
- Các file icon được Đ sưu tầm gửi kèm trong file ZIP, file mp3 thì Đ thu âm từ giọng nói của vợ anh ấy.
Xem code, lên phương án trước khi tiến hành
Chúng tôi nhận xét thấy mã nguồn của game hoàn toàn là web tĩnh với bộ 3 HTML + CSS + JavaScript mà không sử dụng đến server-side script.
File index.html gọi vào 2 file quan trọng để làm nên game là css/arsho.css và js/arsho.js. Mã nguồn có dùng Google Fonts và Fontawesome CSS để tạo các font chữ đẹp.
Để tạo sinh (generate) ra các bài toán mỗi lần chơi khác nhau sẽ khác nhau, file arsho.js chính là trái tim của game.
Mã nguồn dùng 1 CSS framework (Bootstrap), và 1 thư viện JavaScript (jQuery). Hai món này rất thông dụng với người làm Web, giúp tiết kiệm rất nhiều thời gian, công sức viết mã.
Soi mã HTML (dùng DevTools) khi đang chơi game:
Đoạn giao diện trên được tạo ra từ mã HTML:
<table class="table table-sm quiz_rows" id="quiz_rows">
<tr class="quiz_row text-white text-center align-items-center bg-danger">
<td> 7 </td>
<td>
<i class="fas fa-plus"></i>
</td>
<td> 1 </td>
<td>
<i class="fas fa-equals"></i>
</td>
<td>
<input id="0" class="answer_input form-control" type="number" tabindex="1">
</td>
<td>
<button class="btn btn-light">
<i class="fa fa-2x fa-calculator"></i>
</button>
</td>
</tr>
<tr class="quiz_row bg-dark text-white text-center align-items-center">
<td> 1 </td>
<td>
<i class="fas fa-plus"></i>
</td>
<td> 5 </td>
<td>
<i class="fas fa-equals"></i>
</td>
<td>
<input id="1" class="answer_input form-control" type="number" tabindex="2">
</td>
<td>
<button class="btn btn-light">
<i class="fa fa-2x fa-calculator"></i>
</button>
</td>
</tr>
<tr class="quiz_row bg-dark text-white text-center align-items-center">
<td> 8 </td>
<td>
<i class="fas fa-plus"></i>
</td>
<td> 3 </td>
<td>
<i class="fas fa-equals"></i>
</td>
<td>
<input id="2" class="answer_input form-control" type="number" tabindex="3">
</td>
<td>
<button class="btn btn-light">
<i class="fa fa-2x fa-calculator"></i>
</button>
</td>
</tr>
<tr class="quiz_row bg-dark text-white text-center align-items-center">
<td> 2 </td>
<td>
<i class="fas fa-plus"></i>
</td>
<td> 4 </td>
<td>
<i class="fas fa-equals"></i>
</td>
<td>
<input id="3" class="answer_input form-control" type="number" tabindex="4">
</td>
<td>
<button class="btn btn-light">
<i class="fa fa-2x fa-calculator"></i>
</button>
</td>
</tr>
<tr class="quiz_row bg-dark text-white text-center align-items-center">
<td> 7 </td>
<td>
<i class="fas fa-plus"></i>
</td>
<td> 5 </td>
<td>
<i class="fas fa-equals"></i>
</td>
<td>
<input id="4" class="answer_input form-control" type="number" tabindex="5">
</td>
<td>
<button class="btn btn-light">
<i class="fa fa-2x fa-calculator"></i>
</button>
</td>
</tr>
</table>
Các bài toán được chứa trong một table. Mỗi dòng là một phép tính theo hàng ngang. Ở ví dụ là bài toán cộng trong phạm vi 20.
Mới thoạt đầu, chúng tôi nghĩ rằng sẽ làm ảnh nền cho các số hạng trong phép tính. Ví dụ như bài toán đầu tiên là 7 + 1 = thì cho số 7 vào trong 1 hình ảnh làm nền background image như hoa hướng dương hoặc con cá, ly nước chẳng hạn, rồi số 1 cũng tương tự.
Khi mở thư mục chứa các icon ra xem, các icon không “rỗng ruột”, cũng có nền trong suốt hoặc nền trắng. Điều này sẽ khiến cho việc con số đứng giữa hình làm nên nhiều lúc không đọc được, như minh hoạ sau:
Trao đổi thêm, chúng tôi hiểu rằng bạn Đ muốn hình ảnh icon đó đứng cạnh bên, phía trước con số, không phải icon làm ảnh nền.
Thực sự, nếu làm ảnh nền cũng được thôi. Nhưng nảy sinh rắc rối vì sẽ phải làm một vòng tròn bên trong icon với một màu đồng nhất để làm nền cho con số (khá dễ nhưng không đẹp).
Hoặc lập trình để cho số và nền tương phản nhau, việc này khó hơn nhiều và trông chữ số đủ màu cũng không đẹp, nếu đẹp sẽ đầy chất nghệ thuật.
Nếu bạn thích rèn luyện kỹ năng lập trình, đây là điều thú vị, khá khoai chứ không đùa, chúng tôi từng làm cái này trước đây khi tự học. Bạn chỉ muốn xem ai đó làm viêc này ra sao, thư viện JavaScript có tên TinyColor là cái bạn cần nghiên cứu.
Tiến hành thêm icon vào trước con số
Cách 1: chỉ dùng CSS. Mở file css/arsho.css ra, thêm vào cuối file:
Tương tự như vậy ở số hạng thứ 2 đứng sau dấu cộng trong phép tính cộng hàng ngang ta cũng làm tương tự mà hôi, chỉ khác selector:
Xem như giải quyết xong.
Cách 2: sử dụng JavaScript thay đổi thuộc tính CSS. Mở file js/arsho.js để tìm đoạn mã cần sửa:
Ở đây, chúng ta để ý thấy chỗ có operand_1 và operand_2 chính là 2 số hạng trong phép tính tổng kia, cũng chính là 2 nơi cần thêm icon vào trước con số.
Lúc này, ta có thể chọn 1 trong 2 cách để có hình ảnh: 1) Sửa mã HTML chèn icon như là hình ảnh với thẻ IMG vào phía trước con số; 2) Dùng thuộc tính background image để chèn ảnh dưới dạng ảnh nền.
Chúng tôi trình bày luôn cả 2 phương án:
Phương án 1: đoạn mã JavaScript trở thành như sau:
Thật dễ, đơn giản, dường như chẳng có vẻ gì là “lập trình” hết, chỉ thêm 2 đoạn mã HTML chèn ảnh khá thô sơ. Kết quả cái máng lợn:
Phương án 2: mang hơi hướm “lập trình” chút xíu. Với “ý tưởng giải thuật” nghe lâm ly bi đát mùi mẫn kiểu như này: xác định element của 2 ô kia và thêm vào mỗi ô là một element IMG. Nào, chi tiết:
Vì đây là mã nguồn của người khác, tuy đơn giản nhưng là “code lạ”. Ta cần chút thời gian đọc code, khám phá như một người tò mò, cố gắng xử lý nhanh mà không cần nghiên cứu sâu.
Bật DevTools lên, vào tab Console gõ lệnh nhằm kiểm tra xem các ô chứa số hạng đầu tiên của các phép tính là gì. Gõ vào Console:
Nhưng để chắc chắn hơn liệu có chính xác? Ta gõ:
document.querySelectorAll('.quiz_row > td:nth-child(1)')[0];
Nghĩa là truy cập vào node đầu tiên trong NodelList xem trình duyệt có chỉ đúng element mà ta đang muốn tác động? Trình duyệt hiện lên:
Để chắc ăn hơn, lần nữa gõ dòng:
document.querySelectorAll('.quiz_row > td:nth-child(1)')[4];
Đúng là trỏ vào ô đầu tiên của dòng thứ 5 trong bảng. Không có ảnh minh hoạ, mục đích để bạn thử và kiểm chứng.
Vậy là vấn đề còn lại: lặp qua từng phần tử của NodeList để thêm vào một IMG. Lý thuyết là vậy nhưng thực hành thì khi mở file js/arsho.js ra thấy nó dùng jQuery viết mã. Do vậy ta cũng viết kiểu jQuery, không nên trộn lẫn “JavaScript thuần” vào, trừ khi ta không biết jQuery.
Mất vài phút đọc mã, tìm được đoạn:
Mỗi lần vào game, hộp thoại thiết lập đầu tiên số lượng bài toán, toán cộng/ trừ/ nhân/ chia cho các bài toán (5, 10, 15,…) và bấm nút Tạo câu hỏi sẽ sinh ra các dòng bằng vòng lặp và đoạn jQuery (1) ở hình trên.
Ta hiểu rằng ngay khi các dòng vừa được tạo ra, ta sẽ tìm đến cột 1, 3 là chứa các số hạng trong phép toán để thêm icon cho chúng.
Hiện thực hoá bằng đoạn mã JavaScipt sử dụng jQuery như sau:
const image1 = '<img src="icon/mat-cuoi.png" width="50" height="50" alt="Smiles icon">';
const image2 = '<img src="icon/trai-tim.png" width="50" height="50" alt="Heart icon">';
$("#quiz_rows tr td:first-child").prepend(image1);
$("#quiz_rows tr td:nth-child(3)").prepend(image2);
2 dòng mã đầu tiên là khai báo đoạn HTML có tác dụng tạo ảnh icon, nó hoàn toàn là HTML. Bạn có thể đọc hiểu rất dễ dàng, phải không?
2 dòng mã sau là jQuery, thêm ảnh icon vào ô 1, 3, dùng prepend nghĩa là thêm trước và nội dung đang có sẵn (chính là các số hạng của phép tính).
Những dòng trên được đặt ngay sau vòng lặp for ở (1) đã nói ở trên. Như vậy là xong rồi đó. Thử xem kết quả trên trình duyêt có ổn không? Kết quả:
Bạn vẫn có thể cải tiến, hoặc nghĩ ra cách làm khác cho cùng kết quả. Ví dụ: thêm thuộc tính class vào cho các thẻ TD tương ứng với ô 1, 3 của các dòng, các class này để thêm icon dưới dạng ảnh nền (cần định nghĩa các rule trước trong file css/arsho.css). Nếu bạn khá về CSS, thử áp dụng Pseudo Element để cho vị trí icon linh hoạt, đẹp mắt hơn.
Bạn cũng có thể làm vui mắt/ rối mắt người chơi game bằng cách mỗi phép tính dùng icon khác nhau. Xem như phần bài tập về nhà của bạn. Random ngẫu nhiên như thế nào bạn có thể lấy ý tưởng từ phần phát âm bên dưới, tương tự như vậy ta tạo các mảng chứa tên file icon là được.
Thực hiện phát âm khi chấm bài
Khi điền lời giải (trả lời) cho từng bài toán, người chơi click nút hình máy tính bỏ túi để xem chấm điểm. Hiện tại trong game chỉ cho biết kết quả đúng sai bằng cách nếu làm đúng sẽ hiện nền bài toán màu xanh lá cây, cộng điểm vào ô Correct Answer bên trên.
Giờ đây, yêu cầu đặt ra là phát âm những lời khen, động viên tuỳ theo đáp án đúng/ sai của các em bé chơi game. Nào, ta xem qua nút bấm nút hình máy tính bỏ túi là gì:
Với chút căn bản về JavaScript bạn bắt đầu hình dung trong đầu: đoạn mã trong file js/arsho.js bắt sự kiện bấm nút kia. Nếu bạn biết về jQuery nghĩ ngay đến dấu đô-la đứng trước, ngoặc tròn ngay kề, tham số trong ngoặc đề cập đến btn hoặc btn-light.
Sự thật kinh hoàng 😃. chúng tôi tìm không thấy có cái nào như vậy. Đọc file js/arsho.js có đoạn:
Đoạn mã trên chỉ trích một phần không đầy đủ vì việc chụp ảnh code đưa vào dài quá khó theo dõi bài viết. Nếu bạn mở file (chúng tôi cung cấp bản tải về cuối bài) ra xem sẽ thấy đầy đủ khối code.
Khối này bắt sự kiện khi con trỏ text được di chuyển ra bên ngoài một thanh văn bản nhập kết quả tính toán của người chơi.
Khi thử chơi game, chúng tôi phát hiện khi bài toán chưa làm mà mang trỏ text ra ngoài ô trả lời sẽ bị chấm điểm xem như giải sai. Điều này khiến người mới chơi lần đầu mất điểm nếu không lần lượt làm các câu từ trên xuống dưới. Cái này theo chúng tôi là thiết kế game chưa tốt, cần bỏ đi để tránh mất điểm oan cho người chơi, một cái bẫy gây ức chế.
Dẹp bỏ cách bắt sự kiện này bằng cách biến khối thành comment hoặc xoá bỏ. Chúng tôi tận dụng lại nhiều dòng code (có thay đổi chút ít để vì this trong jQuery đã khác) trong khối để đưa vào sự kiện click nút máy tính bỏ túi, không phải viết lại code thông báo đáp án bằng đổi màu, cộng điểm vào ô trả lời.
Mục đích chính ở đây là thêm âm thanh chứ không phải viết lại code của người ta. Giờ đây, code trông như sau:
Trước đó đã tạo thư mục audio, bên trong thư mục này chứa 2 thư mục con là correct, wrong chứa các file MP3 phát âm tương ứng.
Các file phát âm này sẽ được sử dụng trong 2 hàm vừa kể trên. Để lời khen/ đánh giá về giải đúng/ sai ngẫu nhiên, chúng ta sẽ cho tên file vào mảng và mỗi lần dùng hàm random để lấy ngẫu nhiên một phần tử trong mảng ra. Đoạn code bên dưới thực thi ý tưởng đó.
Có một điều 99% khả năng sẽ xảy ra khi file MP3 lớn. Click nút máy tính bỏ túi sẽ mất mấy giây sau âm thanh mới phát ra vì gửi request chục MB sẽ khó mà nhanh được. Khi số “mấy giây” đó lên đến hàng chục giây sẽ khó chấp nhận.
Do vậy, để giải quyết bạn sẽ phải tối ưu kích thước file MP3 và preload nó ngay khi nạp game để có thể phát tiếng mượt mà hơn. Phần này xem như bài tập mở rộng để bạn thử sức.
Xong rồi đó. Bạn có ý kiến gì, hãy tham gia thảo luận, chúng tôi có liên kết tải mã nguồn bên thảo luận.