web analytics

Flutter Project : สร้างเกมหมากฮอส ด้วย Flutter

สวัสดีผู้อ่านทุกท่านครับ ช่วงนี้มีเรื่องให้เครียดหลายเรื่อง มีวิธีนึงที่ผมใช้บ่อย คือการหาเกมอะไรมาเขียน ได้คิด logic ของเกมไปเรื่อยๆ สนุกดี บล็อกนี้จะบันทึกการทำเกม หมากฮอส ด้วย Flutter ครับ ซึ่งก็น่าจะเป็นเกมที่ทุกคนเล่นเป็น รู้กติกากันอยู่แล้ว

จะว่าไปแล้ว เจ้าเกมหมากฮอสนี้ผมก็เขียนไปแล้วรอบนึงนะ ตอนนั้นเรียนอยู่ ปี 3 โดยทำเป็นโปรแกรม Windows Form เขียนด้วย C# อีกอย่างคือ มีเกมหมากหนีบอยู่ในโปรแกรมเดียวกัน เล่นแบบ 2 in 1 ไปเลย

เริ่มต้น

ตัวเกมเป็นเกมกระดาน ดังนั้นเราเก็บข้อมูลกระดานในรูปแบบ 2 มิติ 8×8
สามารถเขียน Flutter วาดตารางแบบง่ายได้ประมาณนี้

  buildGameTable() {
    List<Widget> listCol = List();
    for (int row = 0; row < widget.COUNT_ROW; row++) {
      List<Widget> listRow = List();
      for (int col = 0; col < widget.COUNT_COL; col++) {
        listRow.add(buildBlockContainer());
      }

      listCol.add(Row(mainAxisSize: MainAxisSize.min,
          children: listRow));
    }

    return Container(padding: EdgeInsets.all(6),
        color: Colors.grey[400],
        child: Column(mainAxisSize: MainAxisSize.min,
            children: listCol));
  }

  Widget buildBlockContainer() {
    return Container(width: 42, height: 42, color: Colors.grey[100],
        margin: EdgeInsets.all(2));
  }

ลองกำหนดใส่สีในตาราง ใส่แบบสลับกัน โดยผมจะเรียกช่องที่หมากสามารถเดินได้คือ T และช่องที่ไม่สามารถเดินได้คือ F

 Widget buildBlockContainer(int row,int col) {
    Color colorBackground;
    if (isBlockTypeF(row, col)) {
      colorBackground = widget.colorBackgroundF;
    } else {
      colorBackground = widget.colorBackgroundT;
    }

    return Container(width: 42, height: 42, color: colorBackground,
        margin: EdgeInsets.all(2));
  }

  bool isBlockTypeF(int row, int col) {
    return (row % 2 == 0 && col % 2 == 0) || (row % 2 == 1 && col % 2 == 1);
  }

ต่อมากำหนดหมากลงในตาราง ผมเรียกหมากว่า Men และหากตัวไหนเป็นฮอสผมดันเรียกว่า King เอ้อ นึกว่ากำลังเล่นไพ่

  void initMenOnTable() {
    listMenPlayer1 = List();
    listMenPlayer2 = List();
    initMenOnTableRow(player: 1, row: 0);
    initMenOnTableRow(player: 1, row: 1);
    initMenOnTableRow(player: 2, row: widget.COUNT_ROW - 2);
    initMenOnTableRow(player: 2, row: widget.COUNT_ROW - 1);
  }

  void initMenOnTableRow({int player = 1, int row = 0}) {
    for (int col = 0; col < widget.COUNT_COL; col++) {
      if (!isBlockTypeF(row, col)) {
        List<Men> listMen = player == 1 ? listMenPlayer1 : listMenPlayer2;
        Men men = Men(player: player, coordinate: Coordinate(row: row, col: col));
        listMen.add(men);
        table[row][col].men = men;
      }
    }
  }
Widget buildBlockContainer(int row, int col) {

    ...

    Widget menWidget;
    if (table[row][col].men != null) {
      menWidget = Center(child: Container(width: 32, height: 32,
          decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.grey[200])));
    }else{
      menWidget = Container();
    }

    return Container(
        width: 42,
        height: 42,
        color: colorBackground,
        margin: EdgeInsets.all(2),
        child: menWidget);
  }

กำหนดให้ player1 อยู่ข้างบนเป็นสีดำ

Widget buildBlockContainer(int row, int col) {
  ..
  if (table[row][col].men != null) {
    menWidget = Center(child: Container(width: 32, height: 32,
          decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: men.player == 1 ? Colors.black54 : Colors.grey[100])));
}

จากนั้นเพิ่ม drag ให้กับตัวหมาก ใช้ Widget ที่ชื่อว่า Draggable (เรื่อง draggable นี้ผมก็เขียนบล็อกไว้ด้วยนะ) โดยเราจะต้องกำหนดข้อมูลของหมากใส่ลงไปกับ Draggable นั่นเอง

class _MyGamePageState extends State<MyGamePage> {

  int currentPlayerTurn = 2;
if (table[row][col].men != null) {
      Men men = table[row][col].men;
      ..

      if(men.player == currentPlayerTurn) {
        menWidget = Draggable(
            child: menWidget,
            feedback: menWidget,
            childWhenDragging: Container(),
            data: men);
      }
    }

หากยังไม่ได้อ่านเรื่อง Draggable สามารถอ่านเรื่อง Draggable ได้ที่ลิงค์นี้ครับ

เมื่อทำ Draggable แล้ว เราก็ต้องกำหนด DragTarget ของช่องต่างๆด้วย ซึ่งเงื่อนไขที่จะมี DragTarget คือ ต้องเป็นช่องแบบ T เท่านั้นคือวางหมากได้ และต้องไม่มีหมากอื่นวางอยู่ พอเราใส่ DragTarget แล้ว ก็มากำหนดเงื่อนไขกรณีที่ผู้เล่นลากหมากมาวาง เราจะยอมรับหรือไม่ เงื่อนไขก็คือ จะต้องเป็นช่องที่เราคำนวรไว้ก่อนหน้านี้แล้วว่า สามารถวางได้ และเมื่อวางได้ ก็เคลียหมากที่ถูกกินออกและสลับ turn

    if (!gameTable.hasMen(coor) && !gameTable.isBlockTypeF(coor)) {
      return DragTarget<Men>(
          builder: (context, candidateData, rejectedData) {
            return buildBlockTableContainer(colorBackground, menWidget);
          },
          onWillAccept: (men) {
            BlockTable blockTable = gameTable
                .getBlockTable(coor);
            return
              blockTable.isHighlight || blockTable.isHighlightAfterKilling;
          },
          onAccept: (men) {
            setState(() {
              gameTable.moveMen(men, Coordinate.of(coor));
              gameTable.checkKilled(coor);
              if (gameTable.checkKillableMore(men, coor)) {
                modeWalking = GameTable.MODE_WALK_AFTER_KILLING;
              } else {
                if (gameTable.isKingArea(
                    player: gameTable.currentPlayerTurn, coor: coor)) {
                  men.upgradeToKing();
                }
                modeWalking = GameTable.MODE_WALK_NORMAL;
                gameTable.clearHighlightWalkable();
                gameTable.togglePlayerTurn();
              }
            });
          });
    }

การเดินหมากก็สลับกันเดิน แล้วเรียก setState เพื่ออัพเดทหน้าจอทุกครั้งหลังวางบน DragTarget

การกินสองต่อ

การกินหมากศัตรู แบบสองต่อก็ทำไม่ยาก เพราะหลังจากเดินแล้วก็แค่เช็คเงื่อนไขการกินต่อ ซึ่งหลังจากกินไป 1 ครั้งแล้ว ผมเรียกว่าเป็นสถานะ AFTER_KILLING คือจะต้องเดินต่อ และฆ่าเท่านั้น

หมากฮอส

ตัวเอกของเกมนี้ก็คือฮอสตามชื่อเกม เพราะมันเดินได้สุดกระดาน การเดินของฮอสจะยากกว่าหมากปกติเพราะต้อง ลูปคำนวณทั้ง 4 ทางของมัน

การกินสองต่อของฮอส

ฮอสสามารถกินได้หลายต่อ ไม่จำกัดทิศทาง ดังนั้น จะต้องเขียนโปรแกรมให้สามารถเรียกใช้งานตัวเองได้ (recursive) และหลังจากเดินแล้วก็ต้องเช็คต่อว่ายังสามารถกินได้หรือไม่ ซึ่งเมื่อฮอสกิน ก็จะต้องเช็คต่อโดยที่ไม่คำนวณซ้ำทิศทางเดิม ไม่อย่างนั้นมันก็จะกลับไปกลับมาไม่จบสิ้น ตรงนี้เลยจะเป็นจุดที่ยากที่สุด

ซึ่งหลังจากเขียนเงื่อนไขของฮอสครบแล้ว เราก็จะสามารถเดินกินกี่ต่อก็ได้ ลองแบบฆ่ารวด 7 ตัวไปเลย ก็มาดิค้าบ

จบแล้ว

เกมหมากฮอสฉบับสั้นๆ แบบกิจกรรมยามว่าง ใครสนใจสามารถ clone ไปลองเล่น หรือนำไปพัฒนาต่อ เชิญได้เลยนะครับ

Source Code อยู่ที่ Github ครับ (:
https://github.com/benznest/flutter-checkers-game