import { VerticalBox, HorizontalBox, ScrollView } from "std-widgets.slint"; import { Button } from "Components.slint"; // 卡牌颜色枚举 export enum CardColor { Red, Blue, Green, Yellow } // 游戏方向枚举 export enum GameDirection { Clockwise, CounterClockwise } // 其他玩家信息结构 export struct OtherPlayer { name: string, card-count: int, has-uno: bool, is-current-turn: bool, } // 手牌信息结构 export struct HandCard { image-path: image, is-selected: bool, card-id: int, is-wild: bool, can-be-played: bool, } // 玩家头像组件 component PlayerAvatar inherits Rectangle { in property player-name; in property card-count; in property has-uno; in property is-current-turn; width: 100px; height: 130px; background: transparent; VerticalLayout { spacing: 4px; alignment: center; // UNO 标志占位区域 (固定高度) HorizontalLayout { alignment: center; Rectangle { width: 50px; height: 20px; background: has-uno ? #FF5722 : transparent; border-radius: 4px; Text { text: "UNO!"; font-size: 11px; font-weight: 700; color: has-uno ? #FFFFFF : transparent; horizontal-alignment: center; vertical-alignment: center; } } } // 头像 HorizontalLayout { alignment: center; Rectangle { width: 60px; height: 60px; background: is-current-turn ? #FFD54F : #E0E0E0; border-radius: 30px; border-width: is-current-turn ? 3px : 0px; border-color: #FF9800; Text { text: "👤"; font-size: 28px; horizontal-alignment: center; vertical-alignment: center; } } } // 玩家名称 Text { text: player-name; font-size: 12px; font-weight: 500; color: #333333; horizontal-alignment: center; } // 剩余手牌数 Text { text: "剩余: " + card-count + " 张"; font-size: 10px; color: #666666; horizontal-alignment: center; } } } // 卡牌背面组件 (起牌堆) component CardBack inherits Rectangle { in property card-width: 100; in property card-height: 150; width: card-width * 1px; height: card-height * 1px; background: #2C2C2C; border-radius: 10px; border-width: 2px; border-color: #444444; Rectangle { width: parent.width * 0.75; height: parent.height * 0.83; background: #1a1a1a; border-radius: 8px; Text { text: "UNO"; font-size: 20px; font-weight: 700; color: #888888; horizontal-alignment: center; vertical-alignment: center; } } } // 弃牌堆卡牌组件 component DiscardCard inherits Rectangle { in property card-image; in property card-width: 100; in property card-height: 150; width: card-width * 1px; height: card-height * 1px; background: transparent; border-radius: 10px; drop-shadow-blur: 12px; drop-shadow-color: #00000040; drop-shadow-offset-y: 4px; Image { width: 100%; height: 100%; source: card-image; image-fit: contain; } } // 方向环背景组件 - 只绘制环线 component DirectionRingBackground inherits Rectangle { in property current-color: CardColor.Red; in property ring-width: 500; in property ring-height: 280; property ring-color: current-color == CardColor.Red ? #F44336 : current-color == CardColor.Blue ? #2196F3 : current-color == CardColor.Green ? #4CAF50 : current-color == CardColor.Yellow ? #FFEB3B : #9E9E9E; property ring-color-dim: current-color == CardColor.Red ? #E57373 : current-color == CardColor.Blue ? #64B5F6 : current-color == CardColor.Green ? #81C784 : current-color == CardColor.Yellow ? #FFF176 : #BDBDBD; property a: ring-width / 2; property b: ring-height / 2; width: ring-width * 1px; height: ring-height * 1px; background: transparent; // 底层光晕环 Path { width: 100%; height: 100%; stroke: ring-color-dim; stroke-width: 8px; fill: transparent; opacity: 0.3; MoveTo { x: ring-width / 2 + a; y: ring-height / 2; } ArcTo { x: ring-width / 2 - a; y: ring-height / 2; radius-x: a; radius-y: b; sweep: true; large-arc: true; } ArcTo { x: ring-width / 2 + a; y: ring-height / 2; radius-x: a; radius-y: b; sweep: true; large-arc: true; } } // 主环 Path { width: 100%; height: 100%; stroke: ring-color; stroke-width: 3px; fill: transparent; opacity: 0.9; MoveTo { x: ring-width / 2 + a; y: ring-height / 2; } ArcTo { x: ring-width / 2 - a; y: ring-height / 2; radius-x: a; radius-y: b; sweep: true; large-arc: true; } ArcTo { x: ring-width / 2 + a; y: ring-height / 2; radius-x: a; radius-y: b; sweep: true; large-arc: true; } } } // 方向环光点组件 - 使用内置动画实现平滑移动 component DirectionRingOrbs inherits Rectangle { in property direction: GameDirection.Clockwise; in property current-color: CardColor.Red; in property ring-width: 500; in property ring-height: 280; // 单一的动画计数器,持续递增 in-out property cycle: 0; property ring-color: current-color == CardColor.Red ? #F44336 : current-color == CardColor.Blue ? #2196F3 : current-color == CardColor.Green ? #4CAF50 : current-color == CardColor.Yellow ? #FFEB3B : #9E9E9E; property a: ring-width / 2; property b: ring-height / 2; // 基础角度(持续增加) property base-angle: cycle * 360; animate base-angle { duration: 8s; easing: linear; } // 方向系数:顺时针为1,逆时针为-1 property dir-sign: direction == GameDirection.Clockwise ? 1 : -1; // 实际角度 = 基础角度 * 方向系数 property actual-angle: base-angle * dir-sign; width: ring-width * 1px; height: ring-height * 1px; background: transparent; // 光点1 Rectangle { x: parent.width / 2 - 9px + cos(actual-angle * 1deg) * a * 1px; y: parent.height / 2 - 9px + sin(actual-angle * 1deg) * b * 1px; width: 18px; height: 18px; background: ring-color; border-radius: 9px; drop-shadow-blur: 8px; drop-shadow-color: ring-color; } // 光点2 Rectangle { x: parent.width / 2 - 9px + cos((actual-angle + 90) * 1deg) * a * 1px; y: parent.height / 2 - 9px + sin((actual-angle + 90) * 1deg) * b * 1px; width: 18px; height: 18px; background: ring-color; border-radius: 9px; drop-shadow-blur: 8px; drop-shadow-color: ring-color; } // 光点3 Rectangle { x: parent.width / 2 - 9px + cos((actual-angle + 180) * 1deg) * a * 1px; y: parent.height / 2 - 9px + sin((actual-angle + 180) * 1deg) * b * 1px; width: 18px; height: 18px; background: ring-color; border-radius: 9px; drop-shadow-blur: 8px; drop-shadow-color: ring-color; } // 光点4 Rectangle { x: parent.width / 2 - 9px + cos((actual-angle + 270) * 1deg) * a * 1px; y: parent.height / 2 - 9px + sin((actual-angle + 270) * 1deg) * b * 1px; width: 18px; height: 18px; background: ring-color; border-radius: 9px; drop-shadow-blur: 8px; drop-shadow-color: ring-color; } // 递增计数器 Timer { interval: 8s; running: true; triggered => { cycle = cycle + 1; } } // 初始化时启动动画 init => { cycle = 1; } } // 手牌卡牌组件 component HandCardItem inherits Rectangle { in property card-image; in property is-selected: false; callback clicked; width: 120px; height: 180px; y: is-selected ? -25px : 0px; background: transparent; border-radius: 10px; drop-shadow-blur: is-selected ? 16px : 6px; drop-shadow-color: is-selected ? #00000040 : #00000025; drop-shadow-offset-y: is-selected ? 6px : 3px; animate y { duration: 150ms; easing: ease-out; } animate drop-shadow-blur { duration: 150ms; } touch := TouchArea { clicked => { root.clicked(); } } Image { width: 100%; height: 100%; source: card-image; image-fit: contain; } } // UNO 圆形按钮组件 component UnoButton inherits Rectangle { callback clicked; width: 90px; height: 90px; background: touch.pressed ? #D32F2F : #F44336; border-radius: 45px; drop-shadow-blur: 12px; drop-shadow-color: #F4433660; drop-shadow-offset-y: 4px; touch := TouchArea { clicked => { root.clicked(); } } Text { text: "UNO!"; font-size: 20px; font-weight: 700; color: #FFFFFF; horizontal-alignment: center; vertical-alignment: center; } } // 颜色选择按钮组件 component ColorButton inherits Rectangle { in property btn-color; in property color-name; callback clicked; width: 80px; height: 80px; background: touch.pressed ? btn-color.darker(20%) : btn-color; border-radius: 12px; border-width: 3px; border-color: btn-color.darker(30%); drop-shadow-blur: 8px; drop-shadow-color: btn-color.with-alpha(0.4); drop-shadow-offset-y: 3px; touch := TouchArea { clicked => { root.clicked(); } } Text { text: color-name; font-size: 14px; font-weight: 600; color: #FFFFFF; horizontal-alignment: center; vertical-alignment: center; } } // 颜色选择弹窗组件 component ColorPickerDialog inherits Rectangle { in property show: false; callback color-selected(CardColor); callback cancel; width: 100%; height: 100%; background: show ? #00000080 : transparent; opacity: show ? 1.0 : 0.0; visible: show; animate opacity { duration: 200ms; easing: ease-out; } // 阻止点击穿透 TouchArea { enabled: show; clicked => { root.cancel(); } } // 弹窗内容 Rectangle { x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; width: 600px; height: 280px; background: #FFFFFF; border-radius: 20px; drop-shadow-blur: 30px; drop-shadow-color: #00000040; drop-shadow-offset-y: 10px; // 阻止点击弹窗内容时关闭 TouchArea { enabled: show; } VerticalLayout { padding: 30px; spacing: 25px; alignment: center; // 标题 Text { text: "选择颜色"; font-size: 24px; font-weight: 700; color: #333333; horizontal-alignment: center; } // 颜色按钮网格 HorizontalLayout { spacing: 20px; alignment: center; ColorButton { btn-color: #F44336; color-name: "红色"; clicked => { root.color-selected(CardColor.Red); } } ColorButton { btn-color: #2196F3; color-name: "蓝色"; clicked => { root.color-selected(CardColor.Blue); } } ColorButton { btn-color: #4CAF50; color-name: "绿色"; clicked => { root.color-selected(CardColor.Green); } } ColorButton { btn-color: #FFEB3B; color-name: "黄色"; clicked => { root.color-selected(CardColor.Yellow); } } } // 取消按钮 HorizontalLayout { alignment: center; Button { text: "取消"; clicked => { root.cancel(); } } } } } } // 主游戏页面 export component GamePage inherits Window { // 其他玩家列表 in property <[OtherPlayer]> other-players; // 当前玩家信息 in property current-player-name; in property current-player-card-count; in property current-player-has-uno; in property is-current-player-turn; // 手牌列表 in property <[HandCard]> hand-cards; // 当前选中的卡牌索引 (-1 表示未选中) property selected-card-index: -1; // 弃牌堆顶牌 in property discard-top-card; // 游戏状态 in property game-direction; in property current-color; // 颜色选择弹窗状态 in-out property show-color-picker; // 回调 callback request-play-card(int, CardColor); callback request-draw-card; callback request-uno; width: 1920px; height: 1080px; // 背景渐变 Rectangle { background: @linear-gradient(135deg, #fdfbf7 0%, #f3e7e9 100%); } // 主布局 VerticalLayout { padding: 30px; spacing: 20px; // ===== 顶部:其他玩家区域 ===== Rectangle { height: 150px; background: #FDFBF8; border-radius: 16px; drop-shadow-blur: 10px; drop-shadow-color: #00000010; drop-shadow-offset-y: 2px; HorizontalLayout { padding-top: 10px; padding-bottom: 15px; padding-left: 15px; padding-right: 15px; spacing: 40px; alignment: center; for player[index] in other-players: PlayerAvatar { player-name: player.name; card-count: player.card-count; has-uno: player.has-uno; is-current-turn: player.is-current-turn; } } } // ===== 中间:牌堆区域 ===== Rectangle { vertical-stretch: 1; background: transparent; // 中心容器:包含环和两个牌堆 Rectangle { width: 560px; height: 320px; x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; background: transparent; // 方向环背景 - 在牌堆下面 DirectionRingBackground { x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; ring-width: 560; ring-height: 320; current-color: root.current-color; } // 起牌堆 - 左侧 Rectangle { x: 80px; y: (parent.height - 200px) / 2; width: 130px; height: 200px; CardBack { card-width: 130; card-height: 200; } TouchArea { enabled: root.is-current-player-turn; width: parent.width; height: parent.height; clicked => { root.request-draw-card(); } } } // 弃牌堆 - 右侧 DiscardCard { x: parent.width - 130px - 80px; y: (parent.height - 200px) / 2; card-width: 130; card-height: 200; card-image: root.discard-top-card; } // 方向环光点 - 在牌堆上面 DirectionRingOrbs { x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; ring-width: 560; ring-height: 320; direction: root.game-direction; current-color: root.current-color; } } } // ===== 底部:当前玩家区域 ===== Rectangle { height: 320px; background: #FDFBF8; border-radius: 16px; drop-shadow-blur: 10px; drop-shadow-color: #00000010; drop-shadow-offset-y: -2px; HorizontalLayout { padding: 20px; padding-left: 40px; padding-right: 40px; spacing: 30px; alignment: space-between; // 当前玩家头像 - 靠左,垂直居中与UNO按钮对齐 VerticalLayout { alignment: center; PlayerAvatar { player-name: current-player-name; card-count: current-player-card-count; has-uno: current-player-has-uno; is-current-turn: is-current-player-turn; } } // 手牌区域 VerticalLayout { horizontal-stretch: 1; spacing: 10px; alignment: center; // 出牌按钮 (放在手牌上方) Rectangle { height: 50px; background: transparent; HorizontalLayout { alignment: center; padding-bottom: 10px; Button { text: "出牌"; enabled: is-current-player-turn && selected-card-index >= 0 && hand-cards[selected-card-index].can-be-played; clicked => { if (selected-card-index >= 0) { // 检查是否是万能牌 if (hand-cards[selected-card-index].is-wild) { // 显示颜色选择弹窗 root.show-color-picker = true; } else { // 非万能牌直接出牌,颜色传 Red root.request-play-card(selected-card-index, CardColor.Red); selected-card-index = -1; } } } } } } // 手牌列表 - 上方留出空间给选中效果 Rectangle { height: 210px; background: transparent; // 手牌容器,向下偏移以给选中卡牌留空间,居中显示 Rectangle { x: (parent.width - self.width) / 2; y: 25px; width: hand-cards-layout.preferred-width; height: 180px; hand-cards-layout := HorizontalLayout { alignment: center; spacing: -35px; // 卡牌重叠效果 for card[index] in hand-cards: HandCardItem { card-image: card.image-path; is-selected: index == selected-card-index; clicked => { if (is-current-player-turn && card.can-be-played) { // 切换选中状态 if (selected-card-index == index) { selected-card-index = -1; } else { selected-card-index = index; } } } } } } } } // UNO 按钮 - 靠右 VerticalLayout { alignment: center; UnoButton { clicked => { root.request-uno(); } } } } } } // 颜色选择弹窗 (覆盖在最上层) ColorPickerDialog { show: root.show-color-picker; color-selected(color) => { root.show-color-picker = false; root.request-play-card(selected-card-index, color); selected-card-index = -1; } cancel => { root.show-color-picker = false; } } }