mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-05-02 04:22:04 -04:00
Live Roulette v1 (#59)
* Enabling/disabling gamba games with dynamic settting lookup. * Its roulette baby
This commit is contained in:
committed by
GitHub
parent
6209a76e94
commit
ca2e2c7874
@@ -0,0 +1,137 @@
|
||||
using SixLabors.Fonts;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Drawing;
|
||||
using SixLabors.ImageSharp.Drawing.Processing;
|
||||
using SixLabors.ImageSharp.Formats.Webp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace KfChatDotNetBot.Commands.Kasino.Roulette;
|
||||
|
||||
public static class RouletteAnimationGenerator
|
||||
{
|
||||
// European Wheel Sequence
|
||||
private static readonly int[] WheelNumbers = {
|
||||
0, 32, 15, 19, 4, 21, 2, 25, 17, 34, 6, 27, 13, 36, 11, 30, 8, 23, 10, 5, 24, 16, 33, 1, 20, 14, 31, 9, 22, 18, 29, 7, 28, 12, 35, 3, 26
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Generates an animated roulette wheel that lands on the specified winning number
|
||||
/// </summary>
|
||||
/// <param name="winningNumber">The number (0-36) that the ball should land on</param>
|
||||
/// <returns>A tuple containing the animation duration in seconds and the WebP animation bytes</returns>
|
||||
public static (int durationSeconds, byte[] animationBytes) GenerateAnimation(int winningNumber)
|
||||
{
|
||||
if (winningNumber < 0 || winningNumber > 36)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(winningNumber), "Winning number must be between 0 and 36");
|
||||
}
|
||||
|
||||
using var board = DrawWheelBase();
|
||||
int fps = 20;
|
||||
int duration = Random.Shared.Next(6, 9);
|
||||
int totalFrames = fps * duration;
|
||||
using var animation = new Image<Rgba32>(500, 500);
|
||||
|
||||
// Find the index of the winning number in the wheel sequence
|
||||
int winningIndex = Array.IndexOf(WheelNumbers, winningNumber);
|
||||
if (winningIndex == -1)
|
||||
{
|
||||
throw new InvalidOperationException($"Winning number {winningNumber} not found in wheel sequence");
|
||||
}
|
||||
|
||||
// Set the "Journey"
|
||||
float endWheelRotation = 720f + Random.Shared.Next(0, 360);
|
||||
float sliceAngle = 360f / 37f;
|
||||
|
||||
// The pocket index 'i' is located at (i * sliceAngle) degrees relative to wheel zero.
|
||||
// Our '0' pocket was drawn at -90 degrees.
|
||||
float pocketOffsetOnWheel = (winningIndex * sliceAngle) - 90;
|
||||
|
||||
// This is where the ball MUST be at the end of the video
|
||||
float finalBallAngle = endWheelRotation + pocketOffsetOnWheel;
|
||||
|
||||
// Render frames
|
||||
for (int i = 0; i < totalFrames; i++)
|
||||
{
|
||||
float progress = (float)i / totalFrames;
|
||||
float ease = 1f - MathF.Pow(1f - progress, 3); // Smooth stop
|
||||
|
||||
// Wheel rotates Clockwise (Adding degrees)
|
||||
float currentWheelAngle = endWheelRotation * ease;
|
||||
|
||||
// Ball rotates Counter-Clockwise (Starting high and subtracting)
|
||||
// We start with 5 extra laps (1800 degrees) and "go back" to the final angle
|
||||
float startBallAngle = finalBallAngle + 1800f;
|
||||
float currentBallAngle = startBallAngle - ((startBallAngle - finalBallAngle) * ease);
|
||||
|
||||
var frame = new Image<Rgba32>(500, 500);
|
||||
frame.Mutate(ctx => {
|
||||
// Draw Wheel
|
||||
using var rotatedBoard = board.Clone(b => b.Rotate(currentWheelAngle));
|
||||
int ox = 250 - (rotatedBoard.Width / 2);
|
||||
int oy = 250 - (rotatedBoard.Height / 2);
|
||||
ctx.DrawImage(rotatedBoard, new Point(ox, oy), 1f);
|
||||
|
||||
// Ball Radius (Physics)
|
||||
float dropT = MathF.Max(0, (progress - 0.7f) / 0.3f);
|
||||
float radius = 230 - (45 * MathF.Pow(dropT, 2));
|
||||
|
||||
float rads = currentBallAngle * MathF.PI / 180;
|
||||
float bx = 250 + (radius * MathF.Cos(rads));
|
||||
float by = 250 + (radius * MathF.Sin(rads));
|
||||
|
||||
ctx.Fill(Color.White, new EllipsePolygon(bx, by, 14));
|
||||
});
|
||||
|
||||
frame.Frames.RootFrame.Metadata.GetWebpMetadata().FrameDelay = (uint)(1000 / fps);
|
||||
animation.Frames.AddFrame(frame.Frames.RootFrame);
|
||||
frame.Dispose();
|
||||
}
|
||||
|
||||
animation.Frames.RemoveFrame(0);
|
||||
using var ms = new MemoryStream();
|
||||
animation.SaveAsWebp(ms, new WebpEncoder { FileFormat = WebpFileFormatType.Lossy, Quality = 50 });
|
||||
|
||||
return (duration, ms.ToArray());
|
||||
}
|
||||
|
||||
private static Image<Rgba32> DrawWheelBase()
|
||||
{
|
||||
var img = new Image<Rgba32>(500, 500);
|
||||
float centerX = 250, centerY = 250, outerRadius = 245, innerRadius = 170, step = 360f / 37f;
|
||||
|
||||
img.Mutate(ctx => {
|
||||
for (int i = 0; i < 37; i++) {
|
||||
float startAngle = i * step - (step / 2) - 90;
|
||||
var color = WheelNumbers[i] == 0 ? Color.Green : (i % 2 == 0 ? Color.DarkRed : Color.Black);
|
||||
var path = new PathBuilder().AddArc(centerX, centerY, outerRadius, outerRadius, 0, startAngle, step)
|
||||
.AddArc(centerX, centerY, innerRadius, innerRadius, 0, startAngle + step, -step).Build();
|
||||
ctx.Fill(color, path);
|
||||
ctx.Draw(Color.Gold, 1, path);
|
||||
|
||||
string text = WheelNumbers[i].ToString();
|
||||
float textAngle = (startAngle + (step / 2)) * MathF.PI / 180;
|
||||
float tx = centerX + ((outerRadius + innerRadius) / 2) * MathF.Cos(textAngle);
|
||||
float ty = centerY + ((outerRadius + innerRadius) / 2) * MathF.Sin(textAngle);
|
||||
|
||||
try {
|
||||
var font = SystemFonts.CreateFont("Arial", 14, FontStyle.Bold);
|
||||
ctx.DrawText(
|
||||
new DrawingOptions {
|
||||
Transform = Matrix3x2Extensions.CreateRotationDegrees(startAngle + (step / 2) + 90, new PointF(tx, ty))
|
||||
},
|
||||
text,
|
||||
font,
|
||||
Color.White,
|
||||
new PointF(tx - 6, ty - 9));
|
||||
} catch {
|
||||
// Font loading failed, skip text rendering
|
||||
}
|
||||
}
|
||||
ctx.Fill(Color.DarkSlateGray, new EllipsePolygon(centerX, centerY, innerRadius - 5));
|
||||
});
|
||||
|
||||
return img;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user