So, we have to make a game for our midterm. The first thing that came to my mind was the long-missed card game I used to play with my family. That game is a 5 player game that would have become very difficult to cod in the limited time and also go beyond the scope of the assignment. Hence, I decided to adapt that game to my version of a 2-player game (somewhat similar to an already existing card game called ‘Honeymoon Bridge’). I get so excited by even just hearing the word ‘cards’, hence there was no better choice for me for this project.
Now to simplify what the game is (as many people aren’t familiar with tricks-making card games), here is an outline –
- There will be a normal playing deck of 52 cards, however, only 26 cards will be dealt.
- Each player gets 13 cards and the aim of the game is to make the most tricks.
- None of the players know which are are still in the deck / have not been dealt so a lot of strategy and some guesswork is required to win the game.
- Now, what does it mean to make a trick? Both players play a card one by one. The bigger numbered card gets the trick (King is the highest).
- There are a few rules as to what cards you can play. The first player can put any card, but everyone else on the table has to play a card of the same suit. In case you do not have any cards left of that suit, you may play any other suit but you cannot get the trick in that round.
- The player who plays first is decided by who makes the trick. For the first round, the person who is dealt first plays first.
- Since each player has 13 cards, there will be 13 rounds and any player needs a minimum of 7 tricks to win the game.
- Once 7 tricks have been made, the game finishes.
Besides figuring out the logic, I also had to keep in mind how visually my game arena looks and how I envision my cards to move along the screen to give a sense of an actual card game. I started with first planning out and sketching all my idea (see one of my many pages below), going over pseudocode, and then finally started programming it in Processing.
I first made placeholders for where all I wanted my cards, deck, playing table, etc. to be.
Once the cards and deck were placed, it was time to deal cards. I was going for a theme where one could see each card being dealt. Initially, it was a big disaster (see video below). I was unable to control the position or the timing of the cards being dealt. After working for about 3 hours on it, I decided to leave it for a while and then come back to it.
If not physically, I thought it is a good idea to focus on logically dealing the cards. For that, I made 2 arrays – p1Hand and p2Hand – to keep track of card IDs that have been assigned to each player. I used random() to assign the cards and a boolean variable dealt to keep track of if a card has been dealt or not (so as to not repeat a card while dealing). Once the cards had been dealt ‘logically’, I made a sorting function to arrange the cards by suit and number because you cannot play a good card game without having arranged the cards in hand properly! I used Bubble Sort to do this (which I knew from one of my previous CS courses).
After this, I tried again to deal the cards ‘physically’ on the screen and surprisingly succeeded. I got to it by going step by step and building a really complicated code, which I hope to simplify if I am able to think of a better alternative. Though, having the cards deal exactly how I wanted them to, gave me a lot of satisfaction. This was the hardest part till now.
Then I made placeholders for where the cards had to be played on the playing table and just tried coding for the cards to show up there with a click.
Once that worked properly, I tried going further and coding this according to game rules (being able to click on certain cards or not according to the game rules and keeping track of whose turn it is). Also, after both players have played their cards, the round counter increments, and I call the trick() function to check who won the trick and calculate the total number of tricks of each person.
This is my progress as of now (better to watch the video on 2x speed! :D) –
It contains a few bugs that I still need to fix –
- The tricks are not incrementing all the time. There is some error in that which I have to look at
- Not all cards are being shown on top
Things I still need to work on –
- Change the turn based on who makes the trick
- After making a trick, the cards need to go on the right side and stack up like you put tricks in a line (this will again require animation)
- Rounds need to be updated properly
- Appropriate delays need to be made in order to see the movements on the board
- Decide whether PC is the second player or have 2 people play it – and accordingly code it
- Add sounds of dealing, playing, stacking to make it more realistic
- Make the menu and instructions page
- Make the end page with a restart option
- Introduce the trump variation if time left
- Introduce the ‘baazi’ variation if time left
- (and anything else that I am currently missing)
CODE
The Card class:
class Card { int id; /* rom 1 to 52 - first 13 = clubs, then diamonds, heart and last 13 spades - according to heirarchy of suits */ int number; String suit; PImage cImg; float posX, posY, prevPosX, prevPosY; boolean dealt; //it the card has been assignmed to any player boolean visible; //a card is visible only in the hand or table boolean played; //to keep track whether the card has been played by a player or not //constructor - get parameters of photo, number and suit Card (int cid, int num, String s, PImage img) { id = cid; number = num; suit = s; cImg = img; dealt = false; visible = false; played = false; } }
The main code:
/* SOME BASIC GAME RULES A deck of 52 cards Only 26 cards are dealt - 13 per player Each player plays one card, the one who has a higher card winds the trick - this is one round the game has 13 rounds so whoever makes 7 tricks first has more tricks and thus has won the game the player who plays his card second can only play a card of the same suit unless they don't have that suit the player who makes the trick plays first in the enxt round */ int screenMode = 1; //loading all images PImage[] bg; PImage[] cImgs; PImage table; //Deck deck; Card[] cards; int[] p1Hand; //keeps track of the cards in hand for player one (by id) int[] p2Hand; //keeps track of the cards in hand for player two (by id) //Card[] back; - can have this seperately //game screen positions float cardW, deckW, pCardW; float ratio; float tableH, tableW; //dimension of the playing table float marginX, marginY; // to keep track of our=tside table margins float p1X, p2X; //to keep track of position where players play their cards float deckPosX, deckPosY; //variables needed while dealing the cards boolean dealt = false; int dealCardNum = 0; //to keep track of which card is being dealt; float dealIncNum = 5; //how much distance to cover for each dealt card int[] ctr; //to keep track how much distance my dealt card has travelled //variables needed while playing int round = 0; //game consists of total 13 rounds (each player has 13 cards) int turn = 1; //to keep track which player's turn it is int firstTurn = 1; //to keep track of which player has to start round - no restriction on playing the card int card1Played; //to keep track of which card was played first - to match the suit int p1trick = 0; //to keep track of the number of tricks made by player 1 int p2trick = 0; //to keep track of the number of tricks made by player 2 void setup() { //canvas size size(1200, 800); //fullScreen(); background(255); //frameRate(180); //setting the various positions of game screen tableW = width*0.6; tableH = height*0.55; marginX = (width-tableW)/2; marginY = (height-tableH)/2; cardW = (tableW+marginX)/13; //for cards in hand ratio = 1056/691; //to determine height of cards depending on cardW deckW = marginX*0.6; //for cards in the deck p1X = width/2 - deckW; p2X = width/2 + deckW; deckPosX = marginX/2; deckPosY = height/2; rectMode(CENTER); imageMode(CENTER); textAlign(CENTER, CENTER); //loading backgrounds for different game screens bg = new PImage[4]; bg[1] = loadImage("bg1.jpg"); //loading additional images table = loadImage("table.jpg"); //loading card images cImgs = new PImage[53]; // 1 extra for back side // to store card image names String[] filenames = {"back.png", "AC.png", "2C.png", "3C.png", "4C.png", "5C.png", "6C.png", "7C.png", "8C.png", "9C.png", "10C.png", "JC.png", "QC.png", "KC.png", "AD.png", "2D.png", "3D.png", "4D.png", "5D.png", "6D.png", "7D.png", "8D.png", "9D.png", "10D.png", "JD.png", "QD.png", "KD.png", "AH.png", "2H.png", "3H.png", "4H.png", "5H.png", "6H.png", "7H.png", "8H.png", "9H.png", "10H.png", "JH.png", "QH.png", "KH.png", "AS.png", "2S.png", "3S.png", "4S.png", "5S.png", "6S.png", "7S.png", "8S.png", "9S.png", "10S.png", "JS.png", "QS.png", "KS.png" }; for (int i=0; i<=52; i++) cImgs[i] = loadImage(filenames[i]); //creating my card objects cards = new Card[53]; cards[0] = new Card(0, 0, "back", cImgs[0]); //for clubs - cards 1-13 for (int i=1; i<=13; i++) cards[i] = new Card(i, i, "clubs", cImgs[i]); //for diamonds - cards 14-26 for (int i=14; i<=26; i++) cards[i] = new Card(i, i-13, "diamonds", cImgs[i]); //for hearts - cards 27-39 for (int i=27; i<=39; i++) cards[i] = new Card(i, i-26, "hearts", cImgs[i]); //for spades - cards 40-52 for (int i=40; i<=52; i++) cards[i] = new Card(i, i-39, "spades", cImgs[i]); //player hands p1Hand = new int[13]; p2Hand = new int[13]; //deal ctr = new int[26]; for (int i=0; i<26; i++) ctr[i]=0; deal(); } void draw() { switch (screenMode) { case 0 : startScreen(); break; case 1 : gameScreen(); break; case 2 : exitScreen(); break; case 3 : instScreen(); break; //instruction screen } } void startScreen() { } void gameScreen() { //background - red texture image(bg[1], width/2, height/2, width, height); //display the table pushStyle(); strokeWeight(10); fill(0); stroke(0, 120, 70); rect(width/2, height/2, tableW, tableH); popStyle(); image(table, width/2, height/2, tableW, tableH); //displaying deck cards //for (int i = 0; i<26; i++) image(cards[0].cImg, marginX/2, height/2, deckW, deckW/691*1056); pushStyle(); fill(255, 255, 0); textSize(18); text("Deck", marginX/2, height/2-deckW/691*1056*1.2/2); popStyle(); //displaying other placeholders pushStyle(); fill(0, 0, 0, 160); rect(p1X, height/2, deckW, deckW/691*1056); rect(p2X, height/2, deckW, deckW/691*1056); fill(255, 255, 0); textSize(16); text("Player 1 Card", p1X, height/2-deckW/691*1056*1.2/2); text("Player 2 Card", p2X, height/2-deckW/691*1056*1.2/2); popStyle(); //dealing cards if (!dealt) { for (int i=0; i<26; i++) if (ctr[i]>dealIncNum) if (i%2==0) image(cards[0].cImg, marginX/2+cardW/2+i/2*cardW, height-marginY/2, cardW, cardW/691*1056); else image(cards[0].cImg, marginX/2+cardW/2+i/2*cardW, marginY/2, cardW, cardW/691*1056); if (dealCardNum%2 == 0) dealCards(marginX/2+cardW/2+dealCardNum/2*cardW, height-marginY/2); else dealCards(marginX/2+cardW/2+dealCardNum/2*cardW, marginY/2); if (dealCardNum >= 26) dealt = true; } //start game after dealing cards else { //play cards in the center //println(mouseX + " " + mouseY); //if it is Player 1's turn if (turn == 1) { if (firstTurn == 1) //player 1 is starting the round { if (mouseX<width-marginX/2 && mouseX>marginX/2 && mouseY<height-marginY/2+cardW/691*1056/2 && mouseY>height-marginY/2-cardW/691*1056/2 && mousePressed) { //background(255); int cardPlayed; cardPlayed = int((mouseX-marginX/2)/cardW); card1Played = p1Hand[cardPlayed]; cards[card1Played].posX=p1X; cards[card1Played].posY=height/2; cards[card1Played].played = true; turn=2; //player 2's turn now //image(cards[p1Hand[cardPlayed]].cImg, cards[p1Hand[cardPlayed]].posX, cards[p1Hand[cardPlayed]].posY, cardW, cardW/691*1056); } } } if (turn == 2) { if (firstTurn == 2) //player 2 is starting the round { if (mouseX<width-marginX/2 && mouseX>marginX/2 && mouseY<marginY/2+cardW/691*1056/2 && mouseY>marginY/2-cardW/691*1056/2 && mousePressed) { //background(255); int cardPlayed; //gives index of card in hand not cards array cardPlayed = int((mouseX-marginX/2)/cardW); card1Played = p2Hand[cardPlayed]; cards[card1Played].posX=p2X; cards[card1Played].posY=height/2; cards[card1Played].played = true; turn=1; //player 2's turn now //image(cards[p1Hand[cardPlayed]].cImg, cards[p1Hand[cardPlayed]].posX, cards[p1Hand[cardPlayed]].posY, cardW, cardW/691*1056); } } else //if the player is second to play in this round { if (mouseX<width-marginX/2 && mouseX>marginX/2 && mouseY<marginY/2+cardW/691*1056/2 && mouseY>marginY/2-cardW/691*1056/2 && mousePressed) { int cardPlayed; //gives index of card in hand not cards array cardPlayed = int((mouseX-marginX/2)/cardW); if (check(p2Hand[cardPlayed], p2Hand)) //to check if the card played is correct or not; { //updating position of the card cards[p2Hand[cardPlayed]].posX=p2X; cards[p2Hand[cardPlayed]].posY=height/2; cards[p2Hand[cardPlayed]].played = true; //see who won the trick trick(p2Hand[cardPlayed], 2); round++; cards[card1Played].visible = false; cards[p2Hand[cardPlayed]].visible = false; turn = 1; //needs to be changed according to who made the trick } } } } //display all the cards in their respective places for (int i=1; i<=52; i++) if (cards[i].visible) image(cards[i].cImg, cards[i].posX, cards[i].posY, cardW, cardW/691*1056); pushStyle(); fill(255, 255, 0); textSize(18); text(p1trick, width-marginX/2, height/2+deckW/691*1056*1.2/2); text(p2trick, width-marginX/2, height/2-deckW/691*1056*1.2/2); popStyle(); //loop(); } } void dealCards(float x, float y) { if (ctr[dealCardNum]<=dealIncNum) { float incrementX = (x-deckPosX)/dealIncNum; float incrementY = (y-deckPosY)/dealIncNum; float incrementDeckW = (cardW-deckW)/dealIncNum; //x = marginX/2+cardW/2+0*cardW; //y = height-marginY/2; //dealPosXi += (x - dealPosXi)*0.5; //dealPosYi += (y - dealPosYi)*0.5; //dealDeckW += (cardW - dealDeckW)0.5; image(cards[0].cImg, deckPosX+ctr[dealCardNum]*incrementX, deckPosY+ctr[dealCardNum]*incrementY, deckW+ctr[dealCardNum]*incrementDeckW, (deckW+ctr[dealCardNum]*incrementDeckW)/691*1056); ctr[dealCardNum]++; } else { //image(cards[0].cImg, x, y, cardW, cardW/691*1056); dealCardNum++; } } void sortCards(int[] hand) //using bubble sort - I know this from my prev CS course { int n = hand.length; for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (hand[j] > hand[j+1]) { //swap the elements int temp = hand[j]; hand[j] = hand[j+1]; hand[j+1] = temp; } } } } boolean check(int index, int[] hand) { if (cards[index].suit == cards[card1Played].suit) return true; //correct card has been played else { //first check if there are cards of this suit for (int i=0; i<13; i++) if (cards[hand[i]].suit == cards[card1Played].suit && !cards[hand[i]].played) //found a card of same suit { text("You must play a card of the same suit", width/2, marginY*1.1); return false; } return true; } } void trick(int card2, int player) { int card1 = card1Played; if (cards[card1].suit == cards[card2].suit) { if (card2 > card1) { if (player == 1) //first player played a greater card p1trick++; else if (player == 2) p2trick++; } else //if the second card played is smaller { if (player == 1) //first player played a greater card p2trick++; else if (player == 2) p1trick++; } } else { if (player == 1) p2trick++; else p1trick++; } } void exitScreen() { } //instruction screen void instScreen() { } void deal() { //assigning cards to each player / logically dealing cards int cid; for (int i=0; i<13; i++) { do { cid = int(random(52))+1; } while (cards[cid].dealt); if (!cards[cid].dealt) { cards[cid].dealt = true; p1Hand[i]=cid; //player 1 has been assigned/dealt this card //println(cid); } do { cid = int(random(52))+1; } while (cards[cid].dealt); if (!cards[cid].dealt) { cards[cid].dealt = true; p2Hand[i]=cid; //player 2 has been assigned/dealt this card //println(cid); } } //sort the cards in hand - because you cannot play until you arrange cards properly sortCards(p1Hand); sortCards(p2Hand); //displaying hand cards for player 1 that have been assigned/dealt int j; for (int i = 0; i<13; i++) { j = p1Hand[i]; //update position in the class attribute for those cards cards[j].posX = marginX/2+cardW/2+i*cardW; cards[j].posY = height-marginY/2; cards[j].visible = true; //image(cards[j].cImg, cards[j].posX, cards[j].posY, cardW, cardW/691*1056); //fill(0,0,255); //tresting if values have been assignmed properly //text(p1Hand[i], marginX/2+cardW/2+i*cardW, height-marginY/2); //text(cards[p1Hand[i]].id, marginX/2+cardW/2+i*cardW, height-marginY/2); } //displaying hand cards for player 2 that have been assigned/dealt for (int i = 0; i<13; i++) { j = p2Hand[i]; //update position in the class attribute for those cards cards[j].posX = marginX/2+cardW/2+i*cardW; cards[j].posY = marginY/2; cards[j].visible = true; //image(cards[j].cImg, cards[j].posX, cards[j].posY, cardW, cardW/691*1056); } }