diff options
author | Leonard Kugis <leonard@kug.is> | 2022-10-10 02:09:57 +0200 |
---|---|---|
committer | Leonard Kugis <leonard@kug.is> | 2022-10-10 02:09:57 +0200 |
commit | 9a00eb67fd03d0d852f12b7546df3ef4e0c2e498 (patch) | |
tree | 05d2e553e51d3017efaef85a87b8ec4a63ded2da |
-rw-r--r-- | .gitignore | 61 | ||||
-rw-r--r-- | Cargo.toml | 14 | ||||
-rw-r--r-- | src/main.glade | 268 | ||||
-rw-r--r-- | src/main.rs | 173 |
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: >k::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 |