การออกแบบเชิงวัตถุสำหรับเกมหมากรุก [ปิด]


88

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

เกมหมากรุกมีคลาสดังต่อไปนี้

  • คณะกรรมการ
  • ผู้เล่น
  • ชิ้น
  • สแควร์
  • เกมหมากรุก

คณะกรรมการประกอบด้วยสี่เหลี่ยมดังนั้นคณะกรรมการจึงมีหน้าที่รับผิดชอบในการสร้างและจัดการวัตถุทรงสี่เหลี่ยม แต่ละชิ้นยังอยู่ในรูปสี่เหลี่ยมจัตุรัสดังนั้นแต่ละชิ้นจึงมีการอ้างอิงถึงสี่เหลี่ยมจัตุรัสด้วย (สิ่งนี้สมเหตุสมผลหรือไม่?) จากนั้นแต่ละชิ้นจะต้องรับผิดชอบในการเคลื่อนย้ายตัวเองจากตารางหนึ่งไปยังอีกสี่เหลี่ยมหนึ่ง คลาสผู้เล่นมีการอ้างอิงถึงชิ้นส่วนทั้งหมดที่เขาเป็นเจ้าของและยังรับผิดชอบในการสร้างของพวกเขา (ผู้เล่นควรสร้างชิ้นส่วนหรือไม่) ผู้เล่นมีวิธี TakeTurn ซึ่งจะเรียกวิธีการ movePiece ซึ่งเป็นของคลาสชิ้นส่วนซึ่งเปลี่ยนตำแหน่งของชิ้นส่วนจากตำแหน่งปัจจุบันไปยังตำแหน่งอื่น ตอนนี้ผมสับสนว่าคณะกรรมการต้องรับผิดชอบอะไรกันแน่ ฉันคิดว่าจำเป็นต้องกำหนดสถานะปัจจุบันของเกมและรู้ว่าเมื่อเกมจบลง แต่เมื่อชิ้นส่วนเปลี่ยนมัน ' ตำแหน่งของบอร์ดควรปรับปรุงอย่างไร ควรรักษาอาร์เรย์ของสี่เหลี่ยมที่แยกจากกันซึ่งชิ้นส่วนใดมีอยู่และได้รับการอัปเดตเมื่อชิ้นส่วนเคลื่อนที่หรือไม่

นอกจากนี้ ChessGame ยังสร้างบอร์ดและวัตถุของผู้เล่นอย่างตั้งใจซึ่งจะสร้างสี่เหลี่ยมและชิ้นส่วนตามลำดับและเริ่มการจำลอง สั้น ๆ นี่อาจเป็นลักษณะของโค้ดใน ChessGame

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();

}

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


3
ความคิดเห็นของ Nitpicky: p2 ไม่ควรโทรtakeTurn()หากการเคลื่อนไหวของ p1 จบเกม ความคิดเห็น nitpicky หัก: ฉันคิดว่ามันเป็นธรรมชาติมากขึ้นที่จะเรียกผู้เล่นและwhite black
Kristopher Johnson

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

ฉันชอบตามที่คุณระบุไว้ข้างต้นในข้อมูลโค้ดของคุณ ในการใช้งานของฉันแต่ละชิ้นมีสำเนาภายในของตำแหน่งที่สมบูรณ์เนื่องจากจะใช้ในcanMove()หน้าที่ของตัวเอง และเมื่อการย้ายเสร็จสิ้นชิ้นส่วนอื่น ๆ ทั้งหมดจะอัปเดตสำเนาด้านในของบอร์ด ฉันรู้ว่ามันไม่ดีที่สุด แต่ตอนนั้นก็น่าสนใจในการเรียนรู้ C ++ ต่อมาเพื่อนที่ไม่ใช่ผู้เล่นหมากรุกบอกฉันว่าเขาจะมีclassesสำหรับแต่ละตารางแทนที่จะเป็นแต่ละชิ้น และความคิดเห็นนั้นทำให้ฉันน่าสนใจมาก
eigenfield

คำตอบ:


54

จริงๆแล้วฉันเพิ่งเขียนการใช้งาน C # เต็มรูปแบบของกระดานหมากรุกชิ้นส่วนกฎ ฯลฯ นี่คือวิธีคร่าวๆที่ฉันจำลอง (การใช้งานจริงถูกลบออกเนื่องจากฉันไม่ต้องการเอาความสนุกทั้งหมดออกจากการเขียนโค้ดของคุณ):

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 {
    // ....
}

แนวคิดพื้นฐานคือ Game / Board / etc เป็นเพียงการจัดเก็บสถานะของเกม คุณสามารถจัดการพวกมันเพื่อเช่นตั้งตำแหน่งถ้านั่นคือสิ่งที่คุณต้องการ ฉันมีคลาสที่ใช้อินเทอร์เฟซ IGameRules ของฉันที่รับผิดชอบ:

  • การพิจารณาว่าการเคลื่อนไหวใดที่ถูกต้องรวมถึงการร่ายเวทและ en passant
  • การพิจารณาว่าการย้ายเฉพาะนั้นถูกต้องหรือไม่
  • การกำหนดเวลาที่ผู้เล่นอยู่ในการตรวจสอบ / รุกฆาต / ทางตัน
  • ดำเนินการย้าย

การแยกกฎออกจากคลาสเกม / กระดานยังช่วยให้คุณสามารถปรับใช้รูปแบบต่างๆได้อย่างง่ายดาย วิธีการทั้งหมดของอินเทอร์เฟซกฎใช้Gameวัตถุที่สามารถตรวจสอบเพื่อพิจารณาว่าการเคลื่อนไหวใดถูกต้อง

โปรดทราบว่าฉันไม่ได้จัดเก็บข้อมูลผู้เล่นบนGame. ฉันมีคลาสแยกต่างหากTableที่รับผิดชอบในการจัดเก็บข้อมูลเมตาของเกมเช่นใครกำลังเล่นอยู่เมื่อเกมเกิดขึ้นเป็นต้น

แก้ไข:โปรดทราบว่าจุดประสงค์ของคำตอบนี้ไม่ได้มีไว้เพื่อให้รหัสเทมเพลตที่คุณสามารถกรอกได้จริง ๆ แล้วรหัสของฉันมีข้อมูลเพิ่มเติมเล็กน้อยที่เก็บไว้ในแต่ละรายการวิธีการเพิ่มเติม ฯลฯ จุดประสงค์คือเพื่อแนะนำคุณไปสู่ เป้าหมายที่คุณพยายามบรรลุ


1
ขอบคุณสำหรับการตอบกลับโดยละเอียด อย่างไรก็ตามฉันมีคำถามเล็กน้อยเกี่ยวกับการออกแบบ ตัวอย่างเช่นไม่ชัดเจนในทันทีว่าทำไม Move จึงควรเป็นคลาส สิ่งเดียวที่ฉันมุ่งเน้นคือการมอบหมายความรับผิดชอบและตัดสินใจการโต้ตอบระหว่างชั้นเรียนในลักษณะที่ชัดเจนที่สุด ฉันต้องการทราบ "เหตุผล" เบื้องหลังการตัดสินใจออกแบบใด ๆ ฉันไม่ชัดเจนว่าคุณมาถึงการตัดสินใจออกแบบที่คุณทำได้อย่างไรและเหตุใดจึงเป็นตัวเลือกที่ดี
Sid

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

@cdhowie เป็นการGameเบี่ยงเบนไปยังผู้ดำเนินการIGameRulesหรือคุณบังคับใช้กฎภายนอกวัตถุหรือไม่? หลังดูเหมือนไร้เดียงสาเนื่องจากเกมไม่สามารถปกป้องสถานะของตัวเองได้หรือไม่?
plalx

1
นี่อาจจะโง่ แต่ไม่ควรเปลี่ยนในคลาสเกมเป็นประเภท PieceColor แทนที่จะเป็น PieceType?
Dennis van Gils

1
@nikhil พวกเขาระบุว่าทิศทางของผู้เล่นทั้งสองอาจยังคงปราสาท (ไปยังไฟล์ A และ H) ค่าเหล่านี้เริ่มต้นจากจริง ถ้า A rook ของสีขาวเคลื่อนที่ CanWhiteCastleA จะเป็นเท็จและเช่นเดียวกันกับ H rook หากราชันชุดขาวเคลื่อนไหวทั้งคู่จะเป็นเท็จ และกระบวนการเดียวกันสำหรับสีดำ
cdhowie

6

นี่คือความคิดของฉันสำหรับเกมหมากรุกขั้นพื้นฐาน:

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

ฉันเพิ่งสร้างโปรแกรมหมากรุกใน PHP ( เว็บไซต์คลิกที่นี่ , แหล่งที่มาคลิกที่นี่ ) และฉันทำให้มันเชิงวัตถุ นี่คือคลาสที่ฉันใช้

  • ChessRulebook (คงที่) - ฉันใส่generate_legal_moves()รหัสทั้งหมดที่นี่ วิธีการนั้นจะได้รับบอร์ดซึ่งเป็นตาแหน่งและตัวแปรบางตัวเพื่อกำหนดระดับรายละเอียดของผลลัพธ์และสร้างการเคลื่อนไหวทางกฎหมายทั้งหมดสำหรับตำแหน่งนั้น จะส่งคืนรายการ ChessMoves
  • ChessMove - จัดเก็บทุกสิ่งที่จำเป็นในการสร้างสัญกรณ์เกี่ยวกับพีชคณิตรวมถึงสี่เหลี่ยมเริ่มต้นสี่เหลี่ยมจัตุรัสสิ้นสุดสีประเภทชิ้นส่วนการจับตรวจสอบรุกฆาตประเภทชิ้นส่วนโปรโมชั่นและ en passant ตัวแปรเพิ่มเติมที่เป็นทางเลือก ได้แก่ การลดความสับสน (สำหรับการเคลื่อนไหวเช่น Rae4) การเหวี่ยงและการขึ้นเครื่อง
  • ChessBoard - จัดเก็บข้อมูลเช่นเดียวกับChess FENซึ่งรวมถึงอาร์เรย์ 8x8 ที่แสดงถึงช่องสี่เหลี่ยมและจัดเก็บ ChessPieces ซึ่งถึงคราวของมันคือสี่เหลี่ยมจัตุรัสเป้าหมายที่ผ่านไปสิทธิ์ในการร่ายนาฬิกาแบบ Halfmove และนาฬิกาแบบ Fullmove
  • ChessPiece - เก็บประเภทชิ้นส่วนสีสี่เหลี่ยมและมูลค่าชิ้น (ตัวอย่างเช่นเบี้ย = 1, อัศวิน = 3, rook = 5 เป็นต้น)
  • ChessSquare - เก็บอันดับและไฟล์เป็นints

ฉันกำลังพยายามเปลี่ยนรหัสนี้ให้เป็น AI หมากรุกดังนั้นจึงต้องเร็ว ฉันได้ปรับgenerate_legal_moves()ฟังก์ชั่นให้เหมาะสมจาก 1500ms เป็น 8ms แล้วและฉันยังคงทำงานอยู่ บทเรียนที่ฉันได้เรียนรู้คือ ...

  • อย่าเก็บ ChessBoard ทั้งหมดไว้ใน ChessMove ทุกตัวตามค่าเริ่มต้น จัดเก็บบอร์ดในการเคลื่อนย้ายเมื่อจำเป็นเท่านั้น
  • ใช้ประเภทดั้งเดิมเช่นintเมื่อเป็นไปได้ นั่นคือเหตุผลที่ChessSquareจัดเก็บอันดับและไฟล์เป็นintแทนที่จะจัดเก็บตัวอักษรและตัวเลขที่stringมีสัญกรณ์สี่เหลี่ยมหมากรุกที่มนุษย์อ่านได้เช่น "a4"
  • โปรแกรมสร้าง ChessSquares นับหมื่นเมื่อค้นหาทรีการย้าย ฉันอาจจะ refactor โปรแกรมเพื่อไม่ใช้ ChessSquares ซึ่งควรจะเพิ่มความเร็ว
  • อย่าคำนวณตัวแปรที่ไม่จำเป็นในชั้นเรียนของคุณ ในขั้นต้นการคำนวณ FEN ใน ChessBoards แต่ละตัวของฉันเป็นการฆ่าความเร็วของโปรแกรมจริงๆ ฉันได้ไปหานี้ด้วยProfiler

ฉันรู้ว่ามันเก่า แต่หวังว่ามันจะช่วยใครสักคน โชคดี!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.