1.10. [Thực hành] Từ khóa bí ẩn

Từ đầu của giáo trình này, các bạn đã được làm quen với khá nhiều khái niệm : trình biên dịch, IDE, các biến, các hàm, các lệnh điều kiện, vòng lặp. Các bạn đã được chỉ cho xem rất nhiều ví dụ xuyên suốt các bài học nhưng đã có ai thử viết những chương trình thật sự chưa? Nếu vẫn chưa thì bài học này là 1 cơ hội để bắt đầu thử xem.

Trong giáo trình này, các bạn sẽ thường được rèn luyện qua các bài thực hành, từ 1 đến 2 bài mỗi chương. Đây là cơ hội để bạn thật sự tự mình tạo ra một chương trình tin học có những ứng dụng nhất định.

Chủ đề của bài thực hành lần này không quá khó, nhưng tôi thấy cũng khá thú vị : chúng ta sẽ đảo lộn vị trí các chữ cái trong 1 từ và để người chơi đoán xem từ khóa bí ẩn đó là gì. Đây sẽ 1 trò chơi nhỏ các bạn có thể cùng chơi với bạn bè hoặc người thân trong những lần gặp mặt.

Chuẩn bị trước khi lập trình – Những lời khuyên

Mục tiêu là tìm được từ khóa bí ẩn trong chuỗi ký tự đã đảo lộn vị trí. Trò chơi mà chúng ta thực hiện nghe có vẻ đơn giản nhưng bạn sẽ thấy là chúng ta sẽ cần dùng đến những khái niệm đã được nói đến trong những bài học trước :

  • Các chuỗi ký tự string
  • Các hàm
  • Các lệnh cấu trúc (lệnh điều kiện, vòng lặp)

Nếu các bạn thấy vẫn chưa tự tin lắm với kiến thức của mình, có thể đọc lướt qua lại các bài học trước rồi chúng ta sẽ bắt đầu ngay lập tức.

Nguyên tắc của trò chơi

Trò chơi của chúng ta sẽ diễn ra như sau :

  1. 1 người chơi nhập từ khóa bí ẩn vào từ bàn phím
  2. Máy tính sẽ đảo lộn vị trí các chữ cái trong từ khóa
  3. Những người chơi còn lại sẽ đoán xem từ khóa bí ẩn mà người chơi lúc trước nhập vào là gì.

Sau đây là 1 ví dụ của trò chơi :

  1. Trong ví dụ trên đây, người chơi 1 chọn từ khóa là « TU KHOA BI AN »
  2. Máy tính thay đổi thứ tự các chữ cái trong từ khóa và yêu cầu người chơi 2 tìm ra từ khóa ẩn sau « UIKOB T AA HN »
  3. Người chơi 2 thử các đáp án nhưng chỉ đến lần thứ 3 mới tìm ra được đáp án đúng. Chương trình đưa ra lời khen ngợị và kết thúc.

Đương nhiên là trong trường hợp này, người chơi thứ 2 có thể dễ dàng nhìn thấy từ khóa bí ẩn mà người chơi thứ nhất đã nhập vào. Chúng ta sẽ xem xem làm sao có thể giải quyết vấn đề này ở cuối bài thực hành.

1 vài lời khuyên để bắt đầu

Nếu bạn là người mới lần đầu học lập trình, có người đến nói với bạn “Hãy viết cho tôi chương trình làm việc A, việc B, việc C,… !”, bạn sẽ rất dễ mất phương hướng.

“Tôi phải bắt đầu từ đâu?”, “Tôi phải làm gì? Sử dụng những gì để thực hiện?”, vv… thường là những câu hỏi đầu tiên xuất hiện trong đầu những người mới. Điều này là hiển nhiên vì các bạn chưa từng làm điều này trước đây.

Đừng lo, tôi sẽ không bỏ rơi các bạn đâu. Sau đây là 1 vài lời khuyên có thể hữu ích để việc chuẩn bị của bạn được tốt hơn. Thế nhưng cuối cùng thì đây cũng chỉ là những lời khuyên, quyết định nên làm thế nào vẫn hoàn toàn là ở bạn.

Nhắc lại các bước tiến hành chương trình

Chúng ta sẽ nhắc lại 3 bước trong tiến trình của chương trình :

  1. Nhập vào 1 từ khóa bí ẩn
  2. Đảo lộn vị trí các chữ cái của từ khóa
  3. Lặp cho tới khi từ khóa được tìm ra

Những bước nêu trên đây về cơ bản khá là độc lập với nhau. Vì thế thay vì viết ngay lập tức cả chương trình, sao chúng ta không tách ra và xử lý từng bước một ?

  1. Bước 1 khá đơn giản : người chơi nhập vào 1 từ khóa và chương trình lưu nó vào trong 1 ô nhớ. Dữ liệu kiểu string khá là phù hợp trong trường hợp này. Nắm vững kiến thức về nhập và xuất dữ liệu và bạn sẽ dễ dàng hoàn thành bước này.
  2. Bước 2 là phức tạp nhất : chương trình cần phải đổi chỗ các chữ cái 1 cách bất kỳ để biến « TU KHOA BI AN » thành « UIKOB T AA HN ». Tôi sẽ giúp đỡ các bạn 1 chút trong bước này vì nó yêu cầu 1 số kiến thức các bạn vẫn chưa xem qua.
  3. Bước 3 có độ khó trung bình : chương trình cần lặp lại câu hỏi và sau đó mỗi lần so sánh câu trả lời với đáp án. Vòng lặp chỉ kết thúc khi câu trả lời và đáp án trùng nhau.

Viết bộ khung xử lý chương trình

Chúng ta đều biết mọi chương trình đều diễn ra trong hàm main(). Các bạn có thể bắt đầu viết bộ khung của chương trình sử dụng các bình luận, kiểu giống như hồi nhỏ viết dàn bài cho bài tập làm văn vậy.

Nó sẽ kiểu kiểu như sau.

int main(){
   //1 : Yeu cau nguoi choi nhap tu khoa

   //2 : Dao lon vi tri cac ky tu trong tu khoa

   //3 : On demande à l'utilisateur quel est le mot mystère

    return 0;
}

Chỉ còn việc thực hiện từng bước 1. Để thử với độ khó tăng dần, các bạn có thể viết bước 1 trước tiên, rồi đến bước 3 và bước 2 cuối cùng.

Sau đây là 1 ví dụ của chương trình « bán thành phẩm » sau khi bạn hoàn thành được bước 1 và bước 3.

Như các bạn đã thấy, chương trình của chúng ta vẫn còn thiếu việc đổi chỗ các chữ cái, thế nhưng cũng đã hoàn thành đến 50% rồi.

Trợ giúp nhỏ để đổi chỗ các chữ cái

Đây có thể được coi là phần khó nhất trong bài thực hành này. Đừng lo tôi sẽ không bỏ rơi các bạn đâu. Một vài gợi ý nho nhỏ sau đây có thể rất hữu ích.

Chọn 1 số bất kỳ

Để có thể đổi chỗ các chữ cái 1 cách hỗn loạn, trước tiên bạn phải biết cách làm sao để chọn được 1 số bất kỳ. Vì chúng ta vẫn chưa học cách làm điều này, tôi cần trước tiên giải thích cho các bạn 1 số thứ:

  • Bạn sẽ phải thêm ctimecstdlib vào đầu đoạn mã để có thể thao tác với các số ngẫu nhiên
  • Cần gọi hàm srand(time(0)); 1 lần duy nhất ở đầu chương trình để có thể khởi tạo các số ngẫu nhiên.

Cuối cùng, ví dụ để tạo ra 1 số ngẫu nhiên giữa 0 và 4 chẳng hạn, chúng ta cần viết soNgauNhien = rand()%5 ;

Đoạn mã ví dụ tạo ra số ngẫu nhiên giữa 0 và 4

#include <iostream>
#include <ctime>
#include <cstdlib>
using namespace std;

int main(){
   int soNgauNhien(0);
   srand(time(0));
   soNgauNhien = rand() % 5 ;
   return 0;
}

Chọn 1 ký tự bất kỳ trong chuỗi

? Có liên hệ gì giữa việc chọn 1 số bất kỳ và chọn 1 ký tự bất kỳ trong chuỗi ?

Chúng ta từng nói về việc 1 chuỗi ký tự string có thể được coi như 1 mảng để thao tác. Ví dụ như chúng ta có biến tuKhoa thì tuKhoa[0] sẽ là chữ thứ 1, tuKhoa[1] sẽ là chữ thứ 2, vv…

Vậy chúng ta chỉ cần chọn ra được 1 vị trí bất kỳ giữa 0tuKhoa.size() thì sẽ dễ dàng chọn ra 1 ký tự bất kỳ trong chuỗi đó. Ví dụ :

#include <iostream>
#include <string>
#include <ctime>
#include <cstdlib>
using namespace std;

int main(){
    string tu("TU KHOA BI AN");    
    srand(time(0));    
    int viTri = rand() % tu.size();        
    cout << "Ky tu bat ky :" << tu[viTri];
    return 0;
}

Xóa 1 ký tự khỏi chuỗi

Để tránh việc chọn 2 lần cùng 1 ký tự, tôi kiến nghị chúng ta xóa dần các ký tự ra khỏi chuỗi mỗi khi 1 ký tự được bốc trúng làm ký tự bất kỳ. Để làm điều này, cần phải sử dụng đến hàm erase(). Ví dụ :

tuKhoa.erase(4,1);

Hàm này có 2 thông số :

  • Vị trí mà ta bắt đầu muốn xóa, ở đây là 4 (nghĩa là bắt đầu từ chữ thứ 5 do chỉ số bắt đầu là 0)
  • Độ dài muốn xóa, ở đây là 1 vì chúng ta chỉ muốn xóa 1 ký tự
Tạo ra và sử dụng các hàm

Điều này là không bắt buộc nhưng vẫn tốt hơn là viết tất cả mã nguồn trong hàm main(). Các bạn có thể viết các hàm làm những thao tác cụ thể ví dụ như trong bước 2, việc tráo vị trí các ký tự có thể viết dưới dạng 1 hàm sau đó được sử dụng đến trong hàm main().

tuBiDaoViTri = daoViTriChuCai(tuKhoa);

Hàm này nhận vào 1 tuKhoa và trả về kết quả là tuBiDaoViTri. Tất cả khó khăn chỉ còn là viết mã xử lý của hàm daoViTriChuCai() này.

Cùng bắt tay vào thực hiện thôi !

Đáp án

Dưới đây sẽ là bài chữa.

Chắc hẳn các bạn đã bỏ ra kha khá thời gian để nghĩ về chương trình này. Đôi chỗ không hề đơn giản và bạn không biết phải giải quyết như thế nào. Không sao, quan trọng là bạn đã dám thử ! Đó là cách mà chúng ta tiến bộ.

Về cơ bản thì bước 1 và bước 3 là khá đơn giản với hầu hết mọi người, chỉ duy có bước 2 là có chút phức tạp. Tôi sẽ tách riêng nó ra thành 1 hàm và xử lý riêng như tôi đã đề nghị với các bạn ở bên trên.           

Mã nguồn

Sau đây là chương trình của tôi.

#include <iostream>
#include <string>
#include <ctime>
#include <cstdlib>

using namespace std;

string daoViTriChuCai(string tu){
   string tuBiDaoViTri;
   int viTri(0);

   //Khi ban con chua xoa het cac ky tu cua tu
   while (tu.size() != 0){
      //Cho 1 vi tri bat ky trong tu
      viTri = rand() % tu.size();
      //Them ky tu vao tu bi dao vi tri chu cai
      tuBiDaoViTri += tu[viTri];
      //Xoa ky tu vua dung de tranh chon mai ky tu nay lan nua
      tu.erase(viTri, 1);
    }

   return tuBiDaoViTri;
}

int main(){
   string tuKhoa, tuBiDaoViTri, cauTraLoi;

   srand(time(0));

   //1 : Yeu cau nguoi choi nhap tu khoa
   cout << "Hay nhap 1 tu : " << endl;
   getline (cin, tuKhoa);

   //2 : Dao lon vi tri cac ky tu trong tu khoa
   tuBiDaoViTri = daoViTriChuCai(tuKhoa);

   //3 : On demande à l'utilisateur quel est le mot mystère
   do{
      cout << endl << "Tu khoa bi an dang sau '" << tuBiDaoViTri << "' ?" << endl;
      getline (cin, cauTraLoi);

      if (cauTraLoi == tuKhoa){
         cout << "Hoan ho, ban da tra loi chinh xac !" << endl;
      }else{
         cout << "Suyt nua la dung roi, dung bo cuoc !" << endl;
      }
   }while (cauTraLoi != tuKhoa);
   //Chi dung lai khi nao tu khoa duoc tim ra

    return 0;
}

Hãy học cách đọc mã nguồn của chương trình 1 cách khoa học đó là đọc theo thứ tự các câu lệnh được thực hiện thay vì đọc từ trên xuống dưới. Chúng ta nên bắt đầu với hàm main() mà không phải hàm daoViTriChuCai().

Giải thích

Tôi sẽ giải thích cho các bạn từng bước một trong tiến trình của chương trình.

Bước 1 : Nhập từ khóa

Đây rõ là bước dễ nhất rồi : cout để in câu hỏi ra màn hình và cin hoặc getline() để lưu từ khóa nhập bới người dung vào biến tuKhoa. Các từ trong tiếng Việt thường có nhiều chữ cách nhau vởi dấu cách, vậy nên tôi đề nghị sử dụng getline().

Bước 2 : Đổi vị trí chữ cái

Bước này đã được tách riêng và đoạn mã xử lý được viết trong hàm daoViTriChuCai(). Hàm main() chỉ việc gọi nó và nhận kết quả trả về là 1 từ mà các chữ đều đã được đổi chỗ.

Phân tích đoạn mã bên trên, hàm daoViTriChuCai() tách ra từng ký tự 1 cách ngẫu nhiên từ từ khóa gốc và lần lượt thêm chúng vào chuỗi ký tự mà ta sẽ trả về làm kết quả của hàm. Vòng lặp được thực hiện tới khi nào tách hết các ký tự trong từ khóa.

while (tu.size() != 0){
      //Cho 1 vi tri bat ky trong tu
      viTri = rand() % tu.size();
      //Them ky tu vao tu bi dao vi tri chu cai
      tuBiDaoViTri += tu[viTri];
      //Xoa ky tu vua dung de tranh chon mai ky tu nay lan nua
      tu.erase(viTri, 1);
    }

Trong mỗi vòng lặp, ta chọn 1 vị trí bất kỳ nằm giữa 0 và số ký tự còn lại của từ khóa. Ta thêm ký tự vừa chọn được vào biến tuBiDaoViTri đồng thời xóa nó trong từ khóa để không ngẫu nhiên chọn lại lần thứ 2.

Sau khi tất cả các ký tự đều được tách ra hết, chúng ta kết thúc vòng lặp và trả về biến tuBiDaoViTri chứa các ký tự được sắp xếp bất kỳ.

Bước 3 : Cho người chơi đoán từ khóa gốc

Chúng ta sử dụng vòng lặp do… while trong bước này. Vòng lặp này mang đến cho ta 2 lợi ích :

  • Người chơi được trả lời ít nhất 1 lần.
  • Tiếp tục trò chơi chừng nào người chơi còn chưa đoán ra từ khóa với điều kiện while (cauTraLoi != tuKhoa).

Các thông điệp khác nhau sẽ được in ra màn hình tùy theo người chơi trả lời đúng hay sai.

Tài mã nguồn

Các bạn có thể tải mã nguồn của chương trình theo đường dẫn sau.

Tải mã nguồn

Tệp ZIP bao gồm :

  • main.cpp : tệp mã nguồn chính của chương trình (bắt buộc)
  • TuKhoaBiAn.cbp : tệp dự án Code::Block cho bạn nào dung IDE này (không bắt buộc)

Các bạn có thể tham khảo cũng như dung nó làm tiền đề để phát triển 1 số tính năng cho chương trình mà bạn muốn thêm vào.

Cải tiến chương trình

Chúng ta đến đây là đã hoàn thành mục tiêu của bài thực hành. Thế nhưng tôi biết là nhiều người vẫn chưa hài lòng và muốn cải tiến thêm nữa. Mỗi người có ý tưởng của riêng mình. Sau đây là 1 vài ý tưởng của tôi để hoàn thiện chương trình nếu có bạn nào muốn thử :

  • Thêm vào các dấu xuống dòng ở đầu chương trình : Trong trạng thái hiện tại, người chơi 2 có thể dễ dàng nhìn thấy từ khóa được nhập bởi người chơi 1. Các bạn có thể thêm nhiều dấu xuống dòng với endl để giấu đi từ khóa này trong console.
  • Bắt đầu lại 1 ván chơi khác : Chương trình gốc kết thúc sau 1 lần chơi. Bạn có thể thêm câu hỏi xem người chơi có muốn chơi tiếp không và sẽ kết thúc hoặc bắt đầu lại tùy vào câu trả lời của người chơi.
  • Hạn chế số lần trả lời : Người chơi 2 có thể bị hạn chế số lần trả lời để kích thích sự hấp dẫn.
  • Tình điểm trung bình qua các lần chơi : Sau nhiều ván, tính số lần đoán trung bình trước khi người chơi 2 tìm ra từ khóa đúng. Gợi ý là các bạn có thể dung đến mảng J
  • Thêm chế độ chơi 1 người : Lấy từ khóa từ 1 tệp có sẵn và yêu cầu người chơi đoán xem đó là từ gì. Tệp này càng có nhiều từ thì trò chơi sẽ càng thêm biến hóa.

Cuối cùng, đừng ngại thử ý tưởng của chính các bạn để xem liệu bản thân bạn có thể đi xa tới đâu.