summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore61
-rw-r--r--Cargo.toml14
-rw-r--r--src/main.glade268
-rw-r--r--src/main.rs173
4 files changed, 516 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cf1fae1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,61 @@
+# Created by https://www.toptal.com/developers/gitignore/api/rust,windows,linux
+# Edit at https://www.toptal.com/developers/gitignore?templates=rust,windows,linux
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### Rust ###
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.toptal.com/developers/gitignore/api/rust,windows,linux
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..88b5045
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "rraffle"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+csv = "1.1"
+clap = { version = "4.0.10", features = ["derive"] }
+glib = "0.15.12"
+gtk = "0.15.5"
+rand = "0.8.5"
+text_io = "0.1.12"
diff --git a/src/main.glade b/src/main.glade
new file mode 100644
index 0000000..4d28906
--- /dev/null
+++ b/src/main.glade
@@ -0,0 +1,268 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.40.0 -->
+<interface>
+ <requires lib="gtk+" version="3.24"/>
+ <object class="GtkAdjustment" id="adjDraws">
+ <property name="upper">100</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjLots">
+ <property name="upper">10000</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ </object>
+ <object class="GtkTreeStore" id="ts">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ <!-- column-name lots -->
+ <column type="guint"/>
+ </columns>
+ </object>
+ <object class="GtkApplicationWindow" id="window">
+ <property name="can-focus">False</property>
+ <property name="title" translatable="yes">RRaffle</property>
+ <property name="resizable">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <!-- n-columns=3 n-rows=7 -->
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkLabel" id="lbl_csv">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">5</property>
+ <property name="margin-end">5</property>
+ <property name="margin-top">5</property>
+ <property name="margin-bottom">5</property>
+ <property name="label" translatable="yes">Choose CSV</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFileChooserButton" id="file_choose_csv">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">5</property>
+ <property name="margin-end">5</property>
+ <property name="margin-top">5</property>
+ <property name="margin-bottom">5</property>
+ <property name="title" translatable="yes"/>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="btn_load">
+ <property name="label" translatable="yes">Load</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="margin-start">5</property>
+ <property name="margin-end">5</property>
+ <property name="margin-top">5</property>
+ <property name="margin-bottom">5</property>
+ </object>
+ <packing>
+ <property name="left-attach">2</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="lbl_max_wins">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">5</property>
+ <property name="margin-end">5</property>
+ <property name="margin-top">5</property>
+ <property name="margin-bottom">5</property>
+ <property name="label" translatable="yes">Max Wins</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="sp_max_wins">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="margin-start">5</property>
+ <property name="margin-end">5</property>
+ <property name="margin-top">5</property>
+ <property name="margin-bottom">5</property>
+ <property name="adjustment">adjDraws</property>
+ <property name="climb-rate">1</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="btn_draw_single">
+ <property name="label" translatable="yes">Draw Single</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="margin-start">5</property>
+ <property name="margin-end">5</property>
+ <property name="margin-top">5</property>
+ <property name="margin-bottom">5</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="btn_reset">
+ <property name="label" translatable="yes">Reset Draws</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="margin-start">5</property>
+ <property name="margin-end">5</property>
+ <property name="margin-top">5</property>
+ <property name="margin-bottom">5</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">4</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="lbl_num_draws">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">5</property>
+ <property name="margin-end">5</property>
+ <property name="margin-top">5</property>
+ <property name="margin-bottom">5</property>
+ <property name="label" translatable="yes">Draws</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="btn_draw_multiple">
+ <property name="label" translatable="yes">Draw Multiple</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="margin-start">5</property>
+ <property name="margin-end">5</property>
+ <property name="margin-top">5</property>
+ <property name="margin-bottom">5</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">6</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="sp_num_draws">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="margin-start">5</property>
+ <property name="margin-end">5</property>
+ <property name="margin-top">5</property>
+ <property name="margin-bottom">5</property>
+ <property name="adjustment">adjDraws</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">5</property>
+ <property name="width">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="lbl_round">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin-start">5</property>
+ <property name="margin-end">5</property>
+ <property name="margin-top">5</property>
+ <property name="margin-bottom">5</property>
+ <property name="label" translatable="yes">Draw Round: 1</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="shadow-type">in</property>
+ <property name="min-content-width">200</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkTreeView" id="tv">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="model">ts</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection"/>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="cName">
+ <property name="title" translatable="yes">Name</property>
+ <child>
+ <object class="GtkCellRendererText" id="crName"/>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="cLots">
+ <property name="title" translatable="yes">Lots</property>
+ <child>
+ <object class="GtkCellRendererSpin" id="crLots"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..b92c136
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,173 @@
+use gtk::prelude::*;
+use clap::Parser;
+use csv;
+use rand::Rng;
+use text_io;
+
+const APP_ID: &str = "com.encrox.rraffle";
+
+#[derive(Parser, Debug)]
+struct Cli {
+ /// CSV file with names and number of lots
+ #[arg(short, long)]
+ file: Option<std::path::PathBuf>,
+
+ /// Show GUI
+ #[arg(short, long)]
+ gui: bool,
+
+ /// Shuffle animation
+ #[arg(short, long)]
+ animation: Option<std::path::PathBuf>,
+
+ /// Number of draws
+ #[arg(short, long, default_value_t = 5)]
+ draws: u32,
+
+ /// Number of wins per entry
+ #[arg(short, long, default_value_t = 2)]
+ wins: u32,
+
+ /// Interactive when in CLI mode
+ #[arg(short, long)]
+ interactive: bool,
+}
+
+#[derive(Debug)]
+struct Entry {
+ name: String,
+ lots: u32,
+ wins: u32,
+ odds: f64
+}
+
+fn load_csv(entries : &mut Vec<Entry>, file: std::path::PathBuf) {
+ let reader = csv::Reader::from_path(file);
+ for record in reader.unwrap().records() {
+ //dbg!(record.unwrap().get(0).unwrap());
+ let rec = record.unwrap();
+ entries.push(Entry {
+ name: rec.get(0).unwrap().to_string(),
+ lots: rec.get(1).unwrap().parse::<u32>().unwrap(),
+ wins: 2,
+ odds: 0.0
+ });
+ }
+}
+
+fn max_wins(entries : &mut Vec<Entry>, wins : u32) {
+ for entry in entries {
+ entry.wins = wins;
+ }
+}
+
+fn calc_odds(entries : &mut Vec<Entry>) {
+ let mut sum : u32 = 0;
+ for entry in &mut *entries {
+ sum += if entry.wins > 0 { entry.lots } else { 0 };
+ }
+ for entry in entries {
+ entry.odds = if entry.wins > 0 { entry.lots as f64 / sum as f64 } else { 0.0 };
+ }
+}
+
+fn get_winner(entries : &mut Vec<Entry>) -> &Entry {
+ let mut rng = rand::thread_rng();
+ let rn = rng.gen::<f64>();
+ let mut sum : f64 = 0.0;
+ for entry in entries {
+ if rn >= sum && rn < sum + entry.odds {
+ entry.lots -= 1;
+ entry.wins -= 1;
+ return entry;
+ }
+ sum += entry.odds;
+ }
+ unreachable!();
+}
+
+fn main() {
+ let args = Cli::parse();
+ //dbg!(args);
+
+ if args.gui {
+ // Create a new application
+ let app = gtk::Application::builder().application_id(APP_ID).build();
+
+ // Connect to "activate" signal of `app`
+ app.connect_activate(build_ui);
+
+ // Run the application
+ app.run_with_args(&[""]);
+ } else {
+ if !args.file.is_none() {
+ let mut entries = Vec::<Entry>::new();
+ load_csv(&mut entries, args.file.unwrap());
+ max_wins(&mut entries, args.wins);
+ let mut round = 1;
+ 'outer: loop {
+ calc_odds(&mut entries);
+ println!("Draw round: {}", round);
+ println!("Entries: {:?}", &entries);
+ if args.interactive {
+ print!("Option (s = start, a = abort) [s/a]: ");
+ loop {
+ let option : char = text_io::read!();
+ match option {
+ 'a' => break 'outer,
+ 's' => break,
+ _ => println!("Unknown option: {}", option),
+ }
+ }
+ }
+ let winner = get_winner(&mut entries);
+ println!("Winner is: {}", winner.name);
+ if args.interactive {
+ print!("Option (n = next, a = abort) [n/a]: ");
+ loop {
+ let option : char = text_io::read!();
+ match option {
+ 'a' => break 'outer,
+ 'n' => break,
+ _ => println!("Unknown option: {}", option),
+ }
+ }
+ }
+ if round == args.draws && !args.interactive {
+ println!("Number of draws reached.");
+ break;
+ }
+ round += 1;
+ }
+ }
+ }
+}
+
+fn build_ui(app: &gtk::Application) {
+ let glade_src = include_str!("main.glade");
+ let builder = gtk::Builder::from_string(glade_src);
+
+ let window: gtk::ApplicationWindow = builder.object("window").expect("Couldn't get window");
+ let tv : gtk::TreeView = builder.object("tv").expect("Couldn't get tree view");
+ let btn_draw_single : gtk::Button = builder.object("btn_draw_single").expect("Couldn't get button btn_draw_single");
+ let btn_draw_multiple : gtk::Button = builder.object("btn_draw_multiple").expect("Couldn't get button btn_draw_multiple");
+ let file_choose_csv : gtk::FileChooserButton = builder.object("file_choose_csv").expect("Couldn't get button file_choose_csv");
+ let btn_load : gtk::Button = builder.object("btn_load").expect("Couldn't get button btn_load");
+ let lbl_round : gtk::Label = builder.object("lbl_round").expect("Couldn't get lbl_round");
+ let btn_reset : gtk::Button = builder.object("btn_reset").expect("Couldn't get btn_reset");
+ let sp_num_draws : gtk::SpinButton = builder.object("sp_num_draws").expect("Couldn't get sp_num_draws");
+ let ts : gtk::TreeStore = builder.object("ts").expect("Couldn't get tree store");
+
+ window.set_application(Some(app));
+
+ btn_load.connect_clicked(glib::clone!(@weak file_choose_csv, @weak ts => move |button| {
+ //let data = load_csv(file_choose_csv.filename().unwrap());
+ //for entry in data {
+ let iter = ts.insert(None, -1);
+ ts.set(&iter, &[(0, &glib::Value::from("Eva")), (1, &0.to_value())]);
+ //}
+ }));
+
+ // Present window
+ window.show_all();
+} \ No newline at end of file