Thiết kế hướng đối tượng cho trò chơi Cờ vua [đã đóng]


88

Tôi đang cố gắng tìm hiểu cách thiết kế và suy nghĩ theo hướng Đối tượng và muốn nhận được một số phản hồi từ cộng đồng về chủ đề này. Sau đây là một ví dụ về trò chơi cờ vua mà tôi muốn thiết kế theo cách OO. Đây là một thiết kế rất rộng và trọng tâm của tôi ở giai đoạn này chỉ là xác định ai chịu trách nhiệm về những thông điệp nào và cách các đối tượng tương tác với nhau để mô phỏng trò chơi. Vui lòng chỉ ra nếu có các yếu tố của thiết kế xấu (khớp nối cao, tính liên kết kém, v.v.) và cách cải thiện chúng.

Trò chơi Cờ vua có các lớp sau

  • Bảng
  • Người chơi
  • Cái
  • Quảng trường
  • Trò chơi cờ vua

Board được tạo thành từ các hình vuông và vì vậy Board có thể được thực hiện chịu trách nhiệm tạo và quản lý các đối tượng Square. Mỗi mảnh cũng nằm trên một hình vuông nên mỗi mảnh cũng có một tham chiếu đến hình vuông mà nó nằm trên. (Điều này có nghĩa không?). Sau đó, mỗi mảnh có nhiệm vụ tự di chuyển từ hình vuông này sang hình vuông khác. Lớp người chơi nắm giữ các tham chiếu đến tất cả các mảnh mà anh ta sở hữu và cũng chịu trách nhiệm về việc tạo ra chúng (Người chơi có nên tạo các mảnh không?). Người chơi có phương thức takeTurn mà lần lượt gọi một phương thức moveP mảnh thuộc Class mảnh, nó thay đổi vị trí của mảnh từ vị trí hiện tại sang vị trí khác. Bây giờ tôi đang bối rối về những gì chính xác lớp Board phải chịu trách nhiệm. Tôi cho rằng cần phải xác định trạng thái hiện tại của trò chơi và biết khi nào trò chơi kết thúc. Nhưng khi một mảnh thay đổi nó ' vị trí của hội đồng quản trị nên được cập nhật như thế nào? nó có nên duy trì một mảng ô vuông riêng biệt trên đó các mảnh tồn tại và nhận được cập nhật khi các mảnh di chuyển không?

Ngoài ra, ChessGame còn tạo ra các đối tượng Bảng và người chơi, những người này lần lượt tạo ra các hình vuông và quân cờ tương ứng và bắt đầu mô phỏng. Tóm lại, đây có thể là mã trong ChessGame có thể trông như thế nào

Player p1 =new Player();
Player p2 = new Player();

Board b = new Board();

while(b.isGameOver())
{
  p1.takeTurn(); // calls movePiece on the Piece object
  p2.takeTurn();

}

Tôi không rõ trạng thái của hội đồng quản trị sẽ được cập nhật như thế nào. Mảnh có nên tham chiếu đến hội đồng quản trị? Trách nhiệm nên nằm ở đâu? Ai nắm giữ tài liệu tham khảo? Vui lòng giúp tôi với đầu vào của bạn và chỉ ra các vấn đề trong thiết kế này. Tôi cố tình không tập trung vào bất kỳ thuật toán hoặc chi tiết nào khác của trò chơi vì tôi chỉ quan tâm đến khía cạnh thiết kế. Tôi hy vọng cộng đồng này có thể cung cấp những hiểu biết có giá trị.


3
Nitpicky nhận xét: p2 không nên gọi takeTurn()nếu nước đi của p1 kết thúc trò chơi. Bình luận ít khó hiểu hơn: Tôi thấy tự nhiên hơn khi gọi các cầu thủ whiteblack.
Kristopher Johnson

Đã đồng ý. Nhưng như tôi đã nói, tôi quan tâm hơn đến các khía cạnh thiết kế và những gì Đối tượng phải chịu trách nhiệm cho những hành động nào và ai nắm giữ những tham chiếu nào.
Sid

Tôi đã làm như bạn đã nêu ở trên trong đoạn mã của bạn. Trong cách triển khai của tôi, mỗi phần đều có bản sao bên trong của vị trí hoàn chỉnh vì nó sẽ sử dụng nó trong canMove()chức năng riêng của nó . Và khi việc di chuyển được thực hiện, tất cả các quân cờ khác cập nhật bản sao bên trong của chính chúng trên bàn cờ. Tôi biết nó không tối ưu, nhưng thật thú vị khi học C ++. Sau đó, một người bạn không phải là người chơi cờ vua nói với tôi rằng anh ấy sẽ classeschọn từng ô vuông thay vì từng quân cờ. Và nhận xét đó đánh vào tôi là rất thú vị.
eigenfield

Câu trả lời:


54

Tôi thực sự chỉ viết một triển khai C # đầy đủ của một bàn cờ, quân cờ, quy tắc, v.v. Đây là cách tôi mô hình hóa nó (triển khai thực tế đã bị loại bỏ vì tôi không muốn làm mất hết niềm vui khi viết mã của bạn):

public enum PieceType {
    None, Pawn, Knight, Bishop, Rook, Queen, King
}

public enum PieceColor {
    White, Black
}

public struct Piece {
    public PieceType Type { get; set; }
    public PieceColor Color { get; set; }
}

public struct Square {
    public int X { get; set; }
    public int Y { get; set; }

    public static implicit operator Square(string str) {
        // Parses strings like "a1" so you can write "a1" in code instead
        // of new Square(0, 0)
    }
}

public class Board {
    private Piece[,] board;

    public Piece this[Square square] { get; set; }

    public Board Clone() { ... }
}

public class Move {
    public Square From { get; }
    public Square To { get; }
    public Piece PieceMoved { get; }
    public Piece PieceCaptured { get; }
    public PieceType Promotion { get; }
    public string AlgebraicNotation { get; }
}

public class Game {
    public Board Board { get; }
    public IList<Move> Movelist { get; }
    public PieceType Turn { get; set; }
    public Square? DoublePawnPush { get; set; } // Used for tracking valid en passant captures
    public int Halfmoves { get; set; }

    public bool CanWhiteCastleA { get; set; }
    public bool CanWhiteCastleH { get; set; }
    public bool CanBlackCastleA { get; set; }
    public bool CanBlackCastleH { get; set; }
}

public interface IGameRules {
    // ....
}

Ý tưởng cơ bản là Game / Board / etc chỉ đơn giản là lưu trữ trạng thái của trò chơi. Bạn có thể thao tác chúng để thiết lập một vị trí, nếu đó là những gì bạn muốn. Tôi có một lớp triển khai giao diện IGameRules của mình chịu trách nhiệm về:

  • Xác định những bước di chuyển nào là hợp lệ, bao gồm nhập thành và vượt qua.
  • Xác định xem một động thái cụ thể có hợp lệ hay không.
  • Xác định thời điểm người chơi đang ở trong tình trạng kiểm tra / kiểm tra / bế tắc.
  • Thực hiện các động tác.

Tách các quy tắc khỏi các lớp trò chơi / bảng cũng có nghĩa là bạn có thể triển khai các biến thể tương đối dễ dàng. Tất cả các phương thức của giao diện quy tắc lấy một Gameđối tượng mà chúng có thể kiểm tra để xác định nước đi nào là hợp lệ.

Lưu ý rằng tôi không lưu trữ thông tin người chơi trên Game. Tôi có một lớp riêng Tablechịu trách nhiệm lưu trữ siêu dữ liệu trò chơi như ai đang chơi, thời điểm trò chơi diễn ra, v.v.

CHỈNH SỬA: Lưu ý rằng mục đích của câu trả lời này không thực sự cung cấp cho bạn mã mẫu mà bạn có thể điền vào - mã của tôi thực sự có thêm một chút thông tin được lưu trữ trên mỗi mục, nhiều phương pháp hơn, v.v. Mục đích là để hướng dẫn bạn mục tiêu bạn đang cố gắng đạt được.


1
Cảm ơn bạn đã trả lời chi tiết. Tuy nhiên, tôi có một số câu hỏi liên quan đến thiết kế. Ví dụ, không rõ ràng ngay lập tức tại sao Move phải là một lớp. Trọng tâm duy nhất của tôi là phân công trách nhiệm và quyết định sự tương tác giữa các lớp theo cách rõ ràng nhất có thể. Tôi muốn biết "tại sao" đằng sau bất kỳ quyết định thiết kế nào. Tôi không rõ bạn đã đưa ra quyết định thiết kế như thế nào và tại sao chúng lại là những lựa chọn tốt.
Sid

Movelà một lớp học để bạn có thể lưu trữ toàn bộ lịch sử di chuyển trong một danh sách di chuyển, với ký hiệu và thông tin phụ trợ như những gì mảnh bị bắt, những gì một cầm đồ có thể được thăng vv
cdhowie

@cdhowie Có phải việc Gamexóa bỏ người triển khai IGameRuleshoặc bạn thực thi các quy tắc bên ngoài đối tượng không? Cái thứ hai có vẻ không hợp lệ vì trò chơi không thể bảo vệ trạng thái của chính nó phải không?
plalx

1
Điều này có thể là ngu ngốc, nhưng không nên biến lớp Turn trong Game thuộc loại PieceColor thay vì PieceType?
Dennis van Gils

1
@nikhil Chúng cho biết hướng nào cả hai người chơi vẫn có thể lâu đài (theo hướng tệp A và H). Các giá trị này bắt đầu đúng. Nếu quân A của màu trắng di chuyển, CanWhiteCastleA bị sai, và tương tự đối với quân H. Nếu vua của trắng di chuyển, cả hai đều bị sai. Và quá trình tương tự đối với màu đen.
cdhowie

6

Đây là ý tưởng của tôi, cho một trò chơi cờ khá cơ bản:

class GameBoard {
 IPiece config[8][8];  

 init {
  createAndPlacePieces("Black");
  createAndPlacePieces("White");
  setTurn("Black");

 }

 createAndPlacePieces(color) {
   //generate pieces using a factory method
   //for e.g. config[1][0] = PieceFactory("Pawn",color);
 }

 setTurn(color) {
   turn = color;
 }

 move(fromPt,toPt) {
  if(getPcAt(fromPt).color == turn) {
    toPtHasOppositeColorPiece = getPcAt(toPt) != null && getPcAt(toPt).color != turn;
    possiblePath = getPcAt(fromPt).generatePossiblePath(fromPt,toPt,toPtHasOppositeColorPiece);
   if(possiblePath != NULL) {
      traversePath();
      changeTurn();
   }
  }
 } 

}

Interface IPiece {
  function generatePossiblePath(fromPt,toPt,toPtHasEnemy);
}

class PawnPiece implements IPiece{
  function generatePossiblePath(fromPt,toPt,toPtHasEnemy) {
    return an array of points if such a path is possible
    else return null;
  }
}

class ElephantPiece implements IPiece {....}

0

Gần đây tôi đã tạo một chương trình cờ vua bằng PHP ( nhấp vào trang web tại đây , nguồn nhấp vào đây ) và tôi đã tạo nó theo hướng đối tượng. Đây là các lớp tôi đã sử dụng.

  • ChessRulebook (tĩnh) - Tôi đặt tất cả generate_legal_moves()mã của mình vào đây. Phương pháp đó được đưa ra một bảng, đến lượt của nó, và một số biến để đặt mức độ chi tiết của kết quả và nó tạo ra tất cả các nước đi hợp pháp cho vị trí đó. Nó trả về một danh sách các ChessMoves.
  • ChessMove - Lưu trữ mọi thứ cần thiết để tạo ký hiệu đại số , bao gồm hình vuông bắt đầu, hình vuông kết thúc, màu sắc, loại quân cờ , chụp, kiểm tra, đối chiếu, loại quân cờ thăng hạng và người vượt qua. Các biến bổ sung tùy chọn bao gồm định hướng (đối với các bước di chuyển như Rae4), nhập thành và hội đồng quản trị.
  • ChessBoard - Lưu trữ thông tin tương tự như Chess FEN , bao gồm một mảng 8x8 đại diện cho các ô vuông và lưu trữ các Quân cờ, lần lượt của nó, vi ô mục tiêu thụ động, quyền nhập thành, đồng hồ hiệp và đồng hồ toàn thời gian.
  • ChessPeces - Lưu trữ loại quân cờ, màu sắc, hình vuông và giá trị quân cờ (ví dụ: quân tốt = 1, quân = 3, quân = 5, v.v.)
  • ChessSquare - Lưu trữ thứ hạng và tệp, dưới dạng ints.

Tôi hiện đang cố gắng biến mã này thành AI cờ vua, vì vậy nó cần phải NHANH CHÓNG. Tôi đã tối ưu hóa generate_legal_moves()chức năng từ 1500ms xuống 8ms và vẫn đang làm việc trên nó. Bài học tôi học được từ đó là ...

  • Không lưu trữ toàn bộ ChessBoard trong mỗi ChessMove theo mặc định. Chỉ cất bảng khi di chuyển khi cần thiết.
  • Sử dụng các kiểu nguyên thủy chẳng hạn như intkhi có thể. Đó là lý do tại sao ChessSquarecác cửa hàng xếp hạng và lưu trữ dưới dạng int, thay vì lưu trữ cả chữ và số stringvới ký hiệu ô vuông cờ vua có thể đọc được của con người chẳng hạn như "a4".
  • Chương trình tạo ra hàng chục nghìn ChessSquares khi tìm kiếm cây di chuyển. Tôi có thể sẽ cấu trúc lại chương trình để không sử dụng ChessSquares, điều này sẽ giúp tăng tốc độ.
  • Không tính toán bất kỳ biến không cần thiết nào trong các lớp của bạn. Ban đầu, tính toán FEN trong mỗi Cờ vua của tôi đã thực sự giết chết tốc độ của chương trình. Tôi đã phải tìm ra điều này với một hồ sơ .

Tôi biết điều này là cũ, nhưng hy vọng nó sẽ giúp ích cho ai đó. Chúc may mắn!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.