Skip to content

Commit aac4f23

Browse files
committed
Add license and readme files
1 parent a67bf4b commit aac4f23

File tree

6 files changed

+883
-446
lines changed

6 files changed

+883
-446
lines changed

LICENSE

+595
Large diffs are not rendered by default.

README.md

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Blackjack Game
2+
3+
This repository contains the complete implementation of my multiplayer [Blackjack game](https://blackjack.sahinakkaya.dev), combining a Python-based backend with a React-based frontend. The whole thing was implemented in a week, so the codebase still contains unnecessary comments, test code etc. but it is functional as is. Below is an overview of the backend and frontend parts.
4+
5+
6+
---
7+
8+
## Backend
9+
10+
The backend models the game state using a finite state machine powered by [pytransitions](https://github.com/pytransitions/transitions). It handles communication with clients via WebSockets implemented with [python-socketio](https://github.com/miguelgrinberg/python-socketio).
11+
12+
### Features
13+
- **Infinite number of players support**: Since I modelled the game as finite state machine, it is not hard to keep track of the state of the game. So it can be played as many players as needed.
14+
- **A simple cli**: A simple cli to interact with the game from command line. I wrote it to quickly test my game while developing it. You can run it with `python blackjack/cli.py`.
15+
- **An HTTP API**: My initial thought was to create an api to interact with the game from clients but I ended up using [websockets](https://en.wikipedia.org/wiki/WebSocket) as they are more useful when there is real time data. But the api is still available. I will probably delete this part completely in the future.
16+
- **Connections through websockets**: Clients can connect to backend via websockets and get real time data to update their state.
17+
18+
---
19+
20+
## Frontend
21+
22+
The frontend part is fork of [polivodichka/blackjack](https://github.com/polivodichka/blackjack). I do not enjoy writing frontend code (and also I am not very good at it) so I decided to modify an existing code to have a functional UI as quick as possible. Since it is some one else's code hacked to work with my game, you may see hacky solutions, console.log's, ugly code blocks etc. I will improve it in the future once I implement all the features I want.
23+
24+
25+
### Features
26+
- Creating a new room for a game or joining an existing one by ID.
27+
- Entering the game with a specified name and balance.
28+
- Game interface allowing unlimited amount of players to play at the same time. (though, I only tested it up to 3 players)
29+
- The ability to add and remove chips.
30+
- Displaying game and player's status
31+
- Localization support
32+
33+
34+
---
35+
36+
37+
### Future Plans
38+
- [ ] Add a timer for player actions.
39+
- [ ] Add notifications for events like "Player joined," "Insufficient funds," and "Invalid game."
40+
- [ ] Allow players to top up their balance.
41+
- [ ] Implement chat functionality for players.
42+
- [ ] Implement sound settings.
43+
- [ ] Maintain game state when the page is refreshed.

server/blackjack/cli.py

+133-98
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,147 @@
1-
from pprint import pprint
2-
import requests
3-
1+
import os
2+
from blackjack.models.game import Game
3+
from blackjack.models.player import Player
4+
from blackjack.schemas import PlayerCreate
45
from blackjack.utils import print_cards_side_by_side
6+
from blackjack.utils import calculate_hand_value
57

8+
game = Game("1", num_of_decks=1)
69

7-
game_endpoint = "http://localhost:8000/new_api"
8-
9-
# first_player_name = input("Enter first player name: ")
10-
# second_player_name = input("Enter second player name: ")
11-
# first_player_money = int(input(f"Enter {first_player_name}'s money: "))
12-
# second_player_money = int(input(f"Enter {second_player_name}'s money: "))
13-
14-
first_player_name = "Alice"
15-
second_player_name = "Bob"
16-
first_player_money = 1000
17-
second_player_money = 1000
10+
game.add_player(PlayerCreate(id="1", name="Alice", balance=1000))
11+
game.add_player(PlayerCreate(id="2", name="Bob", balance=1000))
1812

19-
game_id = 1
2013

21-
response = requests.post(
22-
f"{game_endpoint}/game?num_of_decks=1",
23-
)
24-
requests.post(
25-
f"{game_endpoint}/game/{game_id}/player", json={"name": first_player_name, "balance": first_player_money}
26-
)
27-
requests.post(
28-
f"{game_endpoint}/game/{game_id}/player", json={"name": second_player_name, "balance": second_player_money}
29-
)
14+
# game.start_game()
3015

3116

17+
def format_player(player: Player):
18+
return f"{player.name} ({player.balance})"
3219

3320

34-
35-
def place_bet(player_name, player_id):
36-
bet = input(f"{player_name} enter your bet (press enter to skip this round): ")
37-
while bet.isdigit():
38-
response = requests.post(
39-
f"{game_endpoint}/game/{game_id}/player/{player_id}/bet?bet_amount={bet}",
21+
def print_hand(hand, show_result=True):
22+
if not hand:
23+
return
24+
print(hand.value, end=" ")
25+
if show_result:
26+
print(
27+
"(busted)"
28+
if hand.is_bust
29+
else "(won)"
30+
if hand.is_won()
31+
else "(draw)"
32+
if hand.is_draw()
33+
else "(lost)",
4034
)
41-
if response.json().get("error"):
42-
print(response.json()["error"])
43-
elif response.status_code == 200:
44-
break
45-
bet = input(f"{player_name} enter your bet (press enter to skip this round): ")
46-
return bet
47-
48-
49-
def play_round(player, hand_idx, actions):
50-
print('possible actions:', actions)
51-
possible_actions = ", ".join([f"[{action[0]}]{action[1:]}" for action in actions])
52-
single_letter_actions = [action[0] for action in actions]
53-
single_letter_actions.append("")
54-
action = input(f"{player['name']} enter your action ({possible_actions}): ")
55-
while action not in single_letter_actions:
56-
action = input(f"{player['name']} enter your action ({possible_actions}): ")
57-
action = {
58-
"h": "hit",
59-
"s": "split",
60-
"": "stand",
61-
"d": "double_down",
62-
}[action]
63-
response = requests.post(
64-
f"{game_endpoint}/game/{game_id}/player/{player['id']}/play?hand_idx={hand_idx}&action={action}",
35+
else:
36+
print()
37+
print_cards_side_by_side(
38+
[c.model_dump() for c in hand.cards],
6539
)
66-
game_status = requests.get(f"{game_endpoint}/game/{game_id}").json()
67-
return game_status
6840

6941

7042
while True:
71-
game_status = requests.get(f"{game_endpoint}/game/{game_id}").json()
72-
while any(player["state"] == "betting" for player in game_status["players"].values()):
73-
player = next(player for player in game_status["players"].values() if player["state"] == "betting")
74-
place_bet(player["name"], player["id"])
75-
game_status = requests.get(f"{game_endpoint}/game/{game_id}").json()
76-
77-
while any(player["state"] == "playing" for player in game_status["players"].values()):
78-
player = next(player for player in game_status["players"].values() if player["state"] == "playing")
79-
pprint(game_status)
80-
print(f"Dealer's hand: {game_status["dealer"]["value"]}")
81-
print_cards_side_by_side(game_status["dealer"]["hand"])
82-
while any(hand["state"] == "playing" for hand in player["hands"]):
83-
hand = player["hands"][0]
84-
other_hand = player["hands"][1] if len(player["hands"]) > 1 else None
85-
print(f"{first_player_name}'s hand: {hand['value']}")
86-
first_hand = True
87-
print_cards_side_by_side(hand["cards"], is_current=hand["state"] == "playing")
88-
if other_hand:
89-
if hand["state"] != "playing" and other_hand["state"] == "playing":
90-
first_hand = False
91-
print(f"{first_player_name}'s other hand: {other_hand['value']}")
92-
print_cards_side_by_side(other_hand["cards"], is_current=hand["state"] != "playing" and other_hand["state"] == "playing")
93-
94-
actions = []
95-
if first_hand:
96-
actions = ["hit"]
97-
if hand["can_double_down"]:
98-
actions.append("double")
99-
if hand["can_split"]:
100-
actions.append("split")
101-
elif other_hand:
102-
actions = ["hit"]
103-
if other_hand["can_double_down"]:
104-
actions.append("double")
105-
if other_hand["can_split"]:
106-
actions.append("split")
107-
game_status = play_round(player, 0 if first_hand else 1, actions)
108-
pprint(game_status)
109-
player = game_status["players"][str(player["id"])]
110-
game_status = requests.get(f"{game_endpoint}/game/{game_id}").json()
111-
input("Press enter to start next round: ")
112-
requests.post(f"{game_endpoint}/game/{game_id}/next_round")
43+
os.system("clear")
44+
while game.is_accepting_bets():
45+
current_player = game.current_player
46+
bet = input(
47+
f"{format_player(current_player)} enter your bet (press enter to skip this round): "
48+
)
49+
bet = "10"
50+
while not bet.isdigit():
51+
bet = input(
52+
f"That didn't work. {format_player(current_player)} enter your bet (press enter to skip this round): "
53+
)
54+
game.accept_bet(current_player.id, int(bet))
55+
56+
while game.is_dealing():
57+
current_player = game.current_player
58+
while current_player and current_player.is_playing():
59+
os.system("clear")
60+
# print(game.as_dict())
61+
print(f"Dealer's hand: {game.hand[0].value}")
62+
print_cards_side_by_side([game.hand[0].model_dump(), {}])
63+
current_hand = current_player.current_hand
64+
65+
for player in game.players.values():
66+
if not player.hands:
67+
continue
68+
main_hand = player.main_hand
69+
split_hand = player.split_hand
70+
print(f"{player.name}'s hand: {main_hand.value}")
71+
print_cards_side_by_side(
72+
[c.model_dump() for c in main_hand.cards],
73+
is_current=main_hand.is_playing()
74+
and player.id == current_player.id,
75+
)
76+
if split_hand:
77+
print(f"{player.name}'s other hand: {split_hand.value}")
78+
print_cards_side_by_side(
79+
[c.model_dump() for c in split_hand.cards],
80+
is_current=not main_hand.is_playing()
81+
and player.id == current_player.id
82+
and split_hand.is_playing(),
83+
)
84+
85+
actions = current_hand.actions
86+
possible_actions = ", ".join(
87+
[f"[{action[0]}]{action[1:]}" for action in actions]
88+
)
89+
single_letter_actions = [action[0] for action in actions]
90+
single_letter_actions.append("")
91+
action = input(
92+
f"{current_player.name} enter your action ({possible_actions}): "
93+
)
94+
while action not in single_letter_actions:
95+
action = input(
96+
f"{current_player.name} enter your action ({possible_actions}): "
97+
)
98+
99+
action = {
100+
"h": "hit",
101+
"s": "split",
102+
"": "stand",
103+
"d": "double_down",
104+
}[action]
105+
game.play_turn(current_player.id, 0 if current_hand.is_main else 1, action)
106+
current_player = game.current_player
107+
108+
if not game.is_end():
109+
game.end()
110+
print("ending roudn")
111+
112+
i = 2
113+
while i < len(game.hand):
114+
os.system("clear")
115+
print(
116+
f"Dealer's hand: {calculate_hand_value(game.hand[:i])}",
117+
)
118+
print_cards_side_by_side([c.model_dump() for c in game.hand[:i]])
119+
for player in game.players.values():
120+
if not player.hands:
121+
continue
122+
print(format_player(player))
123+
print_hand(player.main_hand, show_result=False)
124+
print_hand(player.split_hand, show_result=False)
125+
input()
126+
i += 1
127+
128+
os.system("clear")
129+
print(
130+
f"Dealer's hand: {game.value}",
131+
"(busted)" if game.is_bust else "",
132+
)
133+
print_cards_side_by_side([c.model_dump() for c in game.hand])
134+
for player in game.players.values():
135+
if not player.hands:
136+
continue
137+
print(format_player(player))
138+
print_hand(player.main_hand)
139+
print_hand(player.split_hand)
140+
game.payout()
141+
for player in game.players.values():
142+
print(format_player(player))
143+
input("press enter to continue")
144+
os.system("clear")
145+
146+
147+
player = game.players[player_id]

0 commit comments

Comments
 (0)