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