2
\$\begingroup\$

I'm learning C and decided to write my first major project in C. I was bored and wanted to play blackjack so I decided to create my own game. My program starts the user off with 500 credits and continues playing until the user runs out of credits or quits, at which point they can start again by typing play. The main input components are hit, stand, help, bet $, and quit.

I've also added a lot of code to handle special cases that I've encountered while playing the game (for example if the player was dealt a 10-value card and an Ace, it automatically gives the user a Blackjack instead of prompting the user to decide what value to get from the Ace card).

I'm certain that there are many things I could do to improve the structure and efficiency of my code.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define CARDS 52

int balance;
int pot;
int deck[52];
int sdeck[52];

void play();
void turn();
int hit(int *val);
char *cface(int v);
char *csuit(int v);
int cval(int v);
void bet();
void dealer();
void result();
void initdeck();
void shuffle();
void checkAce(int v1, int v2, int *a1);
void quit();

int main(){ 
    printf("Welcome to my BlackJack Simulator! Below are the basic rules:\n- Beat the dealer's  hand without going over 21.\n- Face cards are worth 10, Aces are worth 1 or 11, whichever makes a better hand.\n- Each player starts with two cards, and one of the dealer's cards is hidden until the end.\n- Type 'hit' to ask for another card. Type 'stand' to hold your total and end your turn.\n- If you go over 21 you bust, and the dealer wins regardless of his hand.\n- If you are dealt 21 from the start (Ace & 10), you got a blackjack. If you get a blackjack, you win 1.5 times the amount of your bet automatically, unless the dealer also gets a blackjack, in which case it is a push.\n- Remember: Type 'hit' to get another card, and 'stand' to hold. At the beginning of the round, type 'bet' followed by the quantity you want to bet (i.e. 'bet 50').\nType 'play' to begin. At any time, you may type 'help' to get a list of valid commands.\n");
    char input[6];
    scanf("%5s", input);
    while(strcmp(input, "quit") != 0){
        while(strcmp(input, "play") != 0){
            if(strcmp(input, "help") == 0)
                printf("Type 'play' to begin the game.\n");
            else if(strcmp(input, "quit")==0){
                quit();
                printf("Enter 'play' to start the game.\n");
            }else
                printf("Invalid command, type 'play' to begin.\n");
            scanf("%s", input);
        }       
        play();
        scanf("%5s", input);
    }
}

void play(){
    printf("Lets start you off with 500 credits.\n");
    balance = 500;
    while(balance>0){
        bet();
        initdeck();
        shuffle();
        turn();
    }
    printf("Sorry, it looks like you lost! Type 'play' to play another game.\n");
}

void turn(){
    int inc=0;

    int d1 = hit(&inc);
    char *cn1 = csuit(d1);
    char *cf1 = cface(d1);
    int cv1 = cval(d1);

    printf("You got a %s of %s worth %d, and ", cf1, cn1, cv1);

    int d2 = hit(&inc);
    char *cn2 = csuit(d2);
    char *cf2 = cface(d2);
    int cv2 = cval(d2);

    printf("a %s of %s worth %d.\n", cf2, cn2, cv2);

    int d3 = hit(&inc);
    char *cn3 = csuit(d3);
    char *cf3 = cface(d3);
    int cv3 = cval(d3);

    printf("The dealer's face up card is a %s of %s worth %d.\n", cf3, cn3, cv3);

    int d4 = hit(&inc);
    char *cn4 = csuit(d4);
    char *cf4 = cface(d4);
    int cv4 = cval(d4);

    int ptotal = cv1 + cv2;
    int dtotal = cv3;

    checkAce(cv1, cv2, &ptotal);

    printf("You have a total of %d points, and the dealer has %d.\n", ptotal, dtotal);  

    if(ptotal == 21){
        printf("Congrats! You got a blackjack!! Your payout is 2-to-1, %d.\n", (int)(pot*2));
        balance += (pot*2);
    }else{
        char input[6];
        scanf("%5s", input);
        while(strcmp(input,"stand")!=0){
            if(strcmp(input,"hit") == 0){
                int val = hit(&inc);
                char *ns = csuit(val);
                char *nf = cface(val);
                int nv = cval(val);
                ptotal += nv;
                printf("You got a %s of %s worth %d.\n", nf, ns, nv);
                if(ptotal < 21){
                    if(nv==1){
                        if(ptotal+10 == 21){
                            ptotal += 10;
                            printf("Congrats! You got 21! Payout is 2-to-1.\n");
                            balance += (pot*2);                         
                            break;
                        }else{
                            int input;
                            printf("You've got an ace. Choose whether you want to make it count as 1 or 11.\n");
                            scanf("%d",&input);
                            if(input == 11)
                                ptotal += 10;
                        }
                    }
                }else if(ptotal == 21){
                    printf("Congrats! You got 21!\n");
                    break;
                }else{
                    printf("Oh no, you've busted with %d. Try Again!\n", ptotal);
                    break;
                }
            }else if(strcmp(input,"help")==0)
                printf("Type 'hit' to be dealt another card. Type 'stand' to hold.\n");
            else if(strcmp(input, "quit")==0){
                quit();
                printf("Enter 'hit' or 'stand'.\n");
            }else
                printf("Invalid command, try again.\n");
            printf("Your new total is %d.\n", ptotal);
            scanf("%5s", input);
        }
    }

    if(ptotal < 21){

        printf("The dealer's flips a %s of %s worth %d.\n", cf4, cn4, cv4);
        dtotal += cv4;
        if(cv4 == 1){
            if(dtotal+11 < 21)
                dtotal += 10;
        }

        if(dtotal >= 16){
            printf("The dealer stands with %d.\n", dtotal);
        }

        while(dtotal < 16){
            int val = hit(&inc);
            char *ns = csuit(val);
            char *nf = cface(val);
            int nv = cval(val);
            dtotal += nv;
            printf("The dealer got a %s of %s worth %d.\n", nf, ns, nv);
            if(dtotal < 16){
                if(nv==1){
                    if(dtotal+11<21)
                        dtotal += 10;
                }
            }else if(dtotal == 21){
                printf("The dealer just got 21.\n");
                break;
            }else if(dtotal > 21){
                printf("The dealer busted with %d! You win!\n", dtotal);
                break;
            }else{
                printf("The dealer stands with %d.\n", dtotal);
                break;
            }
            printf("The dealer's new total is %d.\n", dtotal);
        }
        if(dtotal<ptotal){
            printf("You beat the dealer! Your payout is %d.\n", (int)(pot*1.5)); 
            balance += (pot*1.5);
        }else if(dtotal==ptotal){
            printf("Its a tie! Push pot, 1-to-1 payout of %d.\n", pot);
            balance += pot;
        }else if(dtotal>ptotal && dtotal <= 21){
            printf("Oh no! Looks like the dealer won. Try again!\n");   
        }else{
            printf("You beat the dealer! Your payout is %d.\n", (int)(pot*1.5)); 
            balance += (pot*1.5);
        }
    }
    if(balance > 0)
        printf("Your new balance is %d.\n", balance);
}

void checkAce(int v1, int v2, int *a1){
    if(v1 == 1 || v2 == 1){
        int input;
        if(v1 == 1 && v2 == 1){
            v1 = 11;
            v2 = 1;
            *a1 = 12;
            printf("Since you got 2 Aces, we set one to be worth 11 and the other to be worth 1.\n");
        }else if(v1 == 1 || v2 == 1){
            if(v1+10+v2 == 21)
                *a1 == 21;
            else{
                printf("You've got an ace. Choose whether you want to make it count as 1 or 11.\n");
                scanf("%d",&input);
                if(input == 11)
                    *a1 += 10;
            }
        }
    }
}

int cval(int v){
    int a;
    if(((v%13)+1)<10)
        a = v%13+1;
    else 
        a = 10;
    return a;
}

char *csuit(int v){
    v = v%4;
    switch(v){
        case 0 :
            return "Hearts";
        case 1 :
            return "Clubs";
        case 2 :
            return "Diamonds";
        case 3 :
            return "Spades";
    }
}

char *cface(int v){
    v = v%13+1;
    char *output;
    switch(v){
        case 1 :
            output = "Ace";
            break;
        case 2 :
            output = "Two";
            break;
        case 3 :
            output = "Three";
            break;
        case 4 :
            output = "Four";
            break;
        case 5 :
            output = "Five";
            break;
        case 6 :
            output = "Six";
            break;
        case 7 :
            output = "Seven";
            break;
        case 8 :
            output = "Eight";
            break;
        case 9 :
            output = "Nine";
            break;
        case 10 :
            output = "Ten";
            break;
        case 11 :
            output = "Jack";
            break;
        case 12 :
            output = "Queen";
            break;
        case 13 :
            output = "King";
    }
    return output;
}

void quit(){
    printf("Are you sure you want to quit? Type 'y' or 'n'.\n");
    char input[6];
    scanf("%s",input);
    if(strcmp(input,"y")==0)
        exit(0);
    else 
        printf("Quit cancelled.\n");
}

int hit(int *i){
    int a = sdeck[*i];
    *i = *i + 1;
    return a;
}

void bet(){
    char input[6];
    int val;
    printf("Enter an amount you would like to bet.\n");
    scanf("%5s", input);
    while(strcmp(input, "bet") != 0){
        if(strcmp(input,"help") == 0)
            printf("Type 'bet' followed by a bet amount. For example, you could type 'bet 50'.\n"); 
        else if(strcmp(input,"quit")==0){
            quit();
            printf("Enter a bet amount.\n");
        }else
            printf("You've entered an invalid command. Type 'help' for a list of valid commands.\n");
        scanf("%5s", input);
    }
    scanf("%d", &val);
    while(val > balance || (val < 10  && val > 0) || val <=0){
        if(val > balance)
            printf("You cannot bet more than your balance. Your balance is: %d.\n", balance);
        else if(val <= 0)
            printf("You must make a bet. Enter a valid bet amount.\n");
        else
            printf("Invalid entry. Try again.\n");
        scanf("%d", &val);
    }
    balance -= val;
    printf("You've made a bet of %d. Good luck!\n", val);
    pot = val;      
}

void initdeck(){
    int i=0;
    for(i;i<52;i++){
        deck[i] = i+1;  
    }
}

void shuffle(){
    srand(time(NULL));
    int i=0;
    for(i;i<52;i++){
        sdeck[i] = deck[i];
    }
    i=0;
    for(i=CARDS-1;i>0;i--){
        int j = rand()%(i+1);
        int n = sdeck[i];
        sdeck[i] = sdeck[j];
        sdeck[j] = n;
    }
}
\$\endgroup\$
2
\$\begingroup\$

This is a really good effort for a first major project! It's very straightforward and easy to understand. Here are a few ways you could take it to the next level.

Avoid Global Variables

Right now you have 4 global variables:

int balance;
int pot;
int deck[52];
int sdeck[52];

These should be local variables inside the main() function and you should pass them to the other functions that need to access them. There are 2 reasons:

  1. As it is now, it's difficult to find who changed them when they change
  2. If you ever want to expand this (perhaps to be a server that serves games to multiple groups of people at the same time), having globals won't work.

Avoid Magic Numbers

You created a constant CARDS but then you only use it once. You should use it to declare your decks, too (and I'd rename it to NUM_CARDS):

int deck[NUM_CARDS];
int sdeck[NUM_CARDS];

While you're unlikely to ever need to change the number of cards in a deck, using a constant can clarify the code and save you from typos. (And actually, there are a few games that use fewer than 52 cards).

Use Arrays for Looking Things Up

In your csuit() and cface() functions, you have a bunch of case statements to convert between an int and a string. You could do this more easily with an array, like this:

const char* csuit(const int v)
{
    const char* kSuits[] = {
        "Hearts",
        "Clubs",
        "Diamonds",
        "Spades"
    };
    return kSuits [ v % 4 ];
}

You can do a similar thing with cface().

Simplify

Several of your functions have complicated nested while loops. I'd try to simplify them. I'd make the one in main() it more like this:

while (strcmp(input, "quit") != 0)
{
    if (strcmp(input, "play") == 0)
    {
        play();
    }
    else if (strcmp(input, "help") == 0)
    {
        printf("Type 'play' to begin the game.\n");
    }

    scanf("%s", input);
}

You can eliminate the call to quit() as your main() will just exit when the user enters "quit".

On the subject of simplifying, I'd probably also break turn() into smaller functions, and try to make the logic simpler. Perhaps use a state machine.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the help! You have some great points, especially about the arrays in the card face and suit functions, I hadn't thought about it that way (: \$\endgroup\$ – Nick Borisenko Mar 25 '16 at 4:53
1
\$\begingroup\$

Flaw with aces

Currently, your program forces you to decide what to do with an ace as soon as it is dealt. So for example, if you were dealt A 3, you might decide to treat the ace as an 11 and have a total count of 14. But if you hit and get a 10, your program would consider that a bust. In real blackjack, if you had A 3 10, you would actually only be at a 14 count still.

To fix this, you need to track whether the hand has any aces, and always treat the aces as value 1. Then at the last moment, when you are evaluating the hand for point count, you can attempt to add back 10 if possible.

\$\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.