3
\$\begingroup\$

This is a follow-up to my ex-posted Blackjack game that uses a MySQL database to store the accounts and user's money. I tried splitting display_info() a bit, I also added a check for validating the Email address and the passwords are now hashed.

from random import shuffle
import os
import cymysql
from getpass import getpass
import sys
import re
from bcrypt import hashpw, gensalt


def shuffled_shoe():
    shoe = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'A', 'J', 'Q', 'K']*4
    shuffle(shoe)
    return shoe


def deal_card(shoe, person, number):
    for _ in range(number):
        person.append(shoe.pop())


def deal_hand(shoe, player, dealer):
    for _ in range(2):
        deal_card(shoe, player, 1)
        deal_card(shoe, dealer, 1)


def score(person):
    non_aces = [c for c in person if c != 'A']
    aces = [c for c in person if c == 'A']
    total = 0
    for card in non_aces:
        if card in 'JQK':
            total += 10
        else:
            total += int(card)
    for card in aces:
        if total <= 10:
            total += 11
        else:
            total += 1
    return total


def set_money(money, money_bet, win, push):
    if win:
        money += money_bet * 2
    elif push:
        money += money_bet
    return money


def clear_console():
    os.system('cls' if os.name == 'nt' else 'clear')


def display_info(still_playing, player, dealer, money, money_bet, player_stands):
    win = False
    push = False
    clear_console()
    print(f"Money: ${money}")
    print(f"Money bet: ${money_bet}")
    print("Your cards:   [{}] ({})".format(']['.join(player), score(player)))
    if player_stands:
        print("Dealer cards: [{}] ({})".format(']['.join(dealer), score(dealer)))
    else:
        print("Dealer cards: [{}][?]".format(dealer[0]))
    first_hand = len(dealer) == 2
    if score(player) == 21:
        print("Blackjack! You won")
        still_playing = False
        win = True
    elif first_hand and score(dealer) == 21:
        print("Dealer got a blackjack. You lost!")
        still_playing = False
    elif score(player) > 21:
        print("Busted! You lost!")
        still_playing = False
    if player_stands:
        if score(dealer) > 21:
            print("Dealer busted! You won")
            win = True
        elif score(player) > score(dealer):
            print("You beat the dealer! You won!")
            win = True
        elif score(player) < score(dealer):
            print("Dealer has beaten you. You lost!")
        else:
            print("Push. Nobody wins or losses.")
            push = True
        still_playing = False
    money = set_money(money, money_bet, win, push)
    return still_playing, money


def hit_or_stand():
    while True:
        print("What do you choose?")
        print("[1] - Hit")
        print("[2] - Stand")
        ans = input('> ')
        if ans in '12':
            return ans


def bet(money):
    clear_console()
    print(f"Money: ${money}")
    print("How much money do you want to bet?")
    while True:
        money_bet = int(input('> '))
        if money_bet <= money and not money_bet <= 0:
            money -= money_bet
            return money, money_bet
        else:
            print("Please enter a valid bet.")


def player_play(shoe, player, dealer, money, money_bet, player_plays, player_stands):
    while not player_stands:
        if hit_or_stand() == '2':
            player_stands = True
            player_plays = False
        elif not player_stands:
            deal_card(shoe, player, 1)
            display_info(True, player, dealer, money, money_bet, player_stands)
            if score(player) >= 21:
                player_plays = False
                break
    return player_plays, player_stands


def dealer_play(shoe, dealer, dealer_minimum_score):
    while score(dealer) <= dealer_minimum_score:
        deal_card(shoe, dealer, 1)
    return False


def check_money(money):
    if money == 0:
        print("\nUnfortunately you don't have any money.")
        sys.exit()


def update_db_money(cur, money, email):
    cur.execute("UPDATE `users` SET `money`=%s WHERE `email`=%s", (money, email))
    cur.close()


def play_again(cur, money, email):
    check_money(money)
    while True:
        print("\nDo you want to play again? [Y]es/[N]o")
        ans = input("> ").lower()
        if ans == 'y':
            return True
        elif ans == 'n':
            update_db_money(cur, money, email)
            return False


def get_user_info():
    while True:
        email = input("Email address (max. 255 chars.): ")
        password = getpass("Password (max. 255 chars.): ").encode('utf-8')
        hashed_pw = hashpw(password, gensalt())
        if len(email) < 255 and len(password) < 255:
            if re.match(r'[^@]+@[^@]+\.[^@]+', email):
                return email, password, hashed_pw
            else:
                print("Please enter a valid email address.")


def register(cur, email, hashed_pw):
    cur.execute("INSERT INTO `users` (`Email`, `Password`) VALUES (%s, %s)", (email, hashed_pw))


def login(cur, email, password, hashed_pw):
    cur.execute("SELECT * FROM `users` WHERE `Email`=%s LIMIT 1", (email,))
    correct_credentials = cur.fetchone()
    correct_hash = correct_credentials[2].encode('utf-8')
    if hashpw(password, correct_hash) == correct_hash:
        print("You've succesfully logged-in!")
    else:
        print("You failed logging-in!")
        sys.exit()


def check_account(cur, email):
    cur.execute("SELECT * FROM `users` WHERE `Email`=%s LIMIT 1", (email,))
    return bool(cur.fetchone())


def start():
    print("\nDo you want to start playing? [Y]es/[N]o")
    ans = input('> ').lower()
    if ans == 'y':
        return True
    elif ans == 'n':
        return False


def db_conn():
    conn = cymysql.connect(
        host='127.0.0.1',
        user='root',
        passwd='',
        db='blackjack'
    )
    with conn:
        cur = conn.cursor()
        email, password, hashed_pw = get_user_info()
        checked = check_account(cur, email)
        if checked:
            login(cur, email, password, hashed_pw)
        else:
            register(cur, email, hashed_pw)
            print("You've succesfully registered and recieved $1000 as a gift!")
        cur.execute("SELECT `money` FROM `users` WHERE `email`=%s", (email,))
        money_tuple = cur.fetchone()
        money = money_tuple[0]
        check_money(money)
        return cur, money, email


def main():
    cur, money, email = db_conn()
    keeps_playing = start()
    while keeps_playing:
        shoe = shuffled_shoe()
        player = []
        dealer = []
        still_playing = True
        player_plays = True
        player_stands = False
        money, money_bet = bet(money)
        deal_hand(shoe, player, dealer)
        still_playing, money = display_info(still_playing, player, dealer, money, money_bet, player_stands)
        while still_playing:
            while player_plays:
                player_plays, player_stands = player_play(shoe, player, dealer, money, money_bet, player_plays, player_stands)
            still_playing = dealer_play(shoe, dealer, 17)
        still_playing, money = display_info(still_playing, player, dealer, money, money_bet, player_stands)
        keeps_playing = play_again(cur, money, email)


if __name__ == '__main__':
    main()

Blackjack.sql:

SET NAMES utf8;
SET time_zone = '+00:00';
SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';

DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `email` varchar(255) COLLATE utf8_bin NOT NULL,
  `password` varchar(255) COLLATE utf8_bin NOT NULL,
  `money` int(11) NOT NULL DEFAULT '1000',
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
\$\endgroup\$
3
\$\begingroup\$

Don't materialize generators unless you have to

These:

non_aces = [c for c in person if c != 'A']
aces = [c for c in person if c == 'A']

take up memory, however inconsequential. Since you only iterate through them once, change the [] brackets to () parens to leave it as a generator.

Choose a quote standard

You have both single and double:

    print("[2] - Stand")
    ans = input('> ')

Choose one and stick with it.

Redundant else

        return money, money_bet
    else:

doesn't require an else, because you've already returned.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.