4
\$\begingroup\$

I made this Blackjack game to help me learn the syntax of JavaScript classes and objects.

As I was writing the game I felt like I was misusing the idea of OOP. By that I mean I felt like some of the variables/methods of the class could be non related to the class and still work.

Let me know how I could improve this.

<script>
window.addEventListener("load", start, false);

function start()
{
	play();
}

function play()
{
	var deck1 = new Deck();
	deck1.createDeck();
	deck1.shuffle();
	deck1.deal21();
	document.getElementById("hit").addEventListener("click", function(){deck1.hit();},false);
	document.getElementById("stand").addEventListener("click", function(){deck1.stand();},false);
	document.getElementById("replay").addEventListener("click", function(){deck1.deal21();}, false);
}

function Deck()
{
	this.deck = new Array();
	this.userTotal;
	this.dealerTotal;
	this.money = 100;
	this.userBust;
	this.dealerBust;
	this.curusrHand;
	this.curdlrHand;
	var userHand = document.getElementById("user-hand");
	var dealerHand = document.getElementById("dealer-hand");
	var userScore = document.getElementById("user-score");
	var dealerScore = document.getElementById("dealer-score");
	var status = document.getElementById("game-status");
	var moneyDiv = document.getElementById("money");

	this.newDeck = function newDeck()
	{
		this.createDeck();
		this.shuffle();
		status.innerHTML="NEW DECK IN PLAY!";
	}

	this.createDeck = function createDeck()
	{
		var numCards=0;
		var suit, symbol, name;
		for(var k=1; k<=4; k++)
		{
			switch(k)
			{
				case 1: suit ="hearts";
				break;
				case 2: suit ="diamonds";
				break;
				case 3: suit ="spades";
				break;
				case 4: suit ="clubs";
				break;
			}

			for(var i=1; i<=13; i++)
			{
				symbol = i;
				switch(i)
				{
					case 1: name= "Ace";
					symbol = "A";
					break;
					case 2: name= "two";
					break;
					case 3: name= "three";
					break;
					case 4: name= "four";
					break;
					case 5: name= "five";
					break;
					case 6: name= "six";
					break;
					case 7: name= "seven";
					break;
					case 8: name= "eight";
					break;
					case 9: name= "nine";
					break;
					case 10: name= "ten";
					break;
					case 11: name= "jack";
					symbol = "J";
					break;
					case 12: name= "queen";
					symbol = "Q";
					break;
					case 13: name= "king";
					symbol = "K";
					break;
				}
				this.deck[numCards] = new Card(suit, i, name, symbol);
				numCards++;
			}
		}
	}

	this.shuffle = function shuffle()
	{
		var randomDeck = new Array();
		var empty = false;
		while(!empty)
		{
		var randomIndex = Math.floor(Math.random()*this.deck.length);
		randomDeck.push(this.deck[randomIndex]);
		this.deck.splice(randomIndex, 1);
		if(this.deck.length <=0) empty = true;
		}
		for(var i=0; i<randomDeck.length; i++)
		{
			this.deck[i] = randomDeck[i];
		}
	};


	this.calcValue = function calcValue(hand)
	{
		var val = 0;
		var tempArr = hand;
		tempArr.sort(function(a,b) { return parseFloat(a.val) - parseFloat(b.val) } );
		for(var i=tempArr.length-1; i>=0; i--)
		{
			var temp = tempArr[i];
			if(temp.val === 1 && val <=10)temp.val = 11;
			else if(temp.val >=10) temp.val = 10;
			val += temp.val;
		}
		return val;
	};

	this.emptyDeck = function emptyDeck()
	{
		if(this.deck.length < 1) return true;
		else return false;
	}

	this.deal21 = function deal21()
	{
		status.innerHTML="";
		this.money--;
		
		//reset all the stuff that needs to be reset if the game is being replayed
		money.innerHTML= "Money: " + this.money;
		dealerHand.innerHTML="<h2>Dealer Hand</h2>";
		userHand.innerHTML="<h2>User Hand</h2>";
		this.userTotal=0;
		this.dealerTotal=0;
		this.userBust=false;
		this.dealerBust=false;
		hit.setAttribute("style", "");
		stand.setAttribute("style", "");
		dealerScore.setAttribute("style", "");
		this.curusrHand = new Array();
		this.curdlrHand = new Array();


		for(i=0; i<2; i++)
		{
			if(this.emptyDeck())this.newDeck();
			this.curusrHand.push(this.deck.pop());
			userHand.innerHTML+=this.curusrHand[i].showCard();
		}
		this.userTotal = this.calcValue(this.curusrHand);
		userScore.innerHTML=this.userTotal;

		for(i=0; i<2; i++)
		{
			if(this.emptyDeck())this.newDeck();
			this.curdlrHand.push(this.deck.pop());
			dealerHand.innerHTML+=this.curdlrHand[i].showCard();
		}
		this.dealerTotal = this.calcValue(this.curdlrHand);
		dealerScore.innerHTML=this.dealerTotal;
		//hide dealers first card
		var firstCard = dealerHand.getElementsByClassName("card")[0];
		firstCard.setAttribute("id", "hidden-card");
		var blackjack =true;
		if(this.userTotal === 21 && this.dealerTotal < 21) this.gameOver(blackjack);
		else if(this.dealerTotal === 21) this.gameOver();
	};

	this.hit = function hit()
	{
		if(this.emptyDeck())this.newDeck();
		this.curusrHand.push(this.deck.pop());
		userHand.innerHTML+=this.curusrHand[this.curusrHand.length-1].showCard();
		this.userTotal = this.calcValue(this.curusrHand);
		userScore.innerHTML=this.userTotal;
		if(this.userTotal >21) 
			{
				userScore.innerHTML+=" <span style='color:red; font-weight: bold;'> BUST</span>";
				this.userBust = true;
				this.gameOver();
			}
	};

	this.stand = function stand()
	{
		while(this.dealerTotal < 17)
		{
			if(this.emptyDeck())this.newDeck();
			this.curdlrHand.push(this.deck.pop());
			dealerHand.innerHTML+=this.curdlrHand[this.curdlrHand.length-1].showCard();
			this.dealerTotal = this.calcValue(this.curdlrHand);
			dealerScore.innerHTML=this.dealerTotal;
			if(this.dealerTotal > 21) 
				{
					dealerScore.innerHTML+=" <span style='color:red; font-weight: bold;'> BUST</span>";
					this.dealerBust = true;
				}
		}
		this.gameOver();
	}

	this.gameOver = function gameOver(blackjack)
	{
		document.getElementById("hidden-card").setAttribute("id","");
		dealerScore.setAttribute("style", "visibility: visible;");
		hit.setAttribute("style", "visibility:hidden;");
		stand.setAttribute("style", "visibility:hidden;");

		if(blackjack) 
		{
			this.money +=3;
			status.innerHTML ="BLACKJACK!!!!!!!!!";
		}

		else if(this.userTotal > this.dealerTotal && this.userBust === false || this.dealerBust ===true)
		{
			//user wins
			this.money+=2;
			status.innerHTML ="YOU WIN!";
		}
		else if(this.userTotal === this.dealerTotal && this.userBust === false)
		{
			//push
			this.money++;
			status.innerHTML="PUSH :o";
		}

		else status.innerHTML="YOU LOSE!";

		money.innerHTML="Money: "+this.money;

	}

	this.dump = function dump()
	{
		for(var i=0; i<this.deck.length; i++)
		{
			this.deck[i].showCard();
		}
	};
}

function Card(suit, val, name, symbol)
{
		this.suit = suit;
		this.val = val;
		this.name = name;
		this.symbol = symbol;

	this.showCard =function showCard()
	{
		var html="";
		switch(this.suit)
		{
			case "hearts": suit_text = "&hearts;";
			break;
			case "diamonds": suit_text = "&diams;";
			break;
			case "spades": suit_text = "&spades;";
			break;
			case "clubs": suit_text = "&clubs;";
			break;
		}
		html="<div class='card " + this.suit + "'><div class='card-value'>" + this.symbol + "</div><div class='suit'>" + suit_text + "</div><div class='main-number'>"+this.symbol +"</div><div class='invert card-value'>"+this.symbol+"</div><div class='invert suit'>"+suit_text+"</div></div>";
		return html;
	}
}
</script>
div.card
{
	display: inline-block;
	width: 100px;
	height: 150px;
	border: solid 1px #333;
	border-radius: 5px 5px 5px 5px;
	float: left;
	margin-right: 10px;
	position: relative;
}

div.card.hearts, div.card.diamonds
{
	color: red;
}

div.card div.main-number
{
	font-size: 36px;
	font-weight: bold;
	text-align: center;
	margin: 0px auto;
	margin-top: 50px;
}

div.card-value, div.suit
{
	position: absolute;
	left: 5px;
}

div.card-value{top: 5px;}
div.card-value{top: 20px;}

div.card-value.invert, div.suit.invert
{
	transform: rotate(180deg);
	right: 5px;
	text-align: right;
	top: auto;
	left: auto;
}

div.card-value.invert{bottom: 5px;}
div.suit.invert{bottom: 20px;}

div.dealer-area{}

div.user-area{float: left;}

div#user-score, div#dealer-score
{
	display: inline-block;
}

div.score-parent
{
	width: 100%;
	display: inline-block;
}

div.score-parent p
{
	display: inline-block;
}

div#dealer-score
{
	visibility: hidden;
}

div.card#hidden-card
{
	background-color: #CBE0FD;
}

div.card#hidden-card div
{
	visibility: hidden;
}

div.controls input
{
	margin-left: 20px;
	width: 200px;
}
<html>
<head>
<title>Cards</title>
<link rel="stylesheet" type="text/css" href="cards.css">
<script type="text/javascript" src="classes.js"></script>
</head>
<body>
<div id="game-status"></div>
	<div class="user-area">
	<div id="money"></div>
	<div id="user-buttons" class="controls">
	<input type="button" id="hit" value="Hit">
	<input type="button" id="stand" value="Stand">
	<input type="button" id="replay" value="Play Again">
	</div>
	<div class="score-parent">
		<p>Score</p>
			<div id="user-score"class="score">
			</div>
		</div>
	<div id="user-hand" class="hand">
	<h2>User Hand</h2>
	</div>
	</div>
	<div class="dealer-area">
	<div class="score-parent">
		<p>Score</p>
			<div id="dealer-score"class="score">
			</div>
		</div>
	<div id="dealer-hand" class="hand">
	<h2>Dealer Hand</h2>
	</div>
	</div>
</body>
</html

\$\endgroup\$
  • 1
    \$\begingroup\$ OOP JavaScript is based on prototypes rather than classes. \$\endgroup\$ – 200_success Mar 1 '15 at 9:02
1
\$\begingroup\$

Your calcValue() function is buggy when it comes to deciding whether to count an ace as a 1 or 11. For example, it would report {A, 7, 5} as a bust.

The Deck object is doing too much. Not only is it a highly Blackjack-specific deck, it also simultaneously serves as model and controller. There should probably be a separate BlackjackDealer object or something.

The giant switch in createDeck() should be avoided. It could be replaced with an array or hash lookup. But I don't see why the English name for the card is needed at all.

The methods, particularly Card.showCard(), should be defined on the prototype instead. You don't want each of the 52 cards to have its own showCard() function.

You should design the user interface such that the buttons are in a predictable place. Buttons that resize or that have a tendency to move around the page lead to a poor user experience.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the feedback. Yeah the ui is a rough rough draft. I'll take care of that later once it is performing better. I couldn't get the calcValue() bug that you were getting although I did find an interesting bug when 2 aces show up and one should be an 11. \$\endgroup\$ – Logan Saruwatari Mar 1 '15 at 10:46
  • \$\begingroup\$ Agree. directly manipulating the DOM from the Deck feels really weird. Imagine if you were trying to implement a multi-deck game, using this code? Consider how you'd seperate the Deck from the game itself. And yeah, that huge switch is definitely better replaced with an array lookup. \$\endgroup\$ – Eric Blade Mar 2 '15 at 0:04

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.