Hướng dẫn lập trình trò chơi ô số

12 448 1
Hướng dẫn lập trình trò chơi ô số

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

I Giới thiệu: Vui lòng đọc II Thuật toán tìm kiếm A* A* giải thuật tìm kiếm thường sử dụng vấn đề liên quan đến đồ thị tìm đường Nó chọn không tính hiệu mà dễ dàng để hiểu cài đặt Bạn cần nắm rõ thuật toán trước tiếp tục Tôi giải sử bạn biết lý thuyết này, nhiên để tiện lợi cho việc tham khảo bạn đọc hai link bên dưới: – Giải thuật tìm kiếm A* – A* search algorithm III Phân tích toán – Như giới thiệu trước, có trạng thái bảng số chuyển trạng thái đích, ta gọi cấu hình hợp lệ không hợp lệ Tỉ lệ chúng ½, điều nhận dễ dàng từ phương pháp tính xem toán đưa trạng thái đích hay không – Rất dễ thấy trạng thái bảng số hoán vị m x m phần tử (với m cạnh), không gian trạng thái (m x m)!, với 8-puzzle 9! = 362880 (m = 3) 15-puzzle 16! = 20922789888000 (m =4) Bạn m tăng lên đơn vị không gian trạng thái tăng lên nhanh, điều khiến cho việc giải phiên m>3 áp dụng – Để áp dụng thuật toán A* giải toán này, bạn cần hàm heuristic h để ước lượng giá trị trạng thái bảng số Có số cách bạn biết tới tính dựa vào khoảng cách sai lệch ô số với vị trí đúng, đơn giản đếm xem có ô sai vị trí,… Ở chọn theo cách thứ nhất, tức tính tổng số ô sai lệch ô số so với vị trí Đây cách tính thường sử dụng có tên gọi Manhattan *Nếu bạn tìm kiếm mạng Manhattan bạn thấy tên quận thành phố New York, nơi mà đường chạy ngang dọc bàn cờ lớn, nhờ việc tìm di chuyển thuận lợi Tham khảo: http://en.wikipedia.org/wiki/Taxicab_geometry -Ví dụ: Tính khoảng cách Manhattan Trong bảng số 3×3 trên, để di chuyển ô số vào vị trí ta cần di chuyển lần, để di chuyển ô số vị trí ta cần cần lần (qua ô khác) Để có kết ta làm phép tính đơn giản: lấy tổng khoảng cách dòng cột hai vị trí (ví dụ với ô số 7): – Lấy tọa độ ô số ta có row1 = column1 = – Lấy tọa độ ô số vị trí đúng, ta có ow2 = column2 = – Vậy khoảng cách Manhattan hai ô là: |row1 – row2| + |column1 – column2| = |0 – 2| + |2 – 0| = Theo đó, ta tính h = 0+1+4+2+2+0+1+1+1 = 12 Như trạng thái đích bảng số có giá trị thấp *Từ giá trị ô số ta tính vị trí dòng cột sau: Ví dụ ô số có thứ tự bảng (tính từ với m cạnh) ta có row = / = 2, col = % = Vậy tổng quát lại ta có: RowIndex = Index / m ColIndex = Index % m IV Triển khai thuật toán A* Trước tiếp tục bạn nên có nhìn tổng quát lớp sử dụng project Sau giải thích vài đoạn code để bạn dễ hiểu Class Form Board thuộc phần giao diện, dùng lớp Board để hiển thị bảng số lên form Bạn dễ dàng sửa lại lớp để thay đổi cách hiển thị mà không làm ảnh hường đến hoạt động chương trình – Lớp Matrix: Đại diện cho bảng số, coi node tìm kiếm bạn cài đặt giải thuật • Value: mảng lưu bảng số • Score: lưu trữ giá trị từ hàm heuristic h bảng số • ComparingValue: giá trị dùng để so sánh phần tử Matrix OpenList, tổng Score StepCount (số nước từ trạng thái đến trạng thái tại) • Size: Độ lớn cạnh bảng số • Length: Tổng số phần tử bảng số • Parent: Lưu đối tượng cha, trạng thái trước trạng thái • Direction: đối tượng kiểu MoveDirection, lưu hướng di chuyển để từ trạng thái trước tới trạng thái • Clone(): phương thức tạo đối tượng Vì Matrix lớp có kiểu tham chiếu nên để tạo bạn gán biến với kiểu giá trị int, double,… • GetId(): Tạo id cho đối tượng, id dựa vào thứ tự xếp số mảng, dĩ nhiên với mảng khác id khác Việc dùng Id giúp ta kiểm tra tìm kiếm đối tượng dễ dàng chúng collection (Bạn nên cẩn thận cho kích thước bảng lớn vượt phạm vi kiểu int biến Id) • MakeMove(MoveDirection): thực “di chuyển” (hoán vị) bảng số dựa vào hướng di chuyển truyền vào • Shuffle(): Xáo trộn bảng số OpenList: Chứa đối tượng Matrix (node) duyệt tới, thêm – node vào danh sách Ta chèn vào vị trí cho OpenList xếp từ nhỏ đến lớn • idList: Danh sách cài đặt HashSet chứa id phần tử thêm vào, dùng để kiểm tra trước thêm xem OpenList có phần tử chưa Việc lưu trữ dùng mã “băm” giúp việc tìm kiếm nhanh so với dạng collection khác GameEngine: đối tượng quản lý chung hoạt động thuật toán, chứa – danh sách OPEN CLOSE Danh sách “solution” để lưu lại đường tìm từ trạng thái tới đích • Solve(): phương thức đế giải toán • Evaluate(): hàm lượng giá Heuristic tính giá trị bảng số • GenMove(Matrix): Sử dụng phương thức CloneMove(Matrix, MoveDirection) sinh nước từ node truyền vào Nếu node tạo tồn CLOSE kiểm tra cập nhật nước ngắn cho node • TrackPath(): Tạo danh sách nước từ trạng thái đến đích lưu vào đối tượng solution Lớp Matrix: Trong lớp thay sử dụng mảng hai chiều để lưu bảng số, sử dụng mảng chiều để so sánh Tuy điều có lợi lưu trữ giúp cho vài đoạn code viết dễ dàng làm số khác lại trở nên tốn chi phí Chính mà hiệu suất chương trình với việc dùng mảng chiều coi tương đương với mảng hai chiều Tuy nhiên phần cuối, cách để khắc phục điểm mảng chiều 10 11 12 internal void GetId() { this.Id = 0; int n = 1; for (int i = 0; i < Length - 1; i++) { if (_value[i] == BlankValue) Blank_Pos = i; this.Id += _value[i] * n; n *= 10; } } Phương thức gọi để tự tạo Id cho đối tượng Nó chuyển mảng chiều thành số có Length-1 chữ số (chữ số cuối không cần xét đến) theo thứ tự ngược lại bảng số Bạn không cần quan tâm đến thứ tự xếp Id ngược hay xuôi với bảng số chúng không ảnh hưởng 10 11 12 13 14 15 16 public bool CanMoveUp { get { return Blank_Pos > Size - 1; } } public bool CanMoveDown { get { return Blank_Pos < Length - Size; } } public bool CanMoveLeft { get { return GameEngine.IndexCols[Blank_Pos] > 0; } } public bool CanMoveRight { get { return GameEngine.IndexCols[Blank_Pos] < Size - 1; } } Các property viết dễ dàng bạn dùng mảng hai chiều Trong bảng sốSize*Size, để ô trống (Blank_Pos) di chuyển lên (hay xuống dưới), tức không nằm dòng (hoặc dòng cuối di chuyển xuống) bảng, phép kiểm tra đơn giản bạn thấy Tương tự với phép kiểm tra di chuyển trái/phải, ta lấy tọa độ ô trống chia dư cho Size để lấy tọa độ cột, cuối so sánh public override bool Equals(object obj) { Matrix m = obj as Matrix; if (m == null) return false; return this.Id.Equals(m.Id); } Phương thức cần override bạn muốn collection tìm kiếm đối tượng Matrix mà muốn Lớp GameEngine Các đoạn mã đưa phần giải thích vào dạng thích Phương thức kiểm tra toán có cấu hình hợp lệ không (có thể đưa dạng đích) 10 11 12 13 14 15 16 17 18 19 /// /// Kiểm tra xem puzzle chuyển dạng đích ko /// Xem thêm https://yinyangit.wordpress.com/2010/12/11/algorithm-tim-hi%E1%BB%83 /// /// /// public bool CanSolve(Matrix matrix) { int value = 0; for (int i = 0; i < matrix.Length; i++) { int t = matrix[i]; if (t > && t < matrix.BlankValue) { for (int m = i + 1; m < matrix.Length; m++) if (matrix[m] < t) value++; } } if (Size % == 1) { 20 21 22 23 24 25 26 27 28 29 30 31 32 33 return value % == 0; } else { } // Vị trí dòng tính từ int row = IndexRows[_matrix.Blank_Pos] + 1; return value % == row % 2; } Thuật toán giới thiệu trước, cách cài đặt đơn giản Ở phần so sánh cuối bạn có viết sau, chúng trả kết tương tự: int row = _matrix.Blank_Pos / Size ; return value % != row % 2; Phương thức Solve() để giải toán đơn giản: 10 11 12 13 14 15 16 17 18 19 20 public void Solve() { // Làm rỗng collection closeQ.Clear(); openQ.Clear(); Solution.Clear(); // Thêm phần tử vào OPEN this._matrix.Parent = null; this._matrix.Score = Evaluate(this._matrix); openQ.Add(this._matrix); while (openQ.Count > 0) { // Lấy node có giá trị (ComparingValue) nhỏ Matrix m = openQ[0]; // Kiểm tra xem có phải trạng thái đích if (m.Score == WIN_VALUE) { // Tạo solution TrackPath(m); return; } 21 22 23 24 25 26 27 28 29 30 } // Xóa node OPEN openQ.Remove(m); // Sinh node node m GenMove(m); } Và phương thức GenMove(): 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 /// /// Sinh nước /// /// private void GenMove(Matrix matrix) { Matrix m1; // node xét qua if (closeQ.ContainsKey(matrix.Id)) { m1 = closeQ[matrix.Id]; // Kiểm tra cập nhật có số nước node CLOSE if (matrix.StepCount < m1.StepCount) m1 = matrix; } else closeQ.Add(matrix.Id, matrix); // Sinh node if (matrix.Direction != MoveDirection.LEFT && matrix.CanMoveRight) { CloneMove(matrix, MoveDirection.RIGHT); } if (matrix.Direction != MoveDirection.UP && matrix.CanMoveDown) { CloneMove(matrix, MoveDirection.DOWN); } if (matrix.Direction != MoveDirection.RIGHT && matrix.CanMoveLeft) { CloneMove(matrix, MoveDirection.LEFT); } } if (matrix.Direction != MoveDirection.DOWN && matrix.CanMoveUp) { CloneMove(matrix, MoveDirection.UP); } 35 36 37 Trong đoạn mã sau: if (matrix.Direction != MoveDirection.LEFT && matrix.CanMoveRight) { CloneMove(matrix, MoveDirection.RIGHT); } Lý kiểm tra hướng node node cha bên phải việc sinh nước bên phải không cẩn thiết Ở direction hướng để node cha biến trở thành node Phương thức lượng giá heuristic: 10 11 12 public int Evaluate(Matrix matrix) { // Ô nằm sai vị trí bị cộng điểm khoảng cách ô đến vị trí int score = 0; for (int i = 0; i < matrix.Length; i++) { int value = matrix[i] - 1; score += Math.Abs(IndexRows[i] - IndexRows[value]) + Math.Abs(IndexCols[i] } return score; } Với mã nguồn tham khảo trên, bạn tự cài đặt project để giải toán tương tự Tuy nhiên tốc độ giải chương trình chưa thật làm hài lòng Vì viết thực vài ý tưởng nhỏ để cải tiến tốc độ, cập nhật vào mã nguồn đính kèm bên Bạn bỏ qua phần trình bày thấy không cần thiết V Một vài hướng cải thiện tốc độ chương trình – Hạn chế tính toán lặp lại nhiều lần: Trong chương trình này, ta khó cải tiến chương trình để vừa làm tăng tốc độ, vừa làm giảm bớt nhớ sử dụng Ở ta nhận thấy, với chương trình nhỏ dạng này, việc sử dụng nhớ thêm chút không ảnh hưởng gì, ta cần tốc độ tính toán Ví dụ, thử xem lại phương thức Evaluate() lớp GameEngine, ta phải thực tính toán để đổi từ giá trị sang hai giá trị dòng cột Các giá trị nằm khoảng từ đến độ dài mảng lưu trữ Mỗi lần tạo node ta lại tính lại phép tính tương tự Vậy để cải tiến, ta việc lưu trữ sẵn giá trị mảng sử dụng thông qua giá trị Tạo hai mảng int toàn cục lớp GameEngine: public static int[] IndexRows; public static int[] IndexCols; Tôi sử dụng public static để dùng lớp khác, bạn cảm thấy làm chương trình thêm rắc rối cần để private Trong property Size lớp GameEngine, ta khởi tạo giá trị cho hai mảng trên: 10 11 12 13 14 15 16 17 18 public int Size { get { return _size; } set { _size = value; _matrix = new Matrix(_size); int m = _size * _size; IndexRows = new int[m]; IndexCols = new int[m]; for (int i = 0; i < m; i++) { IndexRows[i] = i / _size; IndexCols[i] = i % _size; } } } 19 Bạn ta tính sẵn giá trị dòng cột phần tử bảng số Các giá trị tính lần Size gán nên bạn lo lắng lãng phí hay thừa thãi Mọi phần tử hai mảng dùng đến Ta sửa lại phương thức Evaluate() sau: 10 11 12 public int Evaluate(Matrix matrix) { int score = 0; for (int i = 0; i < matrix.Length; i++) { int value = matrix[i] – 1; score += Math.Abs(IndexRows[i] – IndexRows[value]) + Math.Abs(IndexCols[i] – } return score; } Bạn kiểm tra nhận thấy chúng khác biệt tốc độ so với phiên cũ Tuy nhiên bảng số có kích thước lớn, cải thiện thể ưu điểm rõ ràng Cụ thể tính toán phiên 15-puzzle, phương thức Evaluate() chạy nhanh lần so với cách cũ – Dùng mã lệnh ưu tiên tốc độ: Bạn tham khảo thêm viết cải thiện hiệu suất chương trình C# Ở nói “ưu tiên” tức ta phải chịu thiệt chút để bù lại tốc độ, thường làm mã nguồn khó hiểu hơn, thiếu tính OOP Ví dụ chương trình sử dụng properties ít, nguyên nhân truy xuất trực tiếp biến nhanh – Chấp nhận lời giải tương đối: Dĩ nhiên bạn quan tâm đến việc có tìm lời giải hay không, chất lượng lời giải không quan trọng bạn nên suy nghĩ đến hướng Bạn tìm thuật toán khác, chẳng hạn thuật toán tìm kiếm theo chiều sâu Depth-first search, Iterative deepening search Chẳng hạn project sử dụng A* bỏ vấn đề tìm đường ngắn (tức so sánh dựa vào giá trị hàm heuristic) kết tìm đường phiên 8-puzzle gần VI Lời kết Kết thúc viết này, bạn mở rộng toán giải bảng số có dạng mxn, cách làm tương tự nhiên bạn cần kiểm tra lại cách thức để kiểm tra xem trạng thái bảng số có hợp lệ không Chương trình tạm chấp nhận với mức 8-puzzle, nhiên muốn giải toán với n tương đối lớn ta cần tìm giải pháp khác [...]... thấy chúng không có sự khác biệt về tốc độ lắm so với phiên bản cũ Tuy nhiên khi bảng số có kích thước lớn, sự cải thiện này sẽ thể hiện ưu điểm của nó rõ ràng hơn Cụ thể khi tính toán trong phiên bản 15-puzzle, phương thức Evaluate() này chạy nhanh hơn 4 lần so với cách cũ – Dùng mã lệnh ưu tiên tốc độ: Bạn có thể tham khảo thêm bài viết của tôi về cải thiện hiệu suất chương trình C# Ở đây tôi nói “ưu... làm mã nguồn khó hiểu hơn, thiếu tính OOP Ví dụ như trong chương trình này tôi sử dụng properties rất ít, nguyên nhân là vì nếu truy xuất trực tiếp biến thì sẽ nhanh hơn – Chấp nhận lời giải tương đối: Dĩ nhiên nếu bạn chỉ quan tâm đến việc có tìm được lời giải hay không, còn chất lượng lời giải không quan trọng thì bạn nên suy nghĩ đến hướng này Bạn có thể đi tìm như những thuật toán khác, chẳng hạn... tôi vẫn sử dụng A* và bỏ đi vấn đề tìm đường đi ngắn nhất (tức là chỉ so sánh dựa vào giá trị của hàm heuristic) thì kết quả tìm đường đi của phiên bản 8-puzzle gần như ngay lập tức VI Lời kết Kết thúc bài viết này, bạn có thể mở rộng bài toán và giải được những bảng số có dạng mxn, cách làm cũng tương tự tuy nhiên bạn cần kiểm tra lại cách thức để kiểm tra xem trạng thái của bảng số có hợp lệ không...19 Bạn có thể ta tính sẵn giá trị dòng và cột của mọi phần tử trong bảng số Các giá trị này chỉ được tính một lần duy nhất mỗi khi Size được gán nên bạn không phải lo lắng về sự lãng phí hay thừa thãi của nó Mọi phần tử của hai mảng này đều được dùng đến Ta sửa lại phương thức Evaluate() như sau: 1 2 3 4 5 6 7 8 9... viết này, bạn có thể mở rộng bài toán và giải được những bảng số có dạng mxn, cách làm cũng tương tự tuy nhiên bạn cần kiểm tra lại cách thức để kiểm tra xem trạng thái của bảng số có hợp lệ không Chương trình này có thể tạm chấp nhận với mức 8-puzzle, tuy nhiên nếu muốn giải được bài toán với n tương đối lớn thì ta cần tìm một giải pháp khác ... mảng chiều thành số có Length-1 chữ số (chữ số cuối không cần xét đến) theo thứ tự ngược lại bảng số Bạn không cần quan tâm đến thứ tự xếp Id ngược hay xuôi với bảng số chúng không ảnh hưởng 10... dòng cột hai vị trí (ví dụ với ô số 7): – Lấy tọa độ ô số ta có row1 = column1 = – Lấy tọa độ ô số vị trí đúng, ta có ow2 = column2 = – Vậy khoảng cách Manhattan hai ô là: |row1 – row2| + |column1...Tính khoảng cách Manhattan Trong bảng số 3×3 trên, để di chuyển ô số vào vị trí ta cần di chuyển lần, để di chuyển ô số vị trí ta cần cần lần (qua ô khác) Để có kết ta làm phép tính đơn giản:

Ngày đăng: 06/12/2015, 18:06

Từ khóa liên quan

Mục lục

  • I.   Giới thiệu:

  • II. Thuật toán tìm kiếm A*

  • III. Phân tích bài toán

  • IV.  Triển khai thuật toán A*

  • V. Một vài hướng cải thiện tốc độ chương trình

  • VI. Lời kết

Tài liệu cùng người dùng

Tài liệu liên quan