(P) Pairs¶
Goal: I will practice using vectors and object-oriented programming, as well as using Git. I will also learn to follow good programming style.
Ohjeita:
Retrieve the code template: templates/04/pairs
-> student/04/pairs
.
The code template consists of the files main.cpp
and pairs.pro
,
as well as the classes Card
(card.hh
, card.cpp
) and Player
(player.hh
, player.cpp
).
Your task is to continue writing code such that you
finally have the game called Pairs (Memory).
You are allowed to change the existing code in the code template.
Attention
Before starting to write code, read carefully the whole assignment. Especially, note the required commits.
Attention
This project can be done either in pairs or independently. If you do the project in pairs, choose ”Form a group” from the menu on the left to register your group. In addition, the submission box at the very end of the current page shows two choices: “Submit alone” / “Submit with…”. You should be careful when selecting the choice, because you cannot change it afterwards. Enter only the address of the Git repository owned by either of you (not both) in the submit box. The code files must contain (in comments) the personal data of both of you.
You can look for a group via Kooditorio’s Discord link (see Timetable & links).
Header comment and feedback language¶
The Evaluation point in this section requires a header comment in the code file. It means something like below:
/* Pairs
*
* Desc:
* This program generates a pairs (memory) game. The game has a variant
* number of cards and players. At the beginning, the program also asks for a
* seed value, since the cards will be set randomly in the game board.
* On each round, the player in turn gives the coordinates of two cards
* (totally four numbers). After that the given cards will be turned as
* visible and told if they are pairs or not. If they are pairs, they are
* removed from the game board, the score of the player is increased, and
* a new turn is given for the player. If the cards are not pairs, they
* will be turned hidden again, and the next player will be in turn.
* The program checks if the user-given coordinates are legal. The cards
* determined by the coordinates must be found in the game board.
* After each change, the game board is printed again. The cards are
* described as letters, starting from A and lasting so far as there are
* cards. In printing the game board, a visible card is shown as its letter,
* a hidden one as the number sign (#), and a removed one as a dot.
* Game will end when all pairs have been found, and the game board is
* empty. The program tells who has/have won, i.e. collected most pairs.
*
* Program author ( Fill with your own info )
* Name: Teemu Teekkari
* Student number: 123456
* UserID: teekkart ( Necessary due to gitlab folder naming. )
* E-Mail: teemu.teekkari@tuni.fi
*
* Notes about the program and it's implementation:
*
* */
Add above kind of comment at the very beginning of the file main.cpp
.
Since there are other files, too, student (author)
information must be in all of them.
The description part is not necessary to repeat in every file, it is enough
to put it in the main program file.
You can write the above comment also in Finnish (see Finnish assignment), but write all comments in the same language. The template code has comments both in Finnish and English, you remove those not needed. Remember to replace parts of the above text with your personal data.
The feedback language will be chosen in the submission box (at the very end of this page). By default, the feedback language is Finnish, but you can change it, if you want to have the feedback, given by an assistant, in English. We will use the language you have given in the submission box of the final submission.
If you are working in pairs, add personal data of both of you.
Game rules¶
The aim is to collect as much pairs as possible. If the player in turn gets pairs, the cards in question are removed from the game board, the score of the player is increased, and a new turn is given for the player. If the cards are not pairs, they will be turned hidden again, and the next player will be in turn.
The game is over, when all pairs have been found. The winner is the player with most pairs.
Pairs game vs waterdrop game¶
The materials of this round include a description of waterdrop game that also uses a vector, the elements of which are vectors. Further the elements of the inner vector are squares. The pairs project has a similar structure, but the elements of the inner vector are cards. In the waterdrop game, each square has a pointer to the game board, since each square must know its adjacent squares. In pairs, this is not needed, since cards need not know their adjacent cards.
In this project, you do not necessarily need pointers at all.
If you want to use them, you can use a pointer to a player in the same way,
as has been done in the Mölkky game (an exercise from the previous round).
There was the pointer in_turn
pointing to the player currently in turn.
In pairs, several players are allowed, and always one of them is in turn.
Since there can be any number of players, it is best to store them into a
vector.
Therefore you can use a vector index to tell the player in turn instead
of using a pointer.
In the waterdrop game, it would be more natural to have drop objects
instead of square objects.
However, the problem is that the game board can also have empty slots.
If the objects were drops, how could you describe an empty slot?
Actually the pairs game has the same problem.
There will be an empty slot in the game board, when a player has found pairs.
From the point of view of the game board, it would be more natural if
the objects were card places, not cards.
Cards will move from the game board to a player, and thus, from the point
of view of players, it is more natural to keep the objects as cards.
Therefore, the Card
class has an option for an ”empty” card,
which actually means an empty slot in the game board.
Assignment¶
Your task is to implement a program that works in the same way as the pairs game described above.
First the user is asked for the following information:
- the amount of cards
- seed value
- the amount of players.
Based on the number of cards, the cards are set in the game board in the form of a rectangle that is as close to a square as possible (c. f. the exercise Nearest factors from round 2). The seed value is used to draw locations in the game board: the first drawn location gets the card A, the second one gets the second A, the third one gets B, the fourth one gets the second B, and so on. After receiving the amount of players, the program reads as many strings as this amount is, and the strings are stored as the names of the players. The program allows several players with the same names, but then following the game progress becomes more difficult.
The code template gives actions for initializing and printing the game board. In other words, you need not care about drawing nor setting the cards, nor printing the game board.
Your task is to implement the actual game.
This includes also asking for the amount and names of the players.
Based on this information, a necessary amount of player objects are created,
and they are stored into a vector.
You will mainly write the code into the file main.cpp
, but
some small parts must also be written into given classes (Card
and
Player
).
The player in turn is asked for the cards to be turned. Then the player gives x and y coordinates of two cards, i.e. totally four numbers.
If the coordinates given by the user are legal, cards will be turned. This happens by printing the game board again. If the coordinates are illegal, the program informs about illegal input. More precise error messages will be shown in the examaple executions.
The game board is a grid, i.e. a vector, the elements of which are vectors. Instead of a graphical user interface, we will use ASCII graphics, whereupon the game board looks, for example at the beginning, like:
=================
| | 1 2 3 4 5 |
-----------------
| 1 | C # # # # |
| 2 | I # # # . |
| 3 | . # # # # |
| 4 | # # # # # |
=================
The above situation is as follows:
- The amount of cards is 20.
- Pairs have been found from the points (5, 2) and (1, 3), and these cards have been removed: at the place of them you can see a dot.
- The cards at (1, 1) and (1, 2) have been turned visible: they are printed as their letters, so you can see that they are not pairs.
In the printing, the number sign (#
) means a hidden card, and dot is
for an empty space, i.e. for a removed card.
If a card is visible, its letter is printed.
From the coordinates given by the user, the following things are checked.
- The given coordinates must be numbers.
- The points determined by the coordinates must be inside the game board. More precisely, a coordinate must be greater or equal to 1, and it must not exceed the amount of rows/columns. For example, in the game board above, the point (5, 5) is not accepted.
- The cards determined by the coordinates must exist in the game board. In other words, the card must not have been removed earlier. For example, in the game board above, the card (1, 3) is not accepted.
- The cards determined by the coordinates must not be the same cards. For example, the cards (2, 3) and (2, 3) cannot be given in the same input.
If one of the above requirement does not hold, the program prints:
Invalid card.
If the coordinates are valid, the program prints the game board, where the given cards are visible. In addition, it prints either:
Pairs found.
or:
Pairs not found.
After that the program prints the status of each player, i.e. the name and the amount of pairs collected so far. Then the program prints the game board again, all remaining cards hidden, and asks for the next cards to be turned.
Instead of the coordinates (four numbers) the user can give the command
q
(quit), whereupon the program prints:
Why on earth you are giving up the game?
and terminates with the return value EXIT_SUCCESS
.
When all pairs have been found, the game is over, and the program prints:
Game over!
as well as the amount of pairs collected by the winner(s). Printing varies a bit, if there is only one winner, or if there are several of them.
More precise action of the program can be seen in the example execution:
Enter the amount of cards (an even number): xxx
Enter the amount of cards (an even number): 11
Enter the amount of cards (an even number): 0
Enter the amount of cards (an even number): 12
Enter a seed value: 1
Enter the amount of players (one or more): yyy
Enter the amount of players (one or more): 0
Enter the amount of players (one or more): 3
List 3 players: Maarit Minna Eliisa
===============
| | 1 2 3 4 |
---------------
| 1 | # # # # |
| 2 | # # # # |
| 3 | # # # # |
===============
Maarit: Enter two cards (x1, y1, x2, y2), or q to quit: 1 1 5 5
Invalid card.
Maarit: Enter two cards (x1, y1, x2, y2), or q to quit: 1 1 a b
Invalid card.
Maarit: Enter two cards (x1, y1, x2, y2), or q to quit: 1 2 1 2
Invalid card.
Maarit: Enter two cards (x1, y1, x2, y2), or q to quit: 1 1 2 1
===============
| | 1 2 3 4 |
---------------
| 1 | C A # # |
| 2 | # # # # |
| 3 | # # # # |
===============
Pairs not found.
*** Maarit has 0 pair(s).
*** Minna has 0 pair(s).
*** Eliisa has 0 pair(s).
===============
| | 1 2 3 4 |
---------------
| 1 | # # # # |
| 2 | # # # # |
| 3 | # # # # |
===============
Minna: Enter two cards (x1, y1, x2, y2), or q to quit: 1 1 3 1
===============
| | 1 2 3 4 |
---------------
| 1 | C # C # |
| 2 | # # # # |
| 3 | # # # # |
===============
Pairs found.
*** Maarit has 0 pair(s).
*** Minna has 1 pair(s).
*** Eliisa has 0 pair(s).
===============
| | 1 2 3 4 |
---------------
| 1 | . # . # |
| 2 | # # # # |
| 3 | # # # # |
===============
Minna: Enter two cards (x1, y1, x2, y2), or q to quit: 2 1 4 1
===============
| | 1 2 3 4 |
---------------
| 1 | . A . F |
| 2 | # # # # |
| 3 | # # # # |
===============
Pairs not found.
*** Maarit has 0 pair(s).
*** Minna has 1 pair(s).
*** Eliisa has 0 pair(s).
===============
| | 1 2 3 4 |
---------------
| 1 | . # . # |
| 2 | # # # # |
| 3 | # # # # |
===============
Eliisa: Enter two cards (x1, y1, x2, y2), or q to quit: 2 1 2 3
===============
| | 1 2 3 4 |
---------------
| 1 | . A . # |
| 2 | # # # # |
| 3 | # A # # |
===============
Pairs found.
*** Maarit has 0 pair(s).
*** Minna has 1 pair(s).
*** Eliisa has 1 pair(s).
===============
| | 1 2 3 4 |
---------------
| 1 | . . . # |
| 2 | # # # # |
| 3 | # . # # |
===============
Eliisa: Enter two cards (x1, y1, x2, y2), or q to quit: 1 2 2 2
===============
| | 1 2 3 4 |
---------------
| 1 | . . . # |
| 2 | E B # # |
| 3 | # . # # |
===============
Pairs not found.
*** Maarit has 0 pair(s).
*** Minna has 1 pair(s).
*** Eliisa has 1 pair(s).
===============
| | 1 2 3 4 |
---------------
| 1 | . . . # |
| 2 | # # # # |
| 3 | # . # # |
===============
Maarit: Enter two cards (x1, y1, x2, y2), or q to quit: 1 1 2 2
Invalid card.
Maarit: Enter two cards (x1, y1, x2, y2), or q to quit: 4 1 4 2
===============
| | 1 2 3 4 |
---------------
| 1 | . . . F |
| 2 | # # # F |
| 3 | # . # # |
===============
Pairs found.
*** Maarit has 1 pair(s).
*** Minna has 1 pair(s).
*** Eliisa has 1 pair(s).
===============
| | 1 2 3 4 |
---------------
| 1 | . . . . |
| 2 | # # # . |
| 3 | # . # # |
===============
Maarit: Enter two cards (x1, y1, x2, y2), or q to quit: 3 3 4 3
===============
| | 1 2 3 4 |
---------------
| 1 | . . . . |
| 2 | # # # . |
| 3 | # . D E |
===============
Pairs not found.
*** Maarit has 1 pair(s).
*** Minna has 1 pair(s).
*** Eliisa has 1 pair(s).
===============
| | 1 2 3 4 |
---------------
| 1 | . . . . |
| 2 | # # # . |
| 3 | # . # # |
===============
Minna: Enter two cards (x1, y1, x2, y2), or q to quit: 2 2 3 2
===============
| | 1 2 3 4 |
---------------
| 1 | . . . . |
| 2 | # B B . |
| 3 | # . # # |
===============
Pairs found.
*** Maarit has 1 pair(s).
*** Minna has 2 pair(s).
*** Eliisa has 1 pair(s).
===============
| | 1 2 3 4 |
---------------
| 1 | . . . . |
| 2 | # . . . |
| 3 | # . # # |
===============
Minna: Enter two cards (x1, y1, x2, y2), or q to quit: 1 2 1 3
===============
| | 1 2 3 4 |
---------------
| 1 | . . . . |
| 2 | E . . . |
| 3 | D . # # |
===============
Pairs not found.
*** Maarit has 1 pair(s).
*** Minna has 2 pair(s).
*** Eliisa has 1 pair(s).
===============
| | 1 2 3 4 |
---------------
| 1 | . . . . |
| 2 | # . . . |
| 3 | # . # # |
===============
Eliisa: Enter two cards (x1, y1, x2, y2), or q to quit: 1 3 3 3
===============
| | 1 2 3 4 |
---------------
| 1 | . . . . |
| 2 | # . . . |
| 3 | D . D # |
===============
Pairs found.
*** Maarit has 1 pair(s).
*** Minna has 2 pair(s).
*** Eliisa has 2 pair(s).
===============
| | 1 2 3 4 |
---------------
| 1 | . . . . |
| 2 | # . . . |
| 3 | . . . # |
===============
Eliisa: Enter two cards (x1, y1, x2, y2), or q to quit: 1 2 4 3
===============
| | 1 2 3 4 |
---------------
| 1 | . . . . |
| 2 | E . . . |
| 3 | . . . E |
===============
Pairs found.
*** Maarit has 1 pair(s).
*** Minna has 2 pair(s).
*** Eliisa has 3 pair(s).
===============
| | 1 2 3 4 |
---------------
| 1 | . . . . |
| 2 | . . . . |
| 3 | . . . . |
===============
Game over!
Eliisa has won with 3 pairs.
If the game were over with a tie of three players, the program would print:
Game over!
Tie of 3 players with 2 pairs.
As shown at the beginning of the long print above, the program repeatedly asks for the amount of the cards until the user given an even number greater than zero. Similarly, the amount of players is asked until a positive number is given. Also for a seed value, it is possible to give something else than a number, whereupon the seed value will be zero. In such a case, the program does not repeatedly ask a new input, but any input is accepted and a non-numerical input is considered as zero.
At the beginning, you can also see different illegal coordinates. A bit later, you see the same error message due to a missing card.
It is informed only about one falsity, even if there were more of them. For example, if the coordinates were both too great and the same, only one error message would be given.
Input types and reading the input¶
In the template code, numbers (the amount of cards and a seed value)
are first read as strings and then transformed to integers by calling
the given function stoi_with_check
.
In this way, you can make sure that the input values are of the correct type.
Reading the amount of players is not in the template code, but its
implementation has been left for your task.
It is recommended to implement it in a similar way than reading the amount
of cards.
The names of players are strings, and thus, there are two ways to read them:
the operator >>
and the function getline
.
The players are read from the same line.
If you use getline
, you need to split players, since this function
reads the whole line consisting of all players.
If you use >>
(as many times as there are players), there is no need
to split players, but you have them directly as separate strings.
Therefore the latter way is perhaps simpler, but different programmers
may prefer different ways.
You can assume that the number of listed names is the same as given earlier.
The coordinates must be read as strings (or as a string), since also the
quit command q
is a possible input.
You have again the options, either getline
, or >>
four times.
You can assume that the quit command is always at the beginning of the input,
and thus, it is sufficient to check whether the first character/string
is q
or not.
Note that the code template gives you the function stoi_with_check
for transforming a numeric string into an integer.
If transforming does not succeed (the string has also other characters
than digits), the function returns zero.
When you later check the validity of the coordinates, zero is considered
invalid, since coordinates must be greater than zero.
Code templates¶
The template code includes the files pairs.pro
and main.cpp
,
as well as the classes Card
and Player
.
You will mainly write the code in main.cpp
.
The class Card
is otherwise completed, but the implementation for
the method print
is missing.
It is best to implement it as the very first thing to make it possible
to print the whole game board.
The public interface of the class Player
is ready, but your task
is to write implementations for the public methods (1-2 code lines for
each method).
You can do this in the very similar way as in the Player
class in
the Mölkky game exercise.
Which attributes does the class need?
If you feel it necessary, you can add new methods in classes Player
and Card
.
On the other hand, if some method is unnecessary, you can leave it without use.
In such case, the implementation of the method can be empty.
If an unused parameter causes warnings, remove the name of the parameter
or comment it away, but do not remove the type of the parameter.
The file main.cpp
already has the functions:
stoi_with_check
init_with_empties
next_free
init_with_cards
print_line_with_char
print
ask_product_and_calculate_factors
.
The functionality of them are described below, but it does not matter, if you do not understand all details. If desired, you can skip the rest of this subsection and move directly to the next subsection ”Project phases”.
C++ has a ready-made function stoi
for converting a (numeric) string,
given as a parameter, to the corresponding integer value.
If the given string is not numeric, an exception will be thrown.
Exceptions are not considered on this course.
The function stoi_with_check
works in rather similar way than stoi
,
but it checks, whether the given parameter is numeric.
If it is numeric, the conversion is made by calling stoi
.
If it is not numeric, the function returns zero.
In this case (but not in general), zero is a good return value,
since it is not a valid coordinate here.
The function init_with_empties
initializes each card as empty and
adds them in the game board vector by using the vector operation push_back
.
When the game board vector contains all elements (any), it is possible to
use at
function for indexing the vector, and in this way set the
actual elements in their places.
The function next_free
returns the next free (nonempty) cell in
the game board, starting from the given index.
If a free cell has not been found and search reaches the last cell
(right lower corner), search continues from the beginning of the game
board (left upper corner).
This can happen when almost all cells have been filled with cards.
However, a free cell is always found, since the size of the game board is
the same as the amount of cards.
This function is utilized by the function init_with_cards
, which
fills the game board with actual cards.
The function init_with_cards
uses random number generator for drawing
cells.
The first drawn cell gets the card A, the second one gets the second A,
the third one gets B, the fourth one gets the second B, and so on.
Since it is possible draw such a cell that already has a card, the
function next_free
is needed.
It returns the next free cell, if the originally drawn cell was already in
use.
Since the game board is filled in this kind of sprinkling way, it is not
possible to use push_back
, and thus, the game board has been earlier
initialized with empties (in init_with_empties
).
The function print_line_with_char
takes two parameters: the character
to be printed and the line length.
Based on them, the function prints a line consisting of a desired amount
of a desired character.
The function print
prints a game board of a desired size (width and
height).
It utilizes the function print_line_with_char
.
The function ask_product_and_calculate_factor
works like the code
implemented on round 2 (Nearest factors).
In other words, the function asks a product from the user, and based on it,
calculates such factors that as close to each other as possible.
Project phases¶
Use version control in your project such that there can be found at least five commits as follows:
- The program asks for the amount of players and their names, and reads this data.
- The program checks the validity of the given coordinates.
- The program turns a card if possible.
- In addition, you must have at least two more commits.
You must also mention the steps described above clearly enough in the commit messages to enable course assistant to find them easily when evaluating your project.
Tips for completing the assignment:
- Start by completing the classes
Card
andPlayer
. After that you can test how the template code works. - The template code does not print the game board, since the function
print
in the filemain.cpp
is not called. Add this call tentatively, for example, in themain
function. - After the following actions you can start actual programming by asking and reading the amount of players and their names.
Evaluation¶
To end up to assistents’ evaluation, your work must first pass the automatic tests. If the automated tests give 0 points for your work, also your final points will be 0, and your work will not be evaluated by an assistant.
The assistant evaluates the submissions that have passed the automated testing (= 1 p) based on the last commit before the deadline, according to the following criteria:
- The overall principle of the solution: 0-20 points:
- The learning goals of the exercise have been achieved.
- The program code has been split into logical, suitably long segments using functions, classes, and/or methods.
- Classes and objects have been implemented according to the basics of object-oriented programming (see previous material section: About programming style, especially at Object-oriented programming).
- The data structure does not include repetitive data nor unnecessary parts. The chosen data structures have been used in a reasonable way.
- The program code does not include unnecessary repetitions nor other unnecessary parts.
- The program code does not include unnecessary limitations or assumptions or other forced solutions.
- The implementations of the program structures are easy to understand.
- Global variables have not been used in the program code (global constants are OK).
- The program does not terminate with the
exit
function.
- Programming style: 0-20 points:
- Variables and functions are named clearly and appropriately.
- Named constants have been used instead of magic numbers.
- The program code is neatly formatted.
- The length of program code lines do not exceed 80 characters.
- At the beginning of each file, there is a comment explaining the purpose of the file, the creator(s) of the project and other necessary information (see the point Header comment).
- At the beginning of each function/method (in the header file if possible), there is a comment describing its working, return value and parameters.
- There are comments in the code where necessary.
- Comments are related to the current version of the program, not to an older one.
- All the variables have been initialized.
- The compiler does not give warnings while compiling.
- Using the version control: 0-10 points:
- There are enough commits.
- The content of commit messages is clear and relevant.
Attention
More precise requirements concerning good programming style can be found from the previous material section on the current round.
A+ presents the exercise submission form here.