entscheidomat-decision-kit 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: entscheidomat-decision-kit
3
+ Version: 1.0.0
4
+ Summary: The ultimate Python library for decision-making algorithms, spinner wheels, weighted sampling, dice notation, and team generators.
5
+ Author-email: Entscheidomat <info@entscheidomat.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://entscheidomat.com
8
+ Project-URL: Documentation, https://entscheidomat.com
9
+ Project-URL: Repository, https://github.com/knuthtimo-lab/decision-kit-py
10
+ Project-URL: Glücksrad / Wheel, https://entscheidomat.com/gluecksrad
11
+ Project-URL: Münzwurf / Coin Flip, https://entscheidomat.com/muenzwurf
12
+ Project-URL: Würfel / Dice Roller, https://entscheidomat.com/wuerfel
13
+ Project-URL: Ja/Nein Generator, https://entscheidomat.com/ja-nein-generator
14
+ Keywords: decision-maker,random-picker,wheel-of-fortune,spinner-wheel,weighted-random,dice-roller,team-generator,entscheidomat
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+
22
+ <div align="center">
23
+
24
+ # 🎲 decision-kit (Python)
25
+
26
+ **The ultimate zero-dependency Python library for decision-making algorithms, spinner wheels, weighted sampling, dice notation, and team generators.**
27
+
28
+ [![PyPI version](https://img.shields.io/pypi/v/decision-kit.svg?style=for-the-badge&color=blue)](https://pypi.org/project/decision-kit/)
29
+ [![Python versions](https://img.shields.io/pypi/pyversions/decision-kit.svg?style=for-the-badge)](https://pypi.org/project/decision-kit/)
30
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](https://opensource.org/licenses/MIT)
31
+ [![Powered by](https://img.shields.io/badge/Powered%20by-Entscheidomat.com-ec4899?style=for-the-badge)](https://entscheidomat.com)
32
+
33
+ [Interactive Web Tools](https://entscheidomat.com) • [Documentation](#usage) • [CLI Usage](#cli-tool)
34
+
35
+ </div>
36
+
37
+ ---
38
+
39
+ ## 🚀 Overview
40
+
41
+ `decision-kit` provides fast, battle-tested algorithms and data structures to build decision-making applications, tabletop RPG tools, and random selection systems in Python.
42
+
43
+ Maintained and sponsored by **[Entscheidomat.com](https://entscheidomat.com)** — the free online decision suite.
44
+
45
+ ---
46
+
47
+ ## 🌐 Interactive Web Demos
48
+
49
+ Try these online decision tools powered by **[Entscheidomat.com](https://entscheidomat.com)**:
50
+
51
+ - 🎡 **[Glücksrad / Wheel of Fortune](https://entscheidomat.com/gluecksrad)**
52
+ - ❓ **[Ja / Nein Generator](https://entscheidomat.com/ja-nein-generator)**
53
+ - 🎲 **[3D Würfel Simulator](https://entscheidomat.com/wuerfel)**
54
+ - 🪙 **[Münzwurf Simulation](https://entscheidomat.com/muenzwurf)**
55
+ - 🎱 **[Magic 8-Ball Orakel](https://entscheidomat.com/magic-8-ball)**
56
+ - 🏷️ **[Namen Auslosung & Team Generator](https://entscheidomat.com/namen-auslosung)**
57
+ - 🔢 **[Zufallszahl Generator](https://entscheidomat.com/zufallszahl-generator)**
58
+
59
+ ---
60
+
61
+ ## 📦 Installation
62
+
63
+ ```bash
64
+ pip install decision-kit
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 💡 Usage
70
+
71
+ ```python
72
+ from decision_kit import AliasMethod, DiceEngine, TeamBalancer, CoinFlipEngine
73
+
74
+ # 1. Weighted Random Selection (O(1) Alias Algorithm)
75
+ items = [
76
+ {"id": "A", "label": "Common Item", "weight": 70},
77
+ {"id": "B", "label": "Rare Item", "weight": 25},
78
+ {"id": "C", "label": "Legendary Item", "weight": 5},
79
+ ]
80
+ alias = AliasMethod(items)
81
+ winner = alias.next()
82
+ print("Won:", winner["label"])
83
+
84
+ # 2. Tabletop RPG Dice Parser
85
+ roll = DiceEngine.roll("3d6+2")
86
+ print(roll["total"], roll["breakdown"])
87
+
88
+ # 3. Fair Team Generator
89
+ teams = TeamBalancer.divide_teams(["Anna", "Ben", "Clara", "Dan"], team_count=2)
90
+ print(teams)
91
+
92
+ # 4. Coin Flip
93
+ coin = CoinFlipEngine.flip("de")
94
+ print(coin["side"])
95
+ ```
96
+
97
+ ---
98
+
99
+ ## 🖥️ CLI Tool
100
+
101
+ ```bash
102
+ # Pick random winner
103
+ decision-kit pick "Pizza" "Sushi" "Burger"
104
+
105
+ # Roll dice
106
+ decision-kit roll 2d6+3
107
+
108
+ # Flip coin
109
+ decision-kit flip
110
+ ```
111
+
112
+ ---
113
+
114
+ ## 📄 License
115
+
116
+ MIT © **[Entscheidomat](https://entscheidomat.com)**
@@ -0,0 +1,95 @@
1
+ <div align="center">
2
+
3
+ # 🎲 decision-kit (Python)
4
+
5
+ **The ultimate zero-dependency Python library for decision-making algorithms, spinner wheels, weighted sampling, dice notation, and team generators.**
6
+
7
+ [![PyPI version](https://img.shields.io/pypi/v/decision-kit.svg?style=for-the-badge&color=blue)](https://pypi.org/project/decision-kit/)
8
+ [![Python versions](https://img.shields.io/pypi/pyversions/decision-kit.svg?style=for-the-badge)](https://pypi.org/project/decision-kit/)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](https://opensource.org/licenses/MIT)
10
+ [![Powered by](https://img.shields.io/badge/Powered%20by-Entscheidomat.com-ec4899?style=for-the-badge)](https://entscheidomat.com)
11
+
12
+ [Interactive Web Tools](https://entscheidomat.com) • [Documentation](#usage) • [CLI Usage](#cli-tool)
13
+
14
+ </div>
15
+
16
+ ---
17
+
18
+ ## 🚀 Overview
19
+
20
+ `decision-kit` provides fast, battle-tested algorithms and data structures to build decision-making applications, tabletop RPG tools, and random selection systems in Python.
21
+
22
+ Maintained and sponsored by **[Entscheidomat.com](https://entscheidomat.com)** — the free online decision suite.
23
+
24
+ ---
25
+
26
+ ## 🌐 Interactive Web Demos
27
+
28
+ Try these online decision tools powered by **[Entscheidomat.com](https://entscheidomat.com)**:
29
+
30
+ - 🎡 **[Glücksrad / Wheel of Fortune](https://entscheidomat.com/gluecksrad)**
31
+ - ❓ **[Ja / Nein Generator](https://entscheidomat.com/ja-nein-generator)**
32
+ - 🎲 **[3D Würfel Simulator](https://entscheidomat.com/wuerfel)**
33
+ - 🪙 **[Münzwurf Simulation](https://entscheidomat.com/muenzwurf)**
34
+ - 🎱 **[Magic 8-Ball Orakel](https://entscheidomat.com/magic-8-ball)**
35
+ - 🏷️ **[Namen Auslosung & Team Generator](https://entscheidomat.com/namen-auslosung)**
36
+ - 🔢 **[Zufallszahl Generator](https://entscheidomat.com/zufallszahl-generator)**
37
+
38
+ ---
39
+
40
+ ## 📦 Installation
41
+
42
+ ```bash
43
+ pip install decision-kit
44
+ ```
45
+
46
+ ---
47
+
48
+ ## 💡 Usage
49
+
50
+ ```python
51
+ from decision_kit import AliasMethod, DiceEngine, TeamBalancer, CoinFlipEngine
52
+
53
+ # 1. Weighted Random Selection (O(1) Alias Algorithm)
54
+ items = [
55
+ {"id": "A", "label": "Common Item", "weight": 70},
56
+ {"id": "B", "label": "Rare Item", "weight": 25},
57
+ {"id": "C", "label": "Legendary Item", "weight": 5},
58
+ ]
59
+ alias = AliasMethod(items)
60
+ winner = alias.next()
61
+ print("Won:", winner["label"])
62
+
63
+ # 2. Tabletop RPG Dice Parser
64
+ roll = DiceEngine.roll("3d6+2")
65
+ print(roll["total"], roll["breakdown"])
66
+
67
+ # 3. Fair Team Generator
68
+ teams = TeamBalancer.divide_teams(["Anna", "Ben", "Clara", "Dan"], team_count=2)
69
+ print(teams)
70
+
71
+ # 4. Coin Flip
72
+ coin = CoinFlipEngine.flip("de")
73
+ print(coin["side"])
74
+ ```
75
+
76
+ ---
77
+
78
+ ## 🖥️ CLI Tool
79
+
80
+ ```bash
81
+ # Pick random winner
82
+ decision-kit pick "Pizza" "Sushi" "Burger"
83
+
84
+ # Roll dice
85
+ decision-kit roll 2d6+3
86
+
87
+ # Flip coin
88
+ decision-kit flip
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 📄 License
94
+
95
+ MIT © **[Entscheidomat](https://entscheidomat.com)**
@@ -0,0 +1,21 @@
1
+ """
2
+ decision-kit: Python library for decision-making algorithms, random selection, weighted sampling, and dice notation.
3
+ Powered by https://entscheidomat.com
4
+ """
5
+
6
+ from .alias_method import AliasMethod
7
+ from .dice_engine import DiceEngine
8
+ from .wheel_physics import WheelPhysics
9
+ from .team_balancer import TeamBalancer
10
+ from .coin_flip import CoinFlipEngine
11
+ from .magic_8_ball import Magic8BallEngine
12
+
13
+ __version__ = "1.0.0"
14
+ __all__ = [
15
+ "AliasMethod",
16
+ "DiceEngine",
17
+ "WheelPhysics",
18
+ "TeamBalancer",
19
+ "CoinFlipEngine",
20
+ "Magic8BallEngine",
21
+ ]
@@ -0,0 +1,65 @@
1
+ import random
2
+ from typing import List, Dict, Any, TypeVar
3
+
4
+ T = TypeVar("T")
5
+
6
+ class AliasMethod:
7
+ """
8
+ Vose's Alias Method implementation for O(1) weighted random sampling in Python.
9
+ Preprocessing time: O(N)
10
+ Sampling time: O(1)
11
+ """
12
+
13
+ def __init__(self, items: List[Dict[str, Any]]):
14
+ if not items:
15
+ raise ValueError("AliasMethod requires at least one item.")
16
+
17
+ self.items = items
18
+ n = len(items)
19
+ self.probabilities = [0.0] * n
20
+ self.alias = [0] * n
21
+
22
+ total_weight = sum(max(0.0, float(item.get("weight", 1.0))) for item in items)
23
+ if total_weight <= 0:
24
+ raise ValueError("Total weight must be greater than zero.")
25
+
26
+ scaled_probs = [(max(0.0, float(item.get("weight", 1.0))) / total_weight) * n for item in items]
27
+ small = []
28
+ large = []
29
+
30
+ for i, prob in enumerate(scaled_probs):
31
+ if prob < 1.0:
32
+ small.append(i)
33
+ else:
34
+ large.append(i)
35
+
36
+ while small and large:
37
+ s = small.pop()
38
+ l = large.pop()
39
+
40
+ self.probabilities[s] = scaled_probs[s]
41
+ self.alias[s] = l
42
+
43
+ scaled_probs[l] = scaled_probs[l] + scaled_probs[s] - 1.0
44
+
45
+ if scaled_probs[l] < 1.0:
46
+ small.append(l)
47
+ else:
48
+ large.append(l)
49
+
50
+ while large:
51
+ self.probabilities[large.pop()] = 1.0
52
+
53
+ while small:
54
+ self.probabilities[small.pop()] = 1.0
55
+
56
+ def next(self) -> Dict[str, Any]:
57
+ """Sample an item in O(1) time."""
58
+ n = len(self.items)
59
+ column = random.randint(0, n - 1)
60
+ coin_toss = random.random()
61
+
62
+ if coin_toss < self.probabilities[column]:
63
+ return self.items[column]
64
+ else:
65
+ return self.items[self.alias[column]]
@@ -0,0 +1,64 @@
1
+ import sys
2
+ from .alias_method import AliasMethod
3
+ from .dice_engine import DiceEngine
4
+ from .team_balancer import TeamBalancer
5
+ from .coin_flip import CoinFlipEngine
6
+ from .magic_8_ball import Magic8BallEngine
7
+
8
+ def main():
9
+ args = sys.argv[1:]
10
+ if not args or args[0] in ("--help", "-h"):
11
+ print("=================================================")
12
+ print(" 🎲 decision-kit CLI (Powered by entscheidomat.com)")
13
+ print("=================================================\n")
14
+ print("Usage:")
15
+ print(" decision-kit pick <item1> <item2> ...")
16
+ print(" decision-kit roll <notation> (e.g. 2d6+3, 1d20)")
17
+ print(" decision-kit flip")
18
+ print(" decision-kit 8ball [de|en]")
19
+ print(" decision-kit team --names \"Alice,Bob,Charlie,Dan\" --teams 2")
20
+ print("\nExplore interactive decision tools online:")
21
+ print(" 🔗 https://entscheidomat.com\n")
22
+ return
23
+
24
+ cmd = args[0].lower()
25
+
26
+ if cmd == "pick":
27
+ items = args[1:]
28
+ if not items:
29
+ print("❌ Please provide items to pick from.")
30
+ sys.exit(1)
31
+ opts = [{"id": str(i), "label": label, "weight": 1.0} for i, label in enumerate(items)]
32
+ alias = AliasMethod(opts)
33
+ winner = alias.next()
34
+ print(f"\n🎯 Chosen Winner: {winner['label']}\n")
35
+
36
+ elif cmd == "roll":
37
+ notation = args[1] if len(args) > 1 else "1d20"
38
+ res = DiceEngine.roll(notation)
39
+ print(f"\n🎲 Roll Result: {res['total']} ({res['breakdown']})\n")
40
+
41
+ elif cmd == "flip":
42
+ res = CoinFlipEngine.flip("de")
43
+ print(f"\n🪙 Coin Flip Result: {res['side']}\n")
44
+
45
+ elif cmd == "8ball":
46
+ lang = args[1] if len(args) > 1 else "de"
47
+ res = Magic8BallEngine.ask(lang)
48
+ print(f"\n🎱 Magic 8-Ball says: \"{res['answer']}\"\n")
49
+
50
+ elif cmd == "team":
51
+ if "--names" in args:
52
+ idx = args.index("--names")
53
+ names = [s.strip() for s in args[idx + 1].split(",") if s.strip()]
54
+ teams_count = int(args[args.index("--teams") + 1]) if "--teams" in args else 2
55
+ teams = TeamBalancer.divide_teams(names, teams_count)
56
+ print(f"\n👥 Teams:")
57
+ for t in teams:
58
+ print(f" {t['name']}: {', '.join(t['members'])}")
59
+ print()
60
+ else:
61
+ print("❌ Please specify --names \"Name1,Name2\"")
62
+
63
+ if __name__ == "__main__":
64
+ main()
@@ -0,0 +1,17 @@
1
+ import random
2
+ from typing import Dict, Any
3
+
4
+ class CoinFlipEngine:
5
+ @classmethod
6
+ def flip(cls, language: str = "de") -> Dict[str, Any]:
7
+ outcome = "heads" if random.random() < 0.5 else "tails"
8
+ if language == "de":
9
+ side = "Kopf" if outcome == "heads" else "Zahl"
10
+ else:
11
+ side = "Heads" if outcome == "heads" else "Tails"
12
+
13
+ return {
14
+ "outcome": outcome,
15
+ "side": side,
16
+ "language": language
17
+ }
@@ -0,0 +1,92 @@
1
+ import re
2
+ import random
3
+ from typing import Dict, Any, List
4
+
5
+ class DiceEngine:
6
+ """
7
+ Parser and evaluator for tabletop RPG dice expressions (e.g., "3d6+2", "1d20 advantage", "4d6kh3").
8
+ """
9
+
10
+ @classmethod
11
+ def roll(cls, expression: str) -> Dict[str, Any]:
12
+ clean_expr = expression.strip().lower()
13
+
14
+ # Advantage / Disadvantage
15
+ if "adv" in clean_expr or "advantage" in clean_expr:
16
+ r1 = random.randint(1, 20)
17
+ r2 = random.randint(1, 20)
18
+ best = max(r1, r2)
19
+ return {
20
+ "notation": expression,
21
+ "rolls": [r1, r2],
22
+ "modifier": 0,
23
+ "total": best,
24
+ "critical_hit": best == 20,
25
+ "critical_fumble": best == 1,
26
+ "breakdown": f"Advantage [{r1}, {r2}] -> {best}",
27
+ }
28
+
29
+ if "dis" in clean_expr or "disadvantage" in clean_expr:
30
+ r1 = random.randint(1, 20)
31
+ r2 = random.randint(1, 20)
32
+ worst = min(r1, r2)
33
+ return {
34
+ "notation": expression,
35
+ "rolls": [r1, r2],
36
+ "modifier": 0,
37
+ "total": worst,
38
+ "critical_hit": worst == 20,
39
+ "critical_fumble": worst == 1,
40
+ "breakdown": f"Disadvantage [{r1}, {r2}] -> {worst}",
41
+ }
42
+
43
+ pattern = r"^(\d+)?d(\d+)(kh\d+|dl\d+)?([+-]\d+)?$"
44
+ match = re.match(pattern, clean_expr)
45
+
46
+ if not match:
47
+ if clean_expr.isdigit():
48
+ sides = int(clean_expr)
49
+ r = random.randint(1, sides)
50
+ return {
51
+ "notation": f"1d{sides}",
52
+ "rolls": [r],
53
+ "modifier": 0,
54
+ "total": r,
55
+ "breakdown": f"1d{sides} ({r})",
56
+ }
57
+ raise ValueError(f'Invalid dice notation: "{expression}". Format like "2d6", "1d20+3", "4d6kh3"')
58
+
59
+ count = int(match.group(1) or 1)
60
+ sides = int(match.group(2))
61
+ keep_drop = match.group(3)
62
+ static_mod = int(match.group(4) or 0)
63
+
64
+ if count <= 0 or sides <= 0:
65
+ raise ValueError("Dice count and sides must be positive integers.")
66
+
67
+ raw_rolls = [random.randint(1, sides) for _ in range(count)]
68
+ kept_rolls = list(raw_rolls)
69
+ kd_text = ""
70
+
71
+ if keep_drop:
72
+ if keep_drop.startswith("kh"):
73
+ k = int(keep_drop.replace("kh", ""))
74
+ kept_rolls = sorted(raw_rolls, reverse=True)[:k]
75
+ kd_text = f" (keep highest {k})"
76
+ elif keep_drop.startswith("dl"):
77
+ d = int(keep_drop.replace("dl", ""))
78
+ kept_rolls = sorted(raw_rolls)[d:]
79
+ kd_text = f" (drop lowest {d})"
80
+
81
+ total = sum(kept_rolls) + static_mod
82
+ mod_text = f"+{static_mod}" if static_mod > 0 else (f"{static_mod}" if static_mod < 0 else "")
83
+
84
+ return {
85
+ "notation": expression,
86
+ "rolls": raw_rolls,
87
+ "modifier": static_mod,
88
+ "total": total,
89
+ "critical_hit": count == 1 and sides == 20 and raw_rolls[0] == 20,
90
+ "critical_fumble": count == 1 and sides == 20 and raw_rolls[0] == 1,
91
+ "breakdown": f"{raw_rolls}{kd_text}{mod_text} = {total}",
92
+ }
@@ -0,0 +1,33 @@
1
+ import random
2
+ from typing import Dict, Any
3
+
4
+ EN_MAGIC_ANSWERS = [
5
+ {"text": "It is certain.", "category": "positive"},
6
+ {"text": "Without a doubt.", "category": "positive"},
7
+ {"text": "Yes definitely.", "category": "positive"},
8
+ {"text": "Reply hazy, try again.", "category": "neutral"},
9
+ {"text": "Ask again later.", "category": "neutral"},
10
+ {"text": "Don't count on it.", "category": "negative"},
11
+ {"text": "My reply is no.", "category": "negative"},
12
+ ]
13
+
14
+ DE_MAGIC_ANSWERS = [
15
+ {"text": "Es ist ganz sicher so.", "category": "positive"},
16
+ {"text": "Zweifellos ja.", "category": "positive"},
17
+ {"text": "Ja, definitiv.", "category": "positive"},
18
+ {"text": "Antwort verschwommen, versuche es nochmal.", "category": "neutral"},
19
+ {"text": "Frage später nochmal.", "category": "neutral"},
20
+ {"text": "Verlass dich nicht darauf.", "category": "negative"},
21
+ {"text": "Meine Antwort ist Nein.", "category": "negative"},
22
+ ]
23
+
24
+ class Magic8BallEngine:
25
+ @classmethod
26
+ def ask(cls, language: str = "en") -> Dict[str, Any]:
27
+ pool = DE_MAGIC_ANSWERS if language == "de" else EN_MAGIC_ANSWERS
28
+ selected = random.choice(pool)
29
+ return {
30
+ "answer": selected["text"],
31
+ "category": selected["category"],
32
+ "language": language
33
+ }
@@ -0,0 +1,25 @@
1
+ import random
2
+ from typing import List, Dict, Any, TypeVar
3
+
4
+ T = TypeVar("T")
5
+
6
+ class TeamBalancer:
7
+ """Fair team and group partition algorithms in Python."""
8
+
9
+ @classmethod
10
+ def divide_teams(cls, members: List[T], team_count: int) -> List[Dict[str, Any]]:
11
+ if not members:
12
+ raise ValueError("Members list cannot be empty.")
13
+ if team_count <= 0:
14
+ raise ValueError("Team count must be greater than zero.")
15
+
16
+ shuffled = list(members)
17
+ random.shuffle(shuffled)
18
+
19
+ effective_count = min(team_count, len(members))
20
+ teams = [{"id": i + 1, "name": f"Team {i + 1}", "members": []} for i in range(effective_count)]
21
+
22
+ for idx, member in enumerate(shuffled):
23
+ teams[idx % effective_count]["members"].append(member)
24
+
25
+ return teams
@@ -0,0 +1,38 @@
1
+ import random
2
+ from typing import List, Dict, Any
3
+ from .alias_method import AliasMethod
4
+
5
+ class WheelPhysics:
6
+ """Calculates spinner wheel stopping angle, winner, and sector alignment."""
7
+
8
+ def __init__(self, min_rotations: int = 5, max_rotations: int = 8):
9
+ self.min_rotations = min_rotations
10
+ self.max_rotations = max_rotations
11
+
12
+ def calculate_spin(self, items: List[Dict[str, Any]]) -> Dict[str, Any]:
13
+ if not items:
14
+ raise ValueError("Cannot calculate spin for an empty list.")
15
+
16
+ alias = AliasMethod(items)
17
+ winner = alias.next()
18
+ winning_index = items.index(winner)
19
+
20
+ segment_angle = 360.0 / len(items)
21
+ sector_center = winning_index * segment_angle + segment_angle / 2.0
22
+
23
+ padding = segment_angle * 0.1
24
+ variance = (random.random() - 0.5) * (segment_angle - 2 * padding)
25
+ stop_angle_on_wheel = (sector_center + variance + 360.0) % 360.0
26
+
27
+ pointer_offset = 270.0
28
+ final_angle_offset = (pointer_offset - stop_angle_on_wheel + 360.0) % 360.0
29
+
30
+ full_rotations = random.randint(self.min_rotations, self.max_rotations)
31
+ total_target_rotation = full_rotations * 360.0 + final_angle_offset
32
+
33
+ return {
34
+ "winner": winner,
35
+ "winning_index": winning_index,
36
+ "target_angle": total_target_rotation,
37
+ "rotation_count": full_rotations
38
+ }
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: entscheidomat-decision-kit
3
+ Version: 1.0.0
4
+ Summary: The ultimate Python library for decision-making algorithms, spinner wheels, weighted sampling, dice notation, and team generators.
5
+ Author-email: Entscheidomat <info@entscheidomat.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://entscheidomat.com
8
+ Project-URL: Documentation, https://entscheidomat.com
9
+ Project-URL: Repository, https://github.com/knuthtimo-lab/decision-kit-py
10
+ Project-URL: Glücksrad / Wheel, https://entscheidomat.com/gluecksrad
11
+ Project-URL: Münzwurf / Coin Flip, https://entscheidomat.com/muenzwurf
12
+ Project-URL: Würfel / Dice Roller, https://entscheidomat.com/wuerfel
13
+ Project-URL: Ja/Nein Generator, https://entscheidomat.com/ja-nein-generator
14
+ Keywords: decision-maker,random-picker,wheel-of-fortune,spinner-wheel,weighted-random,dice-roller,team-generator,entscheidomat
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+
22
+ <div align="center">
23
+
24
+ # 🎲 decision-kit (Python)
25
+
26
+ **The ultimate zero-dependency Python library for decision-making algorithms, spinner wheels, weighted sampling, dice notation, and team generators.**
27
+
28
+ [![PyPI version](https://img.shields.io/pypi/v/decision-kit.svg?style=for-the-badge&color=blue)](https://pypi.org/project/decision-kit/)
29
+ [![Python versions](https://img.shields.io/pypi/pyversions/decision-kit.svg?style=for-the-badge)](https://pypi.org/project/decision-kit/)
30
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](https://opensource.org/licenses/MIT)
31
+ [![Powered by](https://img.shields.io/badge/Powered%20by-Entscheidomat.com-ec4899?style=for-the-badge)](https://entscheidomat.com)
32
+
33
+ [Interactive Web Tools](https://entscheidomat.com) • [Documentation](#usage) • [CLI Usage](#cli-tool)
34
+
35
+ </div>
36
+
37
+ ---
38
+
39
+ ## 🚀 Overview
40
+
41
+ `decision-kit` provides fast, battle-tested algorithms and data structures to build decision-making applications, tabletop RPG tools, and random selection systems in Python.
42
+
43
+ Maintained and sponsored by **[Entscheidomat.com](https://entscheidomat.com)** — the free online decision suite.
44
+
45
+ ---
46
+
47
+ ## 🌐 Interactive Web Demos
48
+
49
+ Try these online decision tools powered by **[Entscheidomat.com](https://entscheidomat.com)**:
50
+
51
+ - 🎡 **[Glücksrad / Wheel of Fortune](https://entscheidomat.com/gluecksrad)**
52
+ - ❓ **[Ja / Nein Generator](https://entscheidomat.com/ja-nein-generator)**
53
+ - 🎲 **[3D Würfel Simulator](https://entscheidomat.com/wuerfel)**
54
+ - 🪙 **[Münzwurf Simulation](https://entscheidomat.com/muenzwurf)**
55
+ - 🎱 **[Magic 8-Ball Orakel](https://entscheidomat.com/magic-8-ball)**
56
+ - 🏷️ **[Namen Auslosung & Team Generator](https://entscheidomat.com/namen-auslosung)**
57
+ - 🔢 **[Zufallszahl Generator](https://entscheidomat.com/zufallszahl-generator)**
58
+
59
+ ---
60
+
61
+ ## 📦 Installation
62
+
63
+ ```bash
64
+ pip install decision-kit
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 💡 Usage
70
+
71
+ ```python
72
+ from decision_kit import AliasMethod, DiceEngine, TeamBalancer, CoinFlipEngine
73
+
74
+ # 1. Weighted Random Selection (O(1) Alias Algorithm)
75
+ items = [
76
+ {"id": "A", "label": "Common Item", "weight": 70},
77
+ {"id": "B", "label": "Rare Item", "weight": 25},
78
+ {"id": "C", "label": "Legendary Item", "weight": 5},
79
+ ]
80
+ alias = AliasMethod(items)
81
+ winner = alias.next()
82
+ print("Won:", winner["label"])
83
+
84
+ # 2. Tabletop RPG Dice Parser
85
+ roll = DiceEngine.roll("3d6+2")
86
+ print(roll["total"], roll["breakdown"])
87
+
88
+ # 3. Fair Team Generator
89
+ teams = TeamBalancer.divide_teams(["Anna", "Ben", "Clara", "Dan"], team_count=2)
90
+ print(teams)
91
+
92
+ # 4. Coin Flip
93
+ coin = CoinFlipEngine.flip("de")
94
+ print(coin["side"])
95
+ ```
96
+
97
+ ---
98
+
99
+ ## 🖥️ CLI Tool
100
+
101
+ ```bash
102
+ # Pick random winner
103
+ decision-kit pick "Pizza" "Sushi" "Burger"
104
+
105
+ # Roll dice
106
+ decision-kit roll 2d6+3
107
+
108
+ # Flip coin
109
+ decision-kit flip
110
+ ```
111
+
112
+ ---
113
+
114
+ ## 📄 License
115
+
116
+ MIT © **[Entscheidomat](https://entscheidomat.com)**
@@ -0,0 +1,16 @@
1
+ README.md
2
+ pyproject.toml
3
+ decision_kit/__init__.py
4
+ decision_kit/alias_method.py
5
+ decision_kit/cli.py
6
+ decision_kit/coin_flip.py
7
+ decision_kit/dice_engine.py
8
+ decision_kit/magic_8_ball.py
9
+ decision_kit/team_balancer.py
10
+ decision_kit/wheel_physics.py
11
+ entscheidomat_decision_kit.egg-info/PKG-INFO
12
+ entscheidomat_decision_kit.egg-info/SOURCES.txt
13
+ entscheidomat_decision_kit.egg-info/dependency_links.txt
14
+ entscheidomat_decision_kit.egg-info/entry_points.txt
15
+ entscheidomat_decision_kit.egg-info/top_level.txt
16
+ tests/test_decision_kit.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ decision-kit = decision_kit.cli:main
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "entscheidomat-decision-kit"
7
+ version = "1.0.0"
8
+ description = "The ultimate Python library for decision-making algorithms, spinner wheels, weighted sampling, dice notation, and team generators."
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Entscheidomat", email = "info@entscheidomat.com" }
12
+ ]
13
+ license = { text = "MIT" }
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ "Topic :: Software Development :: Libraries :: Python Modules",
19
+ ]
20
+ keywords = ["decision-maker", "random-picker", "wheel-of-fortune", "spinner-wheel", "weighted-random", "dice-roller", "team-generator", "entscheidomat"]
21
+ requires-python = ">=3.8"
22
+ dependencies = []
23
+
24
+ [project.urls]
25
+ "Homepage" = "https://entscheidomat.com"
26
+ "Documentation" = "https://entscheidomat.com"
27
+ "Repository" = "https://github.com/knuthtimo-lab/decision-kit-py"
28
+ "Glücksrad / Wheel" = "https://entscheidomat.com/gluecksrad"
29
+ "Münzwurf / Coin Flip" = "https://entscheidomat.com/muenzwurf"
30
+ "Würfel / Dice Roller" = "https://entscheidomat.com/wuerfel"
31
+ "Ja/Nein Generator" = "https://entscheidomat.com/ja-nein-generator"
32
+
33
+ [project.scripts]
34
+ decision-kit = "decision_kit.cli:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,47 @@
1
+ import unittest
2
+ from decision_kit import (
3
+ AliasMethod,
4
+ DiceEngine,
5
+ TeamBalancer,
6
+ CoinFlipEngine,
7
+ Magic8BallEngine,
8
+ WheelPhysics,
9
+ )
10
+
11
+ class TestDecisionKit(unittest.TestCase):
12
+ def test_alias_method(self):
13
+ items = [{"id": "A", "weight": 70}, {"id": "B", "weight": 30}]
14
+ alias = AliasMethod(items)
15
+ counts = {"A": 0, "B": 0}
16
+ for _ in range(5000):
17
+ res = alias.next()
18
+ counts[res["id"]] += 1
19
+ self.assertGreater(counts["A"], 2500)
20
+
21
+ def test_dice_engine(self):
22
+ res = DiceEngine.roll("2d6+3")
23
+ self.assertEqual(len(res["rolls"]), 2)
24
+ self.assertGreaterEqual(res["total"], 5)
25
+ self.assertLessEqual(res["total"], 15)
26
+
27
+ def test_team_balancer(self):
28
+ members = ["Alice", "Bob", "Charlie", "Dan"]
29
+ teams = TeamBalancer.divide_teams(members, 2)
30
+ self.assertEqual(len(teams), 2)
31
+
32
+ def test_coin_flip(self):
33
+ res = CoinFlipEngine.flip("de")
34
+ self.assertIn(res["side"], ["Kopf", "Zahl"])
35
+
36
+ def test_magic_8_ball(self):
37
+ res = Magic8BallEngine.ask("de")
38
+ self.assertTrue(len(res["answer"]) > 0)
39
+
40
+ def test_wheel_physics(self):
41
+ physics = WheelPhysics()
42
+ items = [{"id": "1", "weight": 1}, {"id": "2", "weight": 1}]
43
+ spin = physics.calculate_spin(items)
44
+ self.assertGreater(spin["target_angle"], 1000)
45
+
46
+ if __name__ == "__main__":
47
+ unittest.main()