- Add playable ten-frame bowling against bot with two timing gates (direction/power) and fixed-step physics - Add lane match interaction, shaded ball surface, pin collisions, bonus balls, and live score sheet - Scale rooftop terrace visitor sprites to eye level according to the taller rooftop standing eye height - Resolve mode and interaction ID conflicts with Hell FPS (MODE_BOWLING=30, INTERACT_BOWL_PLAY=56) - Update launcher hash, installer package, and expansion regression test suites
629 lines
26 KiB
C#
629 lines
26 KiB
C#
using System;
|
||
using System.Diagnostics;
|
||
using System.Drawing;
|
||
using System.Drawing.Drawing2D;
|
||
using System.IO;
|
||
using System.IO.Compression;
|
||
using System.Linq;
|
||
using Microsoft.Win32;
|
||
using System.Reflection;
|
||
using System.Security.Cryptography;
|
||
using System.Threading.Tasks;
|
||
using System.Windows.Forms;
|
||
|
||
namespace PokerGameSetup
|
||
{
|
||
static class Program
|
||
{
|
||
[STAThread]
|
||
static void Main()
|
||
{
|
||
Application.EnableVisualStyles();
|
||
Application.SetCompatibleTextRenderingDefault(false);
|
||
Application.Run(new InstallerForm());
|
||
}
|
||
}
|
||
|
||
sealed class InstallerForm : Form
|
||
{
|
||
const string Marker = "<<POKER_GAME_GUI_PAYLOAD_ZIP>>";
|
||
const string ExpectedPayloadSha256 = "D44B44805C80AA6107F2F0226D0B98F0C7B085031911CB97FB89729F9B2510E6";
|
||
readonly Color bg = Color.FromArgb(15, 12, 10);
|
||
readonly Color panel = Color.FromArgb(28, 22, 18);
|
||
readonly Color gold = Color.FromArgb(236, 186, 75);
|
||
readonly Color red = Color.FromArgb(164, 26, 32);
|
||
readonly Color green = Color.FromArgb(29, 135, 64);
|
||
readonly Color text = Color.FromArgb(245, 236, 205);
|
||
|
||
CasinoArtPanel art;
|
||
Panel page;
|
||
Button backButton;
|
||
Button nextButton;
|
||
Button cancelButton;
|
||
int step;
|
||
TextBox folderBox;
|
||
CasinoProgress progress;
|
||
Label progressText;
|
||
CheckBox launchCheck;
|
||
string installedPath;
|
||
bool installing;
|
||
|
||
public InstallerForm()
|
||
{
|
||
Text = "Assembler Casino Setup";
|
||
try { Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath); } catch { }
|
||
StartPosition = FormStartPosition.CenterScreen;
|
||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||
MaximizeBox = false;
|
||
MinimizeBox = false;
|
||
ClientSize = new Size(820, 500);
|
||
BackColor = bg;
|
||
Font = new Font("Segoe UI", 10f);
|
||
|
||
art = new CasinoArtPanel { Dock = DockStyle.Left, Width = 260 };
|
||
Controls.Add(art);
|
||
|
||
page = new Panel { Left = 280, Top = 28, Width = 500, Height = 365, BackColor = panel };
|
||
page.Paint += (s, e) =>
|
||
{
|
||
using (var pen = new Pen(Color.FromArgb(88, gold), 2))
|
||
e.Graphics.DrawRectangle(pen, 0, 0, page.Width - 1, page.Height - 1);
|
||
};
|
||
Controls.Add(page);
|
||
|
||
backButton = MakeButton("Назад");
|
||
backButton.Left = 406; backButton.Top = 428; backButton.Click += (s, e) => ShowStep(step - 1);
|
||
Controls.Add(backButton);
|
||
|
||
nextButton = MakeButton("Далее");
|
||
nextButton.Left = 532; nextButton.Top = 428; nextButton.Click += NextClicked;
|
||
Controls.Add(nextButton);
|
||
|
||
cancelButton = MakeButton("Отмена");
|
||
cancelButton.Left = 658; cancelButton.Top = 428; cancelButton.Click += (s, e) => { if (!installing) Close(); };
|
||
Controls.Add(cancelButton);
|
||
|
||
ShowStep(0);
|
||
}
|
||
|
||
Button MakeButton(string caption)
|
||
{
|
||
var b = new Button
|
||
{
|
||
Text = caption,
|
||
Width = 108,
|
||
Height = 38,
|
||
FlatStyle = FlatStyle.Flat,
|
||
BackColor = Color.FromArgb(62, 43, 20),
|
||
ForeColor = text,
|
||
Font = new Font("Segoe UI", 10f, FontStyle.Bold),
|
||
Cursor = Cursors.Hand
|
||
};
|
||
b.FlatAppearance.BorderColor = gold;
|
||
b.FlatAppearance.MouseOverBackColor = Color.FromArgb(94, 61, 24);
|
||
b.FlatAppearance.MouseDownBackColor = Color.FromArgb(120, 71, 22);
|
||
return b;
|
||
}
|
||
|
||
Label MakeLabel(string caption, int x, int y, int w, int h, float size, FontStyle style)
|
||
{
|
||
var l = new Label
|
||
{
|
||
Text = caption,
|
||
Left = x,
|
||
Top = y,
|
||
Width = w,
|
||
Height = h,
|
||
ForeColor = text,
|
||
BackColor = Color.Transparent,
|
||
Font = new Font("Segoe UI", size, style)
|
||
};
|
||
page.Controls.Add(l);
|
||
return l;
|
||
}
|
||
|
||
void ShowStep(int value)
|
||
{
|
||
step = Math.Max(0, Math.Min(3, value));
|
||
page.Controls.Clear();
|
||
art.Stage = step;
|
||
art.Invalidate();
|
||
|
||
backButton.Enabled = step > 0 && !installing;
|
||
nextButton.Enabled = !installing;
|
||
cancelButton.Enabled = !installing;
|
||
nextButton.Text = step == 3 ? "Готово" : "Далее";
|
||
|
||
if (step == 0) WelcomePage();
|
||
if (step == 1) FolderPage();
|
||
if (step == 2) InstallPage();
|
||
if (step == 3) FinishPage();
|
||
}
|
||
|
||
void WelcomePage()
|
||
{
|
||
MakeLabel("Добро пожаловать", 32, 32, 430, 42, 22f, FontStyle.Bold);
|
||
MakeLabel("Установщик Assembler Casino", 34, 82, 420, 28, 13f, FontStyle.Regular);
|
||
MakeLabel("Внутри уже лежит вся игра: exe, арты, звуки, анимации, музыка башни и служебные файлы.", 34, 136, 420, 72, 10.5f, FontStyle.Regular);
|
||
MakeLabel("Сейчас выберем папку, красиво распакуем казино и положим рядом нормальный exe для запуска.", 34, 220, 420, 60, 10.5f, FontStyle.Regular);
|
||
MakeLabel("Нажми «Далее», если готов открыть двери.", 34, 315, 420, 26, 10.5f, FontStyle.Bold);
|
||
}
|
||
|
||
void FolderPage()
|
||
{
|
||
MakeLabel("Куда установить игру?", 32, 32, 430, 42, 22f, FontStyle.Bold);
|
||
MakeLabel("Можно выбрать любую папку. По умолчанию игра ставится на рабочий стол.", 34, 86, 420, 50, 10.5f, FontStyle.Regular);
|
||
|
||
folderBox = new TextBox
|
||
{
|
||
Left = 34,
|
||
Top = 160,
|
||
Width = 330,
|
||
Height = 28,
|
||
Text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "покер"),
|
||
BackColor = Color.FromArgb(9, 9, 9),
|
||
ForeColor = text,
|
||
BorderStyle = BorderStyle.FixedSingle
|
||
};
|
||
page.Controls.Add(folderBox);
|
||
|
||
var browse = MakeButton("Обзор");
|
||
browse.Left = 374; browse.Top = 154; browse.Width = 86;
|
||
browse.Parent = page;
|
||
browse.Click += (s, e) =>
|
||
{
|
||
using (var dialog = new FolderBrowserDialog())
|
||
{
|
||
dialog.Description = "Выбери папку для Assembler Casino";
|
||
dialog.SelectedPath = Directory.Exists(folderBox.Text) ? folderBox.Text : Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
|
||
if (dialog.ShowDialog(this) == DialogResult.OK)
|
||
folderBox.Text = Path.Combine(dialog.SelectedPath, "покер");
|
||
}
|
||
};
|
||
|
||
MakeLabel("Сохранения игроков будут храниться отдельно в AppData, поэтому переустановка игру не обнулит.", 34, 230, 420, 70, 10.5f, FontStyle.Regular);
|
||
}
|
||
|
||
void InstallPage()
|
||
{
|
||
MakeLabel("Установка", 32, 32, 430, 42, 22f, FontStyle.Bold);
|
||
MakeLabel("Казино распаковывается. Фишки на стол, карты в колоду, барабаны на место.", 34, 86, 420, 52, 10.5f, FontStyle.Regular);
|
||
|
||
progressText = MakeLabel("Ожидание старта...", 34, 166, 420, 28, 10.5f, FontStyle.Bold);
|
||
progress = new CasinoProgress { Left = 34, Top = 208, Width = 420, Height = 34 };
|
||
page.Controls.Add(progress);
|
||
BeginInstall();
|
||
}
|
||
|
||
void FinishPage()
|
||
{
|
||
MakeLabel("Готово", 32, 32, 430, 42, 22f, FontStyle.Bold);
|
||
MakeLabel("Assembler Casino установлено. Можно запускать и проверять, как оно выглядит уже на другом компьютере.", 34, 86, 420, 70, 10.5f, FontStyle.Regular);
|
||
MakeLabel("Удачи за столами. И да, казино все еще подозрительно смотрит на чит-коды.", 34, 176, 420, 48, 10.5f, FontStyle.Regular);
|
||
|
||
launchCheck = new CheckBox
|
||
{
|
||
Text = "Запустить игру после закрытия установщика",
|
||
Left = 34,
|
||
Top = 275,
|
||
Width = 390,
|
||
Height = 28,
|
||
Checked = true,
|
||
BackColor = Color.Transparent,
|
||
ForeColor = text
|
||
};
|
||
page.Controls.Add(launchCheck);
|
||
cancelButton.Text = "Закрыть";
|
||
}
|
||
|
||
async void BeginInstall()
|
||
{
|
||
installing = true;
|
||
backButton.Enabled = nextButton.Enabled = cancelButton.Enabled = false;
|
||
try
|
||
{
|
||
var target = folderBox != null && !string.IsNullOrWhiteSpace(folderBox.Text)
|
||
? folderBox.Text.Trim()
|
||
: Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "покер");
|
||
|
||
await Task.Run(() => ExtractPayload(target));
|
||
installedPath = target;
|
||
|
||
installing = false;
|
||
ShowStep(3);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
installing = false;
|
||
backButton.Enabled = nextButton.Enabled = cancelButton.Enabled = true;
|
||
MessageBox.Show(this, ex.Message, "Ошибка установки", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||
ShowStep(1);
|
||
}
|
||
}
|
||
|
||
void ExtractPayload(string target)
|
||
{
|
||
Directory.CreateDirectory(target);
|
||
Directory.CreateDirectory(Path.Combine(target, "runtime"));
|
||
|
||
var exe = Assembly.GetExecutingAssembly().Location;
|
||
var marker = System.Text.Encoding.UTF8.GetBytes(Marker);
|
||
|
||
SetProgress(0, "Поиск архива игры...");
|
||
long payloadStart = -1;
|
||
using (var fs = File.OpenRead(exe))
|
||
{
|
||
payloadStart = FindMarkerInStream(fs, marker);
|
||
}
|
||
if (payloadStart < 0) throw new InvalidOperationException("В установщике не найден архив игры.");
|
||
|
||
var tempZip = Path.Combine(Path.GetTempPath(), "assembler_casino_payload_" + Guid.NewGuid().ToString("N") + ".zip");
|
||
try
|
||
{
|
||
SetProgress(5, "Проверка целостности архива...");
|
||
using (var fs = File.OpenRead(exe))
|
||
{
|
||
fs.Position = payloadStart;
|
||
using (var outFs = File.Create(tempZip, 65536))
|
||
using (var sha = SHA256.Create())
|
||
{
|
||
var buf = new byte[65536];
|
||
int r;
|
||
while ((r = fs.Read(buf, 0, buf.Length)) > 0)
|
||
{
|
||
sha.TransformBlock(buf, 0, r, null, 0);
|
||
outFs.Write(buf, 0, r);
|
||
}
|
||
sha.TransformFinalBlock(buf, 0, 0);
|
||
var actual = BitConverter.ToString(sha.Hash).Replace("-", "");
|
||
if (!string.IsNullOrWhiteSpace(ExpectedPayloadSha256) &&
|
||
ExpectedPayloadSha256 != "TO_BE_FILLED_BY_RELEASE_BUILD" &&
|
||
!actual.Equals(ExpectedPayloadSha256, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
throw new InvalidOperationException("Архив игры внутри установщика был изменен. Установка остановлена.");
|
||
}
|
||
}
|
||
}
|
||
|
||
using (var zip = ZipFile.OpenRead(tempZip))
|
||
{
|
||
var files = zip.Entries.Where(e => !string.IsNullOrEmpty(e.Name)).ToList();
|
||
for (int i = 0; i < files.Count; i++)
|
||
{
|
||
var entry = files[i];
|
||
var full = Path.GetFullPath(Path.Combine(target, entry.FullName));
|
||
if (!full.StartsWith(Path.GetFullPath(target), StringComparison.OrdinalIgnoreCase))
|
||
throw new InvalidOperationException("Архив содержит некорректный путь.");
|
||
|
||
Directory.CreateDirectory(Path.GetDirectoryName(full));
|
||
entry.ExtractToFile(full, true);
|
||
int percent = (int)((i + 1) * 100.0 / Math.Max(1, files.Count));
|
||
SetProgress(percent, "Устанавливаем: " + entry.FullName.Replace('/', '\\'));
|
||
}
|
||
}
|
||
|
||
var oldBat = Path.Combine(target, "Assembler Casino.bat");
|
||
try { if (File.Exists(oldBat)) File.Delete(oldBat); } catch { }
|
||
CreateDesktopShortcut(target);
|
||
RegisterUninstaller(target);
|
||
SetProgress(100, "Казино установлено.");
|
||
}
|
||
finally
|
||
{
|
||
try { if (File.Exists(tempZip)) File.Delete(tempZip); } catch { }
|
||
}
|
||
}
|
||
|
||
|
||
void CreateDesktopShortcut(string target)
|
||
{
|
||
try
|
||
{
|
||
var shellType = Type.GetTypeFromProgID("WScript.Shell");
|
||
if (shellType == null) return;
|
||
dynamic shell = Activator.CreateInstance(shellType);
|
||
var desktop = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
|
||
var linkPath = Path.Combine(desktop, "Assembler Casino.lnk");
|
||
dynamic shortcut = shell.CreateShortcut(linkPath);
|
||
var launcher = Path.Combine(target, "Assembler Casino.exe");
|
||
var icon = Path.Combine(target, "assets", "icons", "casino.ico");
|
||
shortcut.TargetPath = launcher;
|
||
shortcut.WorkingDirectory = target;
|
||
shortcut.IconLocation = File.Exists(icon) ? icon : launcher + ",0";
|
||
shortcut.Description = "Assembler Casino";
|
||
shortcut.Save();
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
void RegisterUninstaller(string target)
|
||
{
|
||
try
|
||
{
|
||
using (var key = Registry.CurrentUser.CreateSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\AssemblerCasino"))
|
||
{
|
||
if (key == null) return;
|
||
var launcher = Path.Combine(target, "Assembler Casino.exe");
|
||
var uninstaller = Path.Combine(target, "Uninstall Assembler Casino.exe");
|
||
var icon = Path.Combine(target, "assets", "icons", "casino.ico");
|
||
key.SetValue("DisplayName", "Assembler Casino");
|
||
key.SetValue("DisplayVersion", "1.0");
|
||
key.SetValue("Publisher", "Assembler Casino");
|
||
key.SetValue("InstallLocation", target);
|
||
key.SetValue("DisplayIcon", File.Exists(icon) ? icon : launcher);
|
||
key.SetValue("UninstallString", "\"" + uninstaller + "\"");
|
||
key.SetValue("NoModify", 1, RegistryValueKind.DWord);
|
||
key.SetValue("NoRepair", 1, RegistryValueKind.DWord);
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
long FindMarkerInStream(Stream stream, byte[] marker)
|
||
{
|
||
var buffer = new byte[1024 * 1024];
|
||
long streamPos = 0;
|
||
int bytesRead;
|
||
int overlap = marker.Length - 1;
|
||
int carry = 0;
|
||
|
||
while ((bytesRead = stream.Read(buffer, carry, buffer.Length - carry)) > 0)
|
||
{
|
||
int total = carry + bytesRead;
|
||
int idx = FindMarker(buffer, marker, total);
|
||
if (idx >= 0)
|
||
{
|
||
return streamPos + idx + marker.Length;
|
||
}
|
||
if (total >= overlap)
|
||
{
|
||
Buffer.BlockCopy(buffer, total - overlap, buffer, 0, overlap);
|
||
carry = overlap;
|
||
streamPos += (total - overlap);
|
||
}
|
||
else
|
||
{
|
||
carry = total;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
int FindMarker(byte[] bytes, byte[] marker)
|
||
{
|
||
return FindMarker(bytes, marker, bytes.Length);
|
||
}
|
||
|
||
int FindMarker(byte[] bytes, byte[] marker, int length)
|
||
{
|
||
for (int i = 0; i <= length - marker.Length; i++)
|
||
{
|
||
if (bytes[i] != marker[0]) continue;
|
||
bool ok = true;
|
||
for (int j = 1; j < marker.Length; j++)
|
||
{
|
||
if (bytes[i + j] != marker[j]) { ok = false; break; }
|
||
}
|
||
if (ok) return i;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
void SetProgress(int value, string caption)
|
||
{
|
||
if (InvokeRequired)
|
||
{
|
||
BeginInvoke(new Action<int, string>(SetProgress), value, caption);
|
||
return;
|
||
}
|
||
if (progress != null) progress.Value = value;
|
||
if (progressText != null) progressText.Text = caption;
|
||
}
|
||
|
||
void NextClicked(object sender, EventArgs e)
|
||
{
|
||
if (step == 1)
|
||
{
|
||
if (folderBox == null || string.IsNullOrWhiteSpace(folderBox.Text))
|
||
{
|
||
MessageBox.Show(this, "Выбери папку установки.", "Assembler Casino", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (step == 2) return;
|
||
if (step == 3)
|
||
{
|
||
if (launchCheck != null && launchCheck.Checked)
|
||
{
|
||
var root = !string.IsNullOrWhiteSpace(installedPath) ? installedPath : folderBox.Text.Trim();
|
||
var path = Path.Combine(root, "Assembler Casino.exe");
|
||
if (File.Exists(path)) Process.Start(path);
|
||
}
|
||
Close();
|
||
return;
|
||
}
|
||
ShowStep(step + 1);
|
||
}
|
||
}
|
||
|
||
sealed class CasinoProgress : Control
|
||
{
|
||
int value;
|
||
public int Value
|
||
{
|
||
get { return value; }
|
||
set { this.value = Math.Max(0, Math.Min(100, value)); Invalidate(); }
|
||
}
|
||
|
||
public CasinoProgress()
|
||
{
|
||
DoubleBuffered = true;
|
||
}
|
||
|
||
protected override void OnPaint(PaintEventArgs e)
|
||
{
|
||
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||
var r = ClientRectangle;
|
||
r.Width -= 1; r.Height -= 1;
|
||
using (var b = new SolidBrush(Color.FromArgb(8, 8, 8))) e.Graphics.FillRectangle(b, r);
|
||
using (var p = new Pen(Color.FromArgb(236, 186, 75), 2)) e.Graphics.DrawRectangle(p, r);
|
||
|
||
var fill = new Rectangle(4, 4, (int)((Width - 9) * value / 100.0), Height - 9);
|
||
if (fill.Width > 0 && fill.Height > 0)
|
||
{
|
||
using (var brush = new LinearGradientBrush(fill, Color.FromArgb(30, 130, 60), Color.FromArgb(236, 186, 75), 0f))
|
||
e.Graphics.FillRectangle(brush, fill);
|
||
}
|
||
|
||
var caption = value + "%";
|
||
using (var font = new Font("Consolas", 10f, FontStyle.Bold))
|
||
using (var brush = new SolidBrush(Color.White))
|
||
{
|
||
var sz = e.Graphics.MeasureString(caption, font);
|
||
e.Graphics.DrawString(caption, font, brush, (Width - sz.Width) / 2, (Height - sz.Height) / 2);
|
||
}
|
||
}
|
||
}
|
||
|
||
sealed class CasinoArtPanel : Control
|
||
{
|
||
public int Stage { get; set; }
|
||
readonly Color gold = Color.FromArgb(236, 186, 75);
|
||
readonly Color red = Color.FromArgb(166, 24, 31);
|
||
readonly Color green = Color.FromArgb(20, 112, 54);
|
||
|
||
public CasinoArtPanel()
|
||
{
|
||
DoubleBuffered = true;
|
||
}
|
||
|
||
protected override void OnPaint(PaintEventArgs e)
|
||
{
|
||
var g = e.Graphics;
|
||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||
using (var br = new LinearGradientBrush(ClientRectangle, Color.FromArgb(34, 12, 14), Color.FromArgb(7, 55, 30), 90f))
|
||
g.FillRectangle(br, ClientRectangle);
|
||
|
||
DrawTitle(g);
|
||
DrawCards(g, 28, 96);
|
||
DrawRoulette(g, 132, 235);
|
||
DrawChips(g, 48, 345);
|
||
DrawStageDots(g);
|
||
|
||
using (var pen = new Pen(Color.FromArgb(120, gold), 2))
|
||
g.DrawLine(pen, Width - 1, 0, Width - 1, Height);
|
||
}
|
||
|
||
void DrawTitle(Graphics g)
|
||
{
|
||
using (var f1 = new Font("Georgia", 20f, FontStyle.Bold))
|
||
using (var f2 = new Font("Segoe UI", 9f, FontStyle.Bold))
|
||
using (var b1 = new SolidBrush(gold))
|
||
using (var b2 = new SolidBrush(Color.FromArgb(235, 235, 220)))
|
||
{
|
||
g.DrawString("ASSEMBLER", f1, b1, 24, 24);
|
||
g.DrawString("CASINO SETUP", f2, b2, 62, 58);
|
||
}
|
||
}
|
||
|
||
void DrawCards(Graphics g, int x, int y)
|
||
{
|
||
DrawCard(g, x + 28, y + 8, -10, red, "A", "♥");
|
||
DrawCard(g, x + 76, y, 8, Color.Black, "K", "♠");
|
||
DrawCard(g, x + 124, y + 14, 14, red, "7", "♦");
|
||
}
|
||
|
||
void DrawCard(Graphics g, int x, int y, float angle, Color suitColor, string rank, string suit)
|
||
{
|
||
var state = g.Save();
|
||
g.TranslateTransform(x + 30, y + 42);
|
||
g.RotateTransform(angle);
|
||
var r = new Rectangle(-30, -42, 60, 84);
|
||
using (var path = RoundRect(r, 6))
|
||
using (var br = new SolidBrush(Color.FromArgb(245, 241, 225)))
|
||
using (var pen = new Pen(gold, 2))
|
||
{
|
||
g.FillPath(br, path);
|
||
g.DrawPath(pen, path);
|
||
}
|
||
using (var f = new Font("Georgia", 17f, FontStyle.Bold))
|
||
using (var b = new SolidBrush(suitColor))
|
||
{
|
||
g.DrawString(rank, f, b, -20, -32);
|
||
g.DrawString(suit, f, b, -10, 4);
|
||
}
|
||
g.Restore(state);
|
||
}
|
||
|
||
void DrawRoulette(Graphics g, int x, int y)
|
||
{
|
||
var outer = new Rectangle(x - 68, y - 68, 136, 136);
|
||
using (var br = new SolidBrush(Color.FromArgb(96, 48, 22))) g.FillEllipse(br, outer);
|
||
using (var p = new Pen(gold, 5)) g.DrawEllipse(p, outer);
|
||
for (int i = 0; i < 12; i++)
|
||
{
|
||
var a = i * 30.0 * Math.PI / 180.0;
|
||
var c = (i % 3 == 0) ? green : (i % 2 == 0 ? red : Color.FromArgb(12, 12, 12));
|
||
using (var p = new Pen(c, 8))
|
||
g.DrawLine(p, x, y, x + (float)Math.Cos(a) * 58, y + (float)Math.Sin(a) * 58);
|
||
}
|
||
using (var br = new SolidBrush(Color.FromArgb(30, 120, 50))) g.FillEllipse(br, x - 36, y - 36, 72, 72);
|
||
using (var p = new Pen(Color.FromArgb(255, 230, 150), 3)) g.DrawEllipse(p, x - 36, y - 36, 72, 72);
|
||
using (var br = new SolidBrush(Color.White)) g.FillEllipse(br, x + 18, y - 55, 12, 12);
|
||
using (var p = new Pen(Color.FromArgb(255, 230, 150), 4))
|
||
{
|
||
g.DrawLine(p, x, y, x + 44, y - 18);
|
||
g.DrawLine(p, x, y, x - 36, y + 26);
|
||
g.DrawLine(p, x, y, x + 8, y + 48);
|
||
}
|
||
}
|
||
|
||
void DrawChips(Graphics g, int x, int y)
|
||
{
|
||
DrawChip(g, x, y, red, "$");
|
||
DrawChip(g, x + 48, y + 28, green, "21");
|
||
DrawChip(g, x + 106, y + 2, Color.FromArgb(25, 70, 160), "VIP");
|
||
}
|
||
|
||
void DrawChip(Graphics g, int x, int y, Color c, string label)
|
||
{
|
||
using (var br = new SolidBrush(c)) g.FillEllipse(br, x, y, 54, 54);
|
||
using (var p = new Pen(Color.White, 4)) g.DrawEllipse(p, x + 5, y + 5, 44, 44);
|
||
using (var p = new Pen(gold, 2)) g.DrawEllipse(p, x + 1, y + 1, 52, 52);
|
||
using (var f = new Font("Segoe UI", 9f, FontStyle.Bold))
|
||
using (var b = new SolidBrush(Color.White))
|
||
{
|
||
var sz = g.MeasureString(label, f);
|
||
g.DrawString(label, f, b, x + 27 - sz.Width / 2, y + 27 - sz.Height / 2);
|
||
}
|
||
}
|
||
|
||
void DrawStageDots(Graphics g)
|
||
{
|
||
int y = Height - 44;
|
||
for (int i = 0; i < 4; i++)
|
||
{
|
||
var c = i <= Stage ? gold : Color.FromArgb(90, 80, 62);
|
||
using (var br = new SolidBrush(c)) g.FillEllipse(br, 78 + i * 28, y, 12, 12);
|
||
}
|
||
}
|
||
|
||
GraphicsPath RoundRect(Rectangle r, int radius)
|
||
{
|
||
int d = radius * 2;
|
||
var path = new GraphicsPath();
|
||
path.AddArc(r.X, r.Y, d, d, 180, 90);
|
||
path.AddArc(r.Right - d, r.Y, d, d, 270, 90);
|
||
path.AddArc(r.Right - d, r.Bottom - d, d, d, 0, 90);
|
||
path.AddArc(r.X, r.Bottom - d, d, d, 90, 90);
|
||
path.CloseFigure();
|
||
return path;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|