pyjack-blackjack 2__py3-none-any.whl

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.
pyjack.py ADDED
@@ -0,0 +1,350 @@
1
+ # ░░░░░░░░░░░░░░░░░░░░░░░░░
2
+ # ░█▀█░█░█░▀▀█░█▀█░█▀▀░█░█░
3
+ # ░█▀▀░░█░░░░█░█▀█░█░░░█▀▄░
4
+ # ░▀░░░░▀░░▀▀░░▀░▀░▀▀▀░▀░▀░
5
+ # ░░░░░░░░░░░░░░░░░░░░░░░░░
6
+ # PyJack - Blackjack but in a single Python file!
7
+ # Created by @consciousbone for Hack Club's Stardance
8
+ # ASCII art from https://www.asciiart.eu/text-to-ascii-art
9
+
10
+ # MARK: Imports
11
+ import os
12
+ import random
13
+ import time
14
+ import json
15
+
16
+ # MARK: General constants
17
+ PYJACK_VERSION = 2
18
+ DATA_DIR = os.path.join(os.path.expanduser("~"), ".pyjack_data")
19
+
20
+ PYJACK_STATS_FILENAME = os.path.join(DATA_DIR, "stats.json")
21
+ PYJACK_CONFIG_FILENAME = os.path.join(DATA_DIR, "config.json")
22
+
23
+ # MARK: General variables
24
+ menu_error_header = ""
25
+
26
+ # MARK: General subroutines
27
+ def clear():
28
+ os.system('cls' if os.name == 'nt' else 'clear') # clear terminal
29
+
30
+ def print_logo(page_name):
31
+ config = read_config()
32
+ use_ascii_logo = config.get('show_ascii_art', 1)
33
+
34
+ if use_ascii_logo == 1: # show ascii art
35
+ print(f'''
36
+ ░░░░░░░░░░░░░░░░░░░░░░░░░
37
+ ░█▀█░█░█░▀▀█░█▀█░█▀▀░█░█░
38
+ ░█▀▀░░█░░░░█░█▀█░█░░░█▀▄░
39
+ ░▀░░░░▀░░▀▀░░▀░▀░▀▀▀░▀░▀░
40
+ ░░░░░░░░░░░░░░░░░░░░░░░░░
41
+ PyJack (v{PYJACK_VERSION})
42
+ {page_name}
43
+
44
+ ─────────────────────────
45
+ ''')
46
+ else: # don't show ascii art
47
+ print(f"\nPyJack (v{PYJACK_VERSION})\n{page_name}\n\n─────────────────────────\n")
48
+
49
+ # MARK: Data subroutines/functions
50
+ # Partially stolen from another thing I wrote, https://github.com/R002441/HackTrack
51
+ def initialise_files():
52
+ os.makedirs(DATA_DIR, exist_ok=True)
53
+
54
+ if not os.path.exists(PYJACK_STATS_FILENAME): # write default stats file if it doesn't exist
55
+ default_stats = {
56
+ "games_won": 0,
57
+ "games_lost": 0,
58
+ "games_drew": 0
59
+ }
60
+
61
+ with open(PYJACK_STATS_FILENAME, "w", encoding="utf-8") as file:
62
+ json.dump(default_stats, file, indent=4)
63
+
64
+ if not os.path.exists(PYJACK_CONFIG_FILENAME): # write default config file if it doesn't exist
65
+ default_config = {
66
+ "show_ascii_art": 1,
67
+ "dealer_hits_delay": 1
68
+ }
69
+
70
+ with open(PYJACK_CONFIG_FILENAME, "w", encoding="utf-8") as file:
71
+ json.dump(default_config, file, indent=4)
72
+
73
+ def read_stats():
74
+ if not os.path.exists(PYJACK_STATS_FILENAME):
75
+ return {}
76
+ with open(PYJACK_STATS_FILENAME, "r") as file:
77
+ return json.load(file) # read json file
78
+
79
+ def write_stats(stats):
80
+ with open(PYJACK_STATS_FILENAME, "w") as file:
81
+ json.dump(stats, file, indent=2) # write back into json
82
+
83
+ def read_config():
84
+ if not os.path.exists(PYJACK_CONFIG_FILENAME):
85
+ return {}
86
+ with open(PYJACK_CONFIG_FILENAME, "r") as file:
87
+ return json.load(file) # read json file
88
+
89
+ def write_config(config):
90
+ with open(PYJACK_CONFIG_FILENAME, "w") as file:
91
+ json.dump(config, file, indent=2) # write back into json
92
+
93
+ # MARK: Menu subroutines
94
+ def menu_header(page_name):
95
+ global menu_error_header # let this subroutine use that var
96
+
97
+ clear() # very clean very fancy very wow
98
+ print_logo(page_name)
99
+
100
+ if menu_error_header != "": # theres an error which needs showing, do it!
101
+ print(f"{menu_error_header}\n")
102
+ menu_error_header = "" # clear it to ensure it doesn't keep showing
103
+
104
+
105
+ def main_menu():
106
+ while True:
107
+ global menu_error_header # let this subroutine use that var
108
+ menu_header("Main menu")
109
+ print('''\
110
+ 1) Play
111
+ 2) Stats
112
+ 3) Config
113
+ 4) Quit
114
+ ''')
115
+
116
+ selected_menu_option = input("Select an option.\n> ") # get input
117
+ if selected_menu_option == "1": # play
118
+ while True: # loop
119
+ play_blackjack()
120
+
121
+ while True: # prevents the else case from starting another game
122
+ play_again = input("Play again (Y/n)?\n> ")
123
+ if play_again.lower() == "y": # play again
124
+ clear() # clear screen so terminal isnt cluttered with previous game
125
+ break # play again
126
+ elif play_again.lower() == "n": # don't play again, back to menu
127
+ clear()
128
+ break
129
+ elif play_again.strip() == "": # user inputted nothing, default to yes
130
+ clear()
131
+ break
132
+ else: # user inputted something else entirely
133
+ print("Invalid option.")
134
+
135
+ if play_again.lower() == "n": # don't play again, back to menu
136
+ break # exit the play again loop
137
+ elif selected_menu_option == "2": # stats
138
+ stats_menu()
139
+ elif selected_menu_option == "3": # config
140
+ config_menu()
141
+ elif selected_menu_option == "4": # quit
142
+ print("Thanks for playing!")
143
+ break
144
+ else:
145
+ menu_error_header = "Invalid option."
146
+
147
+ def stats_menu():
148
+ while True:
149
+ global menu_error_header
150
+ stats = read_stats()
151
+
152
+ menu_header("Stats")
153
+
154
+ print(f"Games won: {stats['games_won']}")
155
+ print(f"Games lost: {stats['games_lost']}")
156
+ print(f"Games drew: {stats['games_drew']}")
157
+
158
+ print() # blank line for seperation
159
+ print('''\
160
+ 1) Back
161
+ ''')
162
+
163
+ selected_menu_option = input("Select an option.\n> ")
164
+ if selected_menu_option == "1":
165
+ return
166
+ else:
167
+ menu_error_header = "Invalid option."
168
+
169
+ def config_menu():
170
+ while True:
171
+ global menu_error_header
172
+ config = read_config()
173
+
174
+ menu_header("Config")
175
+
176
+ print("Current config:") # there's probably a much better way to do this, but this is easy :)
177
+ if config.get('show_ascii_art', 1) == 1:
178
+ print("Show ASCII art: Yes")
179
+ elif config.get('show_ascii_art', 1) == 0:
180
+ print("Show ASCII art: No")
181
+ if config.get('dealer_hits_delay', 1) == 1:
182
+ print("Dealer has delay when hitting: Yes")
183
+ elif config.get('dealer_hits_delay', 1) == 0:
184
+ print("Dealer has delay when hitting: No")
185
+
186
+ print() # blank line for seperation
187
+ print('''\
188
+ 1) Toggle "Show ASCII art"
189
+ 2) Toggle "Dealer has delay when hitting"
190
+ 3) Back
191
+ ''')
192
+
193
+ selected_menu_option = input("Select an option.\n> ")
194
+ if selected_menu_option == "1":
195
+ if config.get('show_ascii_art', 1) == 1:
196
+ config['show_ascii_art'] = 0
197
+ write_config(config)
198
+ menu_error_header = "Toggled \"Show ASCII art\"."
199
+
200
+ elif config.get('show_ascii_art', 1) == 0:
201
+ config['show_ascii_art'] = 1
202
+ write_config(config)
203
+ menu_error_header = "Toggled \"Show ASCII art\"."
204
+
205
+ elif selected_menu_option == "2":
206
+ if config.get('dealer_hits_delay', 1) == 1:
207
+ config['dealer_hits_delay'] = 0
208
+ write_config(config)
209
+ menu_error_header = "Toggled \"Dealer has delay when hitting\"."
210
+
211
+ elif config.get('dealer_hits_delay', 1) == 0:
212
+ config['dealer_hits_delay'] = 1
213
+ write_config(config)
214
+ menu_error_header = "Toggled \"Dealer has delay when hitting\"."
215
+
216
+ elif selected_menu_option == "3":
217
+ return
218
+
219
+ else:
220
+ menu_error_header = "Invalid option."
221
+
222
+
223
+ # MARK: Blackjack constants
224
+ SUITS = ["♠", "♥", "♦", "♣"] # ooh fancy unicode thanks to https://en.wikipedia.org/wiki/Playing_cards_in_Unicode
225
+ RANKS = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
226
+
227
+ # MARK: Blackjack variables
228
+ deck = []
229
+
230
+ # MARK: Blackjack subroutines/functions
231
+ def build_deck():
232
+ deck.clear() # make sure deck doesn't end up massive
233
+ for suit in SUITS:
234
+ for rank in RANKS:
235
+ deck.append((rank, suit))
236
+ random.shuffle(deck)
237
+
238
+ def hand_value(hand):
239
+ value = 0
240
+ aces = 0
241
+
242
+ for rank, suit in hand:
243
+ if rank in ["J", "Q", "K"]: # handle the cards that aren't numbers, aces come later
244
+ value += 10
245
+ elif rank == "A": # now handle aces part 1; default to 11, but will move to 1 later if it would cause a bust
246
+ value += 11
247
+ aces += 1 # keep track of number of aces for later
248
+ else:
249
+ value += int(rank) # ez
250
+
251
+ while value > 21 and aces > 0: # handle aces part 2; turn aces one by one from 11 to 1
252
+ value -= 10
253
+ aces -= 1
254
+
255
+ return value # finished counting!
256
+
257
+ def show_hand(name, hand, hide_first_card=False):
258
+ if hide_first_card: # hide the first card, used with dealer
259
+ cards = ["??"] + [f"{rank}{suit}" for rank, suit in hand[1:]] # skip first card since it's hidden
260
+ print(f"{name}: {' '.join(cards)}") # make sure cards are formatted nicely
261
+ else:
262
+ cards = [f"{rank}{suit}" for rank, suit in hand]
263
+ print(f"{name}: {' '.join(cards)} ({hand_value(hand)})") # also show hand value for player
264
+
265
+
266
+ def play_blackjack(): # the main event 0.0
267
+ stats = read_stats()
268
+ config = read_config()
269
+
270
+ build_deck() # get a deck
271
+ menu_header("Play")
272
+
273
+ player = [deck.pop(), deck.pop()] # give player 2 cards
274
+ dealer = [deck.pop(), deck.pop()] # give dealer 2 cards
275
+
276
+ while True: # player's turn!
277
+ show_hand("Dealer", dealer, hide_first_card=True) # show dealer's hand, hiding first card
278
+ show_hand("Player", player) # show player's hand, ofc not hiding your own cards :/
279
+
280
+ player_value = hand_value(player)
281
+ if player_value == 21: # player is at blackjack, end
282
+ if len(player) == 2:
283
+ print("Blackjack!\n")
284
+ else:
285
+ print("21!\n")
286
+ break
287
+ if player_value > 21: # player is bust, end
288
+ stats['games_lost'] = stats['games_lost'] + 1
289
+ write_stats(stats)
290
+ print("Bust, you lose!\n")
291
+ return
292
+
293
+ print("") # blank line for visual seperation
294
+ choice = input("(H)it or (S)tand?\n> ")
295
+ if choice.lower() == "h": # hit; resiliant to PEBCAK errors (google it :D)
296
+ player.append(deck.pop()) # give player another card
297
+ elif choice.lower() == "s": # stand; also resiliant to PEBCAK errors
298
+ break # exit while loop, dealer's turn!
299
+ else:
300
+ print("Invalid option.")
301
+
302
+ print("") # another blank line for seperation
303
+
304
+ # dealer's turn!
305
+ print("\nDealer's turn!\n")
306
+
307
+ show_hand("Dealer", dealer) # show dealer's hand, *not* hiding first card
308
+ show_hand("Player", player) # show player's hand, ofc not hiding your own cards :/
309
+
310
+ while hand_value(dealer) < 17: # dealer must stand on 17
311
+ if config.get('dealer_hits_delay', 1) == 1: # does config allow for a delay
312
+ time.sleep(random.uniform(0.5, 1)) # wait for random amount of time between 0.5s and 1s
313
+
314
+ print("") # blank line for seperation
315
+ print("Dealer hits!")
316
+ dealer.append(deck.pop()) # give dealer another card
317
+
318
+ print("")
319
+ show_hand("Dealer", dealer) # show dealer's hand, *not* hiding first card
320
+ show_hand("Player", player) # show player's hand, ofc not hiding your own cards :/
321
+
322
+ dealer_value = hand_value(dealer)
323
+ player_value = hand_value(player)
324
+
325
+ if dealer_value > 21: # dealer is bust :D
326
+ stats['games_won'] = stats['games_won'] + 1
327
+ write_stats(stats)
328
+ print("\nDealer bust!")
329
+ elif dealer_value > player_value: # dealer wins
330
+ stats['games_lost'] = stats['games_lost'] + 1
331
+ write_stats(stats)
332
+ print("\nDealer wins!")
333
+ elif dealer_value < player_value: # player wins
334
+ stats['games_won'] = stats['games_won'] + 1
335
+ write_stats(stats)
336
+ print("\nYou win!")
337
+ else: # draw
338
+ stats['games_drew'] = stats['games_drew'] + 1
339
+ write_stats(stats)
340
+ print("\nIt\'s a draw!")
341
+
342
+ print("")
343
+
344
+ # MARK: Run the damn thing
345
+ def main():
346
+ initialise_files() # create data files if they don't exist
347
+ main_menu() # run main menu
348
+
349
+ if __name__ == "__main__":
350
+ main()
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyjack-blackjack
3
+ Version: 2
4
+ Summary: A simple blackjack game, but in a single Python file... How revolutionary!
5
+ Author-email: ConsciousBone <eplant@consciousb.one>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ConsciousBone/PyJack
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Dynamic: license-file
14
+
15
+ # PyJack</h1>
16
+ ![Hackatime Badge](https://hackatime-badge.hackclub.com/U0998E0AFFD/PyJack)
17
+ It's blackjack, but in a single Python file.
18
+ Created for Hack Club's Stardance!
19
+ It runs almost anywhere you can run Python (I've not tested this claim but there's nothing really stopping it!), and requires such low system specs that I think the toaster sitting in your kitchen could comfortably run it.
20
+
21
+ # Demos
22
+ ## Screenshots
23
+ <img src="/screenshots/1.png" width=384> <img src="/screenshots/2.png" width=384>
24
+ <img src="/screenshots/3.png" width=384> <img src="/screenshots/4.png" width=384>
25
+ ## Video
26
+ [YouTube video (unlisted)](https://youtu.be/rOiauafCkaA)
27
+
28
+ # How to play
29
+ 1. Download Python from [this page](https://www.python.org/downloads/) if you don't already have it installed.
30
+ 2. Download the latest release of PyJack from [this page](https://github.com/ConsciousBone/PyJack/releases/).
31
+ 3. Run the downloaded `pyjack.py` file. All the required JSON files will be automatically created in a new `.pyjack_data` directory inside your home folder!
32
+ 4. Play the game! Only the keyboard is required for this. You navigate around the menus using the number keys, and actually control the game using H and S to hit or stand. Simple!
33
+
34
+ # Why?
35
+ Why not?
36
+
37
+ # No but seriously, why?
38
+ Blackjack is a pretty good time-waster game, and for those times where you've got nothing to do, why not play some blackjack against a computer? It was also a fun challenge to test my (admittedly kind of lacking) Python skills.
39
+
40
+ # Any future plans for this?
41
+ Yes! In the future I'd like to implement a GUI of some sorts and a money system, however due to my great time management skills, I've left most of this to the last day of Stardance, therefore leaving me with no time to actually implement such features...
42
+ If you want to have a go at implementing these yourself, why not fork this repo and see if you can add some stuff into this simple game?
43
+
44
+ # Tools I used
45
+ - Google
46
+ - ChatGPT (to fix some small bugs, help out with understanding blackjack's rules, and help out with understanding how to publish this on PyPI)
47
+ - Wikipedia (to copy-paste the fancy Unicode symbols used for the cards)
48
+ - VS Code (to write the damn thing)
49
+
50
+ # Something's broken?
51
+ Make a GitHub issue on [this page](https://github.com/ConsciousBone/PyJack/issues), or DM me on Slack and I'll investigate the issue as soon as possible!
@@ -0,0 +1,7 @@
1
+ pyjack.py,sha256=iRmtz8KxABQS2yHvU2kwM5L-RjyWteGt-JE-imhW26g,12923
2
+ pyjack_blackjack-2.dist-info/licenses/LICENSE,sha256=k61eiMSo-tWaJZJFliwTXKQpUf3MjQO8NBcq90Lk8bM,1091
3
+ pyjack_blackjack-2.dist-info/METADATA,sha256=CngAD3ant5vejB5m9veQ96Zu_2r1oUo_qNxxe6q51lc,2912
4
+ pyjack_blackjack-2.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
5
+ pyjack_blackjack-2.dist-info/entry_points.txt,sha256=43MqjKUvv0YdgjANsddFTeRF27O6hEAjhP_zyKpXYbE,39
6
+ pyjack_blackjack-2.dist-info/top_level.txt,sha256=n_A9lKh7OKkx0ZoYo6Mn3A3f4__m6VLTwreGhXbNOvY,7
7
+ pyjack_blackjack-2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pyjack = pyjack:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ConsciousBone
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ pyjack