edit: After receiving the great feedback, I have tried to make my code better. The new version can be seen here: Simple Blackjack game in console (update)
This is one of my first finished projects and I like to be sure that I am doing things right. Especially the part of modelling the classes and who has to do what. Also, I am not so sure about model/view/controller separation. Right now I have the Person class handling some of the view (the printCards() method) and the Player class handling some of the control (the wantToHit() method) and I'm not sure if this is alright or if this could be improved.
Blackjack.java
package nl.rickhurkens.blackjack;
import java.util.ArrayList;
import java.util.Scanner;
public class Blackjack {
private static Blackjack game;
private ArrayList<Person> players;
private Deck deck;
public Blackjack() {
initializeGame();
dealCards();
takeTurns();
declareWinner();
playAgain();
}
public static void main(String[] args) {
game = new Blackjack();
}
private void initializeGame() {
deck = new Deck();
players = new ArrayList<Person>();
players.add(new Player());
players.add(new Dealer());
System.out.println("Welcome to a new Blackjack game!");
}
private void dealCards() {
for (Person player : players) {
deck.dealInitialCards(player);
player.printCards(player.getName().equals("You"));
}
}
private void takeTurns() {
for (Person player: players) {
boolean endOfTurn = false;
while(!endOfTurn) {
player.printCards(true);
boolean hit = player.wantToHit();
if (hit) {
deck.dealCard(player);
System.out.println(player.getName() + " drew a card.\n");
if (player.getTotal() > 21) {
endOfTurn = true;
System.out.println(player.getName() + " died.\n");
}
} else {
endOfTurn = true;
System.out.println(player.getName() + " stayed.\n");
}
}
}
}
private void declareWinner() {
byte highest = -1;
byte topPlayer = -1;
for (byte i = 0; i < players.size(); i++) {
String name = players.get(i).getName();
byte total = players.get(i).getTotal();
System.out.println(name + (name.equals("You") ? " have " : " has ") + "a total of " + total + ".");
if (total > highest && total <= 21) {
highest = total;
topPlayer = i;
}
if (total == highest && name.equals("The dealer")) {
topPlayer = i;
}
}
if (topPlayer == -1) {
System.out.println("Everyone is dead. Nobody wins.");
} else {
System.out.println(players.get(topPlayer).getName() + " wins!");
}
}
private void playAgain() {
System.out.print("\nPlay again? \"y\" / \"n\": ");
Scanner keyboard = new Scanner(System.in);
while(true) {
String input = keyboard.next();
if (input.equals("y")) {
game = new Blackjack();
} else if (input.equals("n")) {
System.exit(0);
} else {
System.out.print("Please type \"y\" or \"n\": ");
}
}
}
}
Person.java
package nl.rickhurkens.blackjack;
import java.util.ArrayList;
import java.util.List;
public abstract class Person {
private List<Card> cards;
protected String name;
protected byte total;
public Person() {
cards = new ArrayList<Card>();
total = 0;
}
public Card receiveCard(Card card) {
cards.add(card);
total += card.getValue();
return card;
}
public abstract boolean wantToHit();
public byte getTotal() {
return total;
}
public String getName() {
return name;
}
public void printCards(boolean showAll) {
System.out.print("\n" + name + (name == "You" ? " have a" : " has a") + (cards.get(0).getValue() == 8 || cards.get(0).getValue() == 11 ? "n " : " ") + cards.get(0).toString());
for (int i = 1; i < cards.size(); i++) {
if (showAll) {
System.out.print(" and a" + (cards.get(i).getValue() == 8 || cards.get(i).getValue() == 11 ? "n " : " ") + cards.get(i).toString());
} else {
System.out.println(" and a hidden card.");
}
}
if (showAll) {
System.out.println(".\n" + name + (name == "You" ? "r" : "'s") + " total is " + total + ".");
}
}
}
Player.java
package nl.rickhurkens.blackjack;
import java.util.Scanner;
public class Player extends Person {
private Scanner keyboard;
public Player() {
super();
name = "You";
keyboard = new Scanner(System.in);
}
public boolean wantToHit() {
System.out.print("\nWould you like to \"hit\" or \"stay\": ");
while (true) {
String input = keyboard.next();
if (input.equals("hit")) {
return true;
} else if (input.equals("stay")) {
return false;
} else {
System.out.print("Type \"hit\" or \"stay\": ");
}
}
}
}
Dealer.java
package nl.rickhurkens.blackjack;
import java.util.concurrent.ThreadLocalRandom;
public class Dealer extends Person {
public Dealer() {
super();
name = "The dealer";
}
public boolean wantToHit() {
if (total < 16) return true;
if (total > 16) return false;
if (ThreadLocalRandom.current().nextInt(0,2) == 0) return true;
return false;
}
}
Deck.java
package nl.rickhurkens.blackjack;
import java.util.concurrent.ThreadLocalRandom;
public class Deck {
public Card drawNextCard() {
int value = ThreadLocalRandom.current().nextInt(2,12);
return new Card(value);
}
public void dealCard(Person person) {
person.receiveCard(drawNextCard());
}
public void dealInitialCards(Person person) {
dealCard(person);
dealCard(person);
}
}
Card.java
package nl.rickhurkens.blackjack;
public class Card {
private int value;
public Card(int value) {
this.value = value;
}
public String toString() {
return "" + value;
}
public int getValue() {
return value;
}
}