蜘蛛纸牌是一个经典的单人纸牌游戏。这篇文章我们来实现一个完整的蜘蛛纸牌游戏,包括游戏逻辑、拖拽交互、难度选择、以及得分系统。通过这个功能,我们能展示如何构建一个复杂的交互式游戏 。

游戏的核心逻辑
PlayingCard类代表一张牌:
dart
class PlayingCard {
final int rank;
final Suit suit;
bool faceUp;
PlayingCard({required this.rank, required this.suit, this.faceUp = false});
String get rankString {
switch (rank) {
case 1: return 'A';
case 11: return 'J';
case 12: return 'Q';
case 13: return 'K';
default: return rank.toString();
}
}
String get suitSymbol {
switch (suit) {
case Suit.spades: return '♠';
case Suit.hearts: return '♥';
case Suit.diamonds: return '♦';
case Suit.clubs: return '♣';
}
}
Color get suitColor {
return (suit == Suit.hearts || suit == Suit.diamonds) ? Colors.red : Colors.black;
}
}
每张牌有rank(1-13)、suit(花色)和faceUp(是否翻开)。
rankString和suitSymbol提供了牌的显示信息。
游戏状态管理
SpiderSolitaireScreen管理游戏的整体状态:
dart
class _SpiderSolitaireScreenState extends State<SpiderSolitaireScreen> {
List<List<PlayingCard>> columns = List.generate(10, (_) => []);
List<PlayingCard> stock = [];
List<List<PlayingCard>> completed = [];
int moves = 0;
int score = 500;
Difficulty difficulty = Difficulty.easy;
bool gameStarted = false;
bool gameWon = false;
columns存储10列的牌。
stock存储剩余的牌。
completed存储已完成的牌组。
score从500开始,每次移动减1,完成一组加100。
游戏初始化
initGame方法初始化游戏:
dart
void initGame() {
List<Suit> suits;
switch (difficulty) {
case Difficulty.easy:
suits = [Suit.spades];
break;
case Difficulty.medium:
suits = [Suit.spades, Suit.hearts];
break;
case Difficulty.hard:
suits = Suit.values;
break;
}
List<PlayingCard> deck = [];
int decksPerSuit = 8 ~/ suits.length;
for (var suit in suits) {
for (int d = 0; d < decksPerSuit; d++) {
for (int rank = 1; rank <= 13; rank++) {
deck.add(PlayingCard(rank: rank, suit: suit));
}
}
}
deck.shuffle(Random());
根据难度选择花色数量。
简单模式只有黑桃,中等模式有黑桃和红心,困难模式有所有花色。
创建牌组并打乱。
牌的移动逻辑
getMovableCards方法获取可移动的牌组:
dart
List<PlayingCard>? getMovableCards(int col, int startIdx) {
if (startIdx >= columns[col].length) return null;
var cards = columns[col].sublist(startIdx);
if (cards.isEmpty || !cards.first.faceUp) return null;
// 检查是否是同花色递减序列
for (int i = 0; i < cards.length - 1; i++) {
if (cards[i].suit != cards[i + 1].suit ||
cards[i].rank != cards[i + 1].rank + 1) {
return null;
}
}
return cards;
}
只有同花色递减序列才能整体移动。
canPlace方法检查是否可以放置牌:
dart
bool canPlace(int toCol, PlayingCard card) {
if (columns[toCol].isEmpty) return true;
var top = columns[toCol].last;
// 只要求数值递减1,不要求花色
return top.rank == card.rank + 1;
}
可以在任何数值比当前牌小1的牌上放置。
完成检查
checkComplete方法检查是否有完成的牌组:
dart
void checkComplete() {
for (int col = 0; col < columns.length; col++) {
if (columns[col].length < 13) continue;
int start = columns[col].length - 13;
bool valid = true;
Suit? s;
for (int i = 0; i < 13; i++) {
var c = columns[col][start + i];
if (!c.faceUp) { valid = false; break; }
if (i == 0) {
if (c.rank != 13) { valid = false; break; }
s = c.suit;
} else {
if (c.rank != 13 - i || c.suit != s) { valid = false; break; }
}
}
if (valid) {
setState(() {
var removed = columns[col].sublist(start);
columns[col].removeRange(start, columns[col].length);
completed.add(removed);
score += 100;
});
}
}
}
检查每列是否有K到A的同花色序列。
如果有就移除并加分。
拖拽交互
buildCol方法实现拖拽交互:
dart
Widget buildCol(int colIdx, double cardW, double cardH) {
var col = columns[colIdx];
return DragTarget<Map<String, dynamic>>(
onWillAcceptWithDetails: (details) {
var data = details.data;
int fromCol = data['col'];
List<PlayingCard> cards = data['cards'];
if (fromCol == colIdx) return false;
return canPlace(colIdx, cards.first);
},
onAcceptWithDetails: (details) {
var data = details.data;
moveCards(data['col'], data['idx'], colIdx);
},
使用DragTarget接收拖拽的牌。
在onWillAcceptWithDetails中检查是否可以放置。
在onAcceptWithDetails中执行移动。
牌的显示
buildCard方法显示牌:
dart
Widget buildCard(PlayingCard card, double w, double h, {bool dragging = false}) {
if (!card.faceUp) {
return Container(
width: w,
height: h,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF1565C0), Color(0xFF0D47A1)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(4),
border: Border.all(color: Colors.white30),
),
child: Center(
child: Text('🕷️', style: TextStyle(fontSize: w * 0.4)),
),
);
}
return Container(
width: w,
height: h,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4),
border: Border.all(color: Colors.grey.shade400),
),
child: Stack(
children: [
// 左上角
Positioned(
left: 2,
top: 1,
child: Column(
children: [
Text(card.rankString, style: TextStyle(color: card.suitColor, fontWeight: FontWeight.bold, fontSize: w * 0.32, height: 1)),
Text(card.suitSymbol, style: TextStyle(color: card.suitColor, fontSize: w * 0.24, height: 1)),
],
),
),
// 中间大花色
Center(
child: Text(card.suitSymbol, style: TextStyle(color: card.suitColor, fontSize: w * 0.55)),
),
// 右下角(倒置)
Positioned(
right: 2,
bottom: 1,
child: Transform.rotate(
angle: 3.14159,
child: Column(
children: [
Text(card.rankString, style: TextStyle(color: card.suitColor, fontWeight: FontWeight.bold, fontSize: w * 0.32, height: 1)),
Text(card.suitSymbol, style: TextStyle(color: card.suitColor, fontSize: w * 0.24, height: 1)),
],
),
),
),
],
),
);
}
翻开的牌显示为白色,背面显示为蓝色。
牌的四个角显示牌的信息。
总结
这篇文章我们实现了一个完整的蜘蛛纸牌游戏。涉及到的知识点包括:
- 游戏逻辑 - 实现复杂的游戏规则和检查
- 拖拽交互 - 使用DragTarget和Draggable实现拖拽
- 状态管理 - 管理游戏的多个状态
- 难度系统 - 支持多个难度级别
- 得分系统 - 实现得分计算和显示
- UI渲染 - 使用Stack和Transform创建逼真的牌显示
蜘蛛纸牌游戏展示了如何构建一个复杂的交互式游戏 。通过完善的游戏逻辑、流畅的交互、以及精心的UI设计,我们能为用户提供一个有趣的游戏体验。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net