2026-03-13 13:51:58 +08:00

656 lines
25 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AiSportsMicrospaceDB.DBContext;
using Common;
using Dto;
using Enum;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SportExpand;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using Wpf_AiSportsMicrospace;
using Wpf_AiSportsMicrospace.Common;
using Wpf_AiSportsMicrospace.Enum;
using Wpf_AiSportsMicrospace.MyUserControl;
using Wpf_AiSportsMicrospace.Views;
using Yztob.AiSports.Inferences.Things;
using Yztob.AiSports.Postures.Sports;
using Yztob.AiSports.Sensors.Things;
namespace Views.SquatFu
{
/// <summary>
/// WhackAMole.xaml 的交互逻辑
/// </summary>
public partial class WhackAMole : UserControl
{
private Main _mainWin => Application.Current.MainWindow as Main;
private MediaPlayer _mediaPlayer = new MediaPlayer();
List<RankItem> RankingItemList = new();
SportOperate sportOperate = new SportOperate();
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private GameState _currentGameState = GameState.NotStarted;
private readonly object _updateLock = new object();
private readonly Dictionary<int, (string lastNumber, DateTime lastChangeTime, string currentState)> _jumpStatus = new Dictionary<int, (string, DateTime, string)>();
private WhackAMoleContext _WhackAMoleContext;
Image tipImage;
public WhackAMole()
{
InitializeComponent();
Loaded += UserControl_Loaded;
Unloaded += UserControl_Unloaded;
_WhackAMoleContext = new WhackAMoleContext();
}
private async void UserControl_Loaded(object sender, RoutedEventArgs e)
{
// 使用局部变量保存事件处理方法,以便解绑
Action<VideoFrame> handler = null;
var app = (App)Application.Current;
using var scope = App.AppHost.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// 获取配置
var pointCount = await db.BasicConfig
.Where(x => x.Code == AppSettings.Code)
.Select(x => x.HomeWidth)
.FirstOrDefaultAsync();
var cfg = await db.GroupPoints.FirstOrDefaultAsync(x => x.Code == AppSettings.Code);
if (pointCount == 0 || cfg == null)
{
HandyControl.Controls.MessageBox.Error("配置加载失败!", "提示");
return;
}
_WhackAMoleContext.CirclePositions = Utils.GetPointsList(pointCount, cfg);
DrawCirclesWithText();
// 播放音乐
PlayMusic("raisehand.mp3");
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
_mainWin.WebcamClient.StartExtract();
}
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
_mainWin.HumanFrameUpdated -= OnHumanFrameUpdated;
}
private void PlayMusic(string musicFileName)
{
// 获取项目根目录
string projectRoot = System.IO.Path.Combine(AppContext.BaseDirectory, @"..\..\..");
string musicPath = System.IO.Path.Combine(projectRoot, "Resources", "Music", musicFileName);
string imgPath = System.IO.Path.Combine(projectRoot, "Resources", "Img", "tip.png");
if (!File.Exists(musicPath))
{
Console.WriteLine($"音乐文件不存在: {musicPath}");
return;
}
_mediaPlayer.Open(new Uri(musicPath, UriKind.Absolute));
ShowCenterTip(imgPath, TimeSpan.FromSeconds(3));
// 监听播放完成事件
_mediaPlayer.MediaEnded += MediaPlayer_MediaEnded;
//_mainWin.WebcamClient.StartExtract();
_mediaPlayer.Play();
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
}
private void ShowCenterTip(string imagePath, TimeSpan duration)
{
tipImage = new Image
{
Source = new BitmapImage(new Uri(imagePath, UriKind.Absolute)),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Opacity = 0,
Margin = new Thickness(0, -100, 0, 0),
};
// 增加图片的大小,调整比例
tipImage.Width = 1920 * 0.9; // 宽度为 Canvas 宽度的 90%
tipImage.Height = 1080 * 0.6; // 高度为 Canvas 高度的 60%
// 将图片添加到 Overlay Canvas
userListBox.Children.Add(tipImage);
// 渐变出现动画
var fadeInAnimation = new DoubleAnimation
{
From = 0,
To = 1,
Duration = TimeSpan.FromSeconds(1.5)
};
tipImage.BeginAnimation(UIElement.OpacityProperty, fadeInAnimation);
// 定时移除,并且渐变消失
//Task.Delay(duration).ContinueWith(_ =>
//{
// Dispatcher.Invoke(() =>
// {
// // 渐变消失动画
// var fadeOutAnimation = new DoubleAnimation
// {
// From = 1,
// To = 0,
// Duration = TimeSpan.FromSeconds(1.5)
// };
// tipImage.BeginAnimation(UIElement.OpacityProperty, fadeOutAnimation);
// //_isCountdownStarted = true;
// //FinishCountdown();
// //_currentGameState = GameState.Running;
// // 完成后移除图片
// fadeOutAnimation.Completed += (s, e) =>
// {
// userListBox.Children.Remove(tipImage);
// };
// });
//});
}
private void MediaPlayer_MediaEnded(object sender, EventArgs e)
{
// 音乐播放完成后的逻辑
Console.WriteLine("音乐播放完成!");
// 可在这里绑定抽帧事件
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
}
private bool _isCountdownStarted = false; // 新增标记
private void OnHumanFrameUpdated(object sender, List<Human> humans)
{
try
{
if (humans == null || humans.Count == 0) return;
switch (_currentGameState)
{
case GameState.NotStarted: // 未开始
if (!_isCountdownStarted) // 只有倒计时未开始才检测左手
{
int leftWaving = sportOperate.VerifyLeftWavingAction(humans);
if (leftWaving == 5)
{
_mainWin.HumanFrameUpdated -= OnHumanFrameUpdated;
_mainWin.WebcamClient.StopExtract();
// 举左手逻辑,例如结束动画或退出
var newPage = new Home();
_mainWin?.SwitchPageWithMaskAnimation(newPage, true);
return; // 提前退出
}
}
int rightWaving = sportOperate.VerifyRightWavingAction(humans);
if (rightWaving >= 3 && RankingGrid.Visibility != Visibility.Visible)
{
switch (rightWaving)
{
case (int)WavingAction.FirstHand:
StartCountdown(3);
_isCountdownStarted = true; // 倒计时开始,停止左手检测
break;
//case (int)WavingAction.Raising:
// UpdateCountdown();
// break;
//case (int)WavingAction.RaiseHand:
// FinishCountdown();
// _currentGameState = GameState.Running; // 倒计时完成 → 游戏开始
// break;
}
}
break;
case GameState.Running: // 游戏进行中
UpdateCircleCounts(humans);
break;
case GameState.Finished: // 游戏结束
int leftWave = sportOperate.VerifyLeftWavingAction(humans);
if (leftWave == 5)
{
_mainWin.HumanFrameUpdated -= OnHumanFrameUpdated;
_mainWin.WebcamClient.StopExtract();
// 举左手逻辑,例如结束动画或退出
var newPage = new Home();
_mainWin?.SwitchPageWithMaskAnimation(newPage, true);
return; // 提前退出
}
break;
}
}
catch (Exception ex)
{
Console.WriteLine("OnFrameExtracted error: " + ex.Message);
}
}
private double _currentCountdown = 3600;
private DateTime _lastUpdateTime = DateTime.Now;
private async void StartCountdown(int start = 3)
{
tipImage.Visibility = Visibility.Collapsed; // 隐藏提示图片
_currentGameState = GameState.Running;
_mainWin.ShowCountDownAnimation();
Utils.PlayBackgroundMusic("countdown_3.mp3", false);
//开始人脸识别 =============================================
//Action < VideoFrame > handler = null;
//handler = async (frame) =>
//{
// var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray();
// double[] xValues = [.. _WhackAMoleContext.CirclePositions.Select(p => p.XNorm)];
// var studentList = Utils.FacialRecognition(buffer, xValues);
// if (studentList != null && studentList.Count > 0)
// {
// // 解绑事件
// _mainWin.FacialRecognitionEvent -= handler;
// for (int i = 0; i < studentList.Count; i++)
// {
// var student = studentList[i];
// if (student != null)
// {
// _WhackAMoleContext.UserList[i].NameText = student.StudentName;
// }
// }
// }
//};
// 绑定事件
//_mainWin.FacialRecognitionEvent += handler;
await Task.Delay(3000);
FinishCountdown();
}
private void UpdateCountdown()
{
if ((DateTime.Now - _lastUpdateTime).TotalMilliseconds >= 600)
{
_lastUpdateTime = DateTime.Now;
_currentCountdown -= 600;
if (_currentCountdown > 0)
{
countdownText.Text = _currentCountdown.ToString();
}
else
{
FinishCountdown();
}
}
}
private System.Timers.Timer _gameTimer;
private Random _random = new Random();
private async void FinishCountdown()
{
countdownText.Text = "GO!";
countdownGrid.Visibility = Visibility.Hidden;
// 播放背景音乐(循环)
Utils.PlayBackgroundMusic("homeprojectselected1.mp3", true);
// 启动60秒倒计时(独立任务)
StartGameCountdown(60);
// 开始游戏定时器,每2-3秒随机选取用户出现地鼠或兔子
StartGameTimer();
}
private void StartGameTimer()
{
_gameTimer = new System.Timers.Timer();
_gameTimer.Elapsed += OnGameTimerElapsed;
_gameTimer.AutoReset = true;
// 设置第一次触发时间为2-3秒之间的随机值
int firstInterval = _random.Next(2000, 3000);
_gameTimer.Interval = firstInterval;
_gameTimer.Start();
}
private void OnGameTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Dispatcher.Invoke(() =>
{
// 随机选取2-4个用户
int userCount = _random.Next(2, 5); // 2-4个
int actualCount = Math.Min(userCount, _WhackAMoleContext.UserList.Count);
// 获取所有用户索引
var allIndices = Enumerable.Range(0, _WhackAMoleContext.UserList.Count).ToList();
// 随机打乱顺序
var shuffled = allIndices.OrderBy(x => _random.Next()).Take(actualCount).ToList();
foreach (var index in shuffled)
{
// 随机决定是地鼠(1)还是兔子(3)
int imageState = _random.Next(2) == 0 ? 1 : 3;
if (_WhackAMoleContext.UserList[index].ImageState == "0")
{
_WhackAMoleContext.UserList[index].ImageState = imageState.ToString();
}
}
// 设置下一次触发时间为2-3秒之间的随机值
_gameTimer.Interval = _random.Next(2000, 3000);
});
}
double Tiggle = 1;
private async void StartGameCountdown(int seconds)
{
countdownGrid.Visibility = Visibility.Visible;
for (int i = seconds; i >= 0; i--)
{
countdownText.Text = i.ToString();
await Task.Delay(1000);
}
countdownGrid.Visibility = Visibility.Hidden;
_gameTimer.Stop();
_WhackAMoleContext.UserList.ForEach(x =>
{
Tiggle += 1;
//x.TriggerValue = Tiggle;
});
_mainWin.HumanFrameUpdated -= OnHumanFrameUpdated;
_mainWin.WebcamClient.StopExtract();
RankingItemList = _WhackAMoleContext.UpdateWhackAMoleRankList();
ShowRankingBoard(RankingItemList);
Utils.StopBackgroundMusic();
_currentGameState = GameState.Finished;
_isCountdownStarted = false;
_mainWin.HumanFrameUpdated += OnHumanFrameUpdated;
_mainWin.WebcamClient.StartExtract();
}
private DateTime? _raiseStartTime;
private bool _firstHandTriggered;
private int _lastCountdownSecond = 3;
private DateTime? _countdownStartTime;
private void ResetRaiseState()
{
_raiseStartTime = null;
_countdownStartTime = null;
_firstHandTriggered = false;
_lastCountdownSecond = 3;
}
private void DrawCirclesWithText()
{
userListBox.Children.Clear();
_WhackAMoleContext.Sports.Clear();
_WhackAMoleContext.UserList.Clear();
_WhackAMoleContext.UserNumberList.Clear();
for (int i = 0; i < _WhackAMoleContext.CirclePositions.Count; i++)
{
var pos = _WhackAMoleContext.CirclePositions[i];
double x = pos.XNorm;
double y = pos.YNorm;
// 绘制发光圆
AddUserItem(x, y, i);
// 绑定运动对象
var sport = new FrontSquat();
Debug.WriteLine($"userListBox长度:{userListBox.Children.Count}");
//Optimizes.Optimizeing(sport);
//var sport = SportBase.Create("rope-skipping");
int indexCopy = i;
//var currentItem = userItem;
// 订阅事件
sport.OnTicked += (count, times) =>
{
// 获取当前用户的ImageState
var imageState = _WhackAMoleContext.UserList[indexCopy].ImageState;
// 获取当前分数
int currentScore = 0;
int.TryParse(_WhackAMoleContext.UserNumberList[indexCopy], out currentScore);
// 根据ImageState判断加分还是减分
if (imageState == "1") // 地鼠:加10分
{
currentScore += 10;
}
else if (imageState == "3") // 兔子:减5分
{
currentScore -= 5;
// 分数不能为负数
//if (currentScore < 0) currentScore = 0;
}
// 更新分数
string newScore = currentScore.ToString();
_WhackAMoleContext.UserNumberList[indexCopy] = newScore;
_WhackAMoleContext.UserList[indexCopy].NumberText = newScore;
// 如果打中地鼠或兔子,重置ImageState为0
if (imageState == "1")
{
_WhackAMoleContext.UserList[indexCopy].ImageState = "2";
}
else if (imageState == "3")
{
_WhackAMoleContext.UserList[indexCopy].ImageState = "4";
}
};
sport.Start();
_WhackAMoleContext.Sports.Add(sport);
}
}
private void UpdateCircleCounts(List<Human> humans)
{
double halfW = AppSettings.AreaSize.X;
double halfH = AppSettings.AreaSize.Y;
int circleCount = _WhackAMoleContext.CirclePositions.Count;
for (int i = 0; i < circleCount; i++)
{
var circle = _WhackAMoleContext.CirclePositions[i];
double cx = circle.XNorm;
double cy = circle.YNorm;
Human targetHuman = null;
// 1️⃣ 找圈内的人(单脚命中即可)
foreach (var h in humans)
{
if (sportOperate.IsAnyFootInCircle(h, cx, cy, halfW, halfH))
{
targetHuman = h;
break; // 每个圈只认一个人
}
}
bool hasHuman = targetHuman != null;
// 3️⃣ 推送跳绳计数
if (hasHuman)
{
_WhackAMoleContext.Sports[i].Pushing(targetHuman);
}
}
}
void AddUserItem(double centerX, double centerY, int index)
{
//var userItem = new WhackAMoleUserControl();
//userItem.Width = 400;
//userItem.Height = 600;
//userItem.NameText = _WhackAMoleContext.UserNameList[index]; //名字
//userItem.VerticalAlignment = VerticalAlignment.Top;
//userItem.HorizontalAlignment = HorizontalAlignment.Left;
//userItem.Index = index;
//userItem.ImageState = "1";
//userItem.Margin = new Thickness(centerX - (index == 0 ? 80 : index == 6 ? 190 : 135), centerY - 540, 0, 0);
var userItem = new WhackAMoleUserControl
{
Width = 410,
Height = 580,
NameText = _WhackAMoleContext.UserNameList[index],
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left,
ImageState = "0"
};
var top = (centerX - 200) > 0 ? (centerX - 200) : 0;
userItem.Margin = new Thickness(top, centerY - 570, 0, 0);
userListBox.Children.Add(userItem);
_WhackAMoleContext.UserList.Add(userItem);
_WhackAMoleContext.UserNumberList.Add("0");
//return userItem; //
}
public void ShowRankingBoard(List<RankItem> list)
{
var scoreGrid = FindName("ScoreGrid") as Grid;
name1.Text = list[0].Name;
name2.Text = list[1].Name;
name3.Text = list[2].Name;
number1.Text = list[0].Number.ToString();
number2.Text = list[1].Number.ToString();
number3.Text = list[2].Number.ToString();
foreach (var item in list)
{
var grid = new Grid
{
Width = 1344,
Height = 71,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Top,
Margin = new Thickness(0, 69 * item.Rank, 0, 0)
};
var border = new Border
{
Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#fff"))
};
grid.Children.Add(border);
var rankText = new TextBlock
{
Text = item.Rank.ToString(),
FontSize = 24,
FontWeight = FontWeights.Bold,
Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999999")),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
TextAlignment = TextAlignment.Center,
Width = 200
};
grid.Children.Add(rankText);
var positionText = new TextBlock
{
Text = item.Name,
FontSize = 24,
FontWeight = FontWeights.Bold,
Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999999")),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
};
grid.Children.Add(positionText);
var scoreText = new TextBlock
{
Text = item.Number.ToString(),
FontSize = 24,
FontWeight = FontWeights.Bold,
Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#999999")),
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Center,
TextAlignment = TextAlignment.Center,
Width = 200
};
grid.Children.Add(scoreText);
ScoreGrid.Children.Add(grid);
}
showElement("RankingGrid");
//var rankingGrid = FindName("RankingGrid") as Grid;
//if (rankingGrid == null) return;
//rankingGrid.Visibility = Visibility.Visible;
//var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(600));
//rankingGrid.BeginAnimation(UIElement.OpacityProperty, fadeIn);
}
/*
* 控制元素隐藏(渐隐动画
* time: 动画持续时间(毫秒)
* name: 元素名称
*/
public void HideElement(string name, int time)
{
var Element = FindName(name) as Grid;
if (Element == null) return;
var fadeOut = new DoubleAnimation(1, 0, TimeSpan.FromMilliseconds(time));
fadeOut.Completed += (s, e) => Element.Visibility = Visibility.Collapsed;
Element.BeginAnimation(UIElement.OpacityProperty, fadeOut);
}
/*
* 控制元素限时(渐隐动画
* time: 动画持续时间(毫秒)
* name: 元素名称
*/
public void showElement(string name, int time = 600)
{
var Element = FindName(name) as FrameworkElement;
if (Element == null) return;
Element.Visibility = Visibility.Visible;
var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(time));
Element.BeginAnimation(UIElement.OpacityProperty, fadeIn);
}
}
}