Jump to content
  • Advertisement
Sign in to follow this  
vinb

MasterMind source code. Any criticism welcome.

This topic is 5460 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have just finished a simple console version of the game MasterMind (at least that's what it was called when I was growing up). I have included the source code here in hopes that someone could criticize it and offer any suggestions on how to clean it up, you know, change functions to increase cohesion and reduce coupling. I'm just getting the hang of using pointers (I think) so please be kind (not that I had any doubt as this is an excellent forum). Any help would be greatly appreciated. Thanks. main.h
#include <iostream>
#include <time.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctime>

using namespace std ;

// GLOBALS
const int MAX_ROWS = 20 ;
const int ROW_WIDTH = 7 ;

typedef char Row[ROW_WIDTH] ;

struct gamestate{
	Row game[MAX_ROWS] ; 
	Row key ;
	int difficulty ;
	int rowcount ;
	int score ;
} ;


char keyTranslate(int i){
	
	char c ;

	switch (i){
		case 1: c = 'R' ;
			break;
		case 2: c = 'G' ;
			break;
		case 3: c = 'B' ;
			break;
		case 4: c = 'Y' ;
			break;
		case 5: c = 'W' ;
			break;
		case 6: c = 'P' ;
			break;
		case 7: c = 'O' ;
			break;
		default: c = 'X' ;
	}
	return c ;
}

void setKey(Row keyRow) 
{
	srand(time(NULL)) ; // this should give us a random number all day
	for (int i=0;i < ROW_WIDTH; i++) {
		keyRow = ( keyTranslate( (rand() % ROW_WIDTH) + 1)  ); } 
}

inline bool isLoser(const int rowcount, int difficulty){
	return rowcount == difficulty ;
}

void writeKey(const Row key) {
	for (int i=0;i < ROW_WIDTH; i++) 
		cout <<  key << " "  ;
		cout << endl ;
}

bool isWinner(const Row UserChoice, const Row key ) {
	
	bool win = false ;
	int iNumCorrect = 0 ;

	for (int i=0;i < ROW_WIDTH; i++) {
		if ( UserChoice == key ){
			iNumCorrect++ ;
		}
	}

	if (iNumCorrect == ROW_WIDTH)
		win = true ;

	return win ;
}

inline void updateGame(const Row line, int rowcount, const int score, gamestate *thisGame){
	for (int i=0;i<ROW_WIDTH;i++){
		thisGame->game[rowcount] = line ;
	}

	thisGame->rowcount = rowcount ;
	thisGame->score = score ;

}

inline void writeInstructions(const int difficulty){
	cout << "Enter " << ROW_WIDTH << " colors (R)ed (G)reen (B)lue (Y)ellow (W)hite (P)urple, (O)range " << endl ;
	cout << "You have " << difficulty << " tries." << endl ;
}

inline COORD setPoint(const int x, const int y){
	COORD point = {x,y} ;
	return point ;
}

void setTitle(char title[], char cScore[]){
	char GAMETITLE[50] = "MasterMind! score = " ;
	strcat(GAMETITLE, cScore) ;
	SetConsoleTitle(GAMETITLE);
}

void setDifficulty(gamestate *thisGame){
	char tries[2] ;
	cin.get(tries,3) ;cin.ignore(3, '\n') ;
	thisGame->difficulty = atoi(tries) ;
	system("cls");
}

void writeGameState(const gamestate *thisGame){

	writeInstructions(thisGame->difficulty) ;
	
	HANDLE stdCon = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD point ;

	const int RED_ORANGE = 12 ;
		
	for (int i=0;i<thisGame->rowcount+1;i++)
		{
			point = setPoint(30,i + 3);
			SetConsoleCursorPosition(stdCon, point);

			for (int j=0;j<ROW_WIDTH;j++){
				switch (thisGame->game[j]){
					case 'R': SetConsoleTextAttribute(stdCon,FOREGROUND_RED | FOREGROUND_INTENSITY);
						break ;
					case 'G': SetConsoleTextAttribute(stdCon,FOREGROUND_GREEN | FOREGROUND_INTENSITY);
						break;
					case 'B': SetConsoleTextAttribute(stdCon,FOREGROUND_BLUE | FOREGROUND_INTENSITY);
						break;
					case 'P': SetConsoleTextAttribute(stdCon,FOREGROUND_BLUE | FOREGROUND_RED);
						break;
					case 'O': SetConsoleTextAttribute(stdCon, RED_ORANGE );
						break;
					case 'Y': SetConsoleTextAttribute(stdCon,FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY  );
						break;
					case 'W': SetConsoleTextAttribute(stdCon,FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);
						break;
					default: SetConsoleTextAttribute(stdCon,FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);
				}
				
				cout << thisGame->game[j] << " " ; }

			cout << "     " ;
	
			SetConsoleTextAttribute(stdCon,FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);

			for (int j=0;j<ROW_WIDTH;j++){
				if ( thisGame->game[j] == thisGame->key[j] )
					cout << 'Y' << " " ;
				else
					cout << 'N' << " " ;
			
			}
			cout << endl;
		}
}


main.cpp
#include "main.h"

// FUNCTIONS
char keyTranslate(int i) ;
void setKey(Row keyRow) ;
bool checkPattern(const Row UserChoice, const Row key, Row returnRow ) ;
void updateGame(const Row line, const int rowcount, const int score, gamestate *thisGame) ;
void writeGameState(const gamestate thisGame) ;
bool runGame(gamestate *thisGame) ;
void setDifficulty(gamestate *thisGame) ;
bool isWinner(const Row UserChoice, const Row key ) ;

// MAIN
void main() 
{
	int score = 0, difficulty = 0 ;
	char cScore[6] = "0", GAMETITLE[50] = "" ;

	gamestate *thisGame = new gamestate ;

	do {
		setTitle(GAMETITLE, itoa(score,cScore,10) ) ;
		cout << "Set difficulty (5 to 20)" << endl ;
		
		setDifficulty(thisGame);
		if ( thisGame->difficulty < 5 ) thisGame->difficulty = 5 ;
		if ( thisGame->difficulty > 20 ) thisGame->difficulty = 20 ;

		if ( !runGame(thisGame) ) {
			//MessageBox(NULL, "ERROR!!", "MasterMind!", MB_OK) ; 
			break ;}

		system("cls") ;
	} while (true) ;
	
	delete thisGame ;

}

bool runGame(gamestate *thisGame) {
	int i = 0, score = thisGame->score, rowcount = 0; 
	thisGame->rowcount = 0 ;
	Row line ;

	setKey(thisGame->key) ;
	//writeKey(thisGame->key); // for debugging only
	writeInstructions(thisGame->difficulty) ;
	
	COORD point ;
	HANDLE stdCon = GetStdHandle(STD_OUTPUT_HANDLE);
	
	bool lKeepGoing ;
	char cResponse[2] ;
	
	do{
		point = setPoint(0, 3) ; // reset cursor position 
		SetConsoleCursorPosition(stdCon, point);
        cin.get(line, ROW_WIDTH+1) ; cin.ignore(2000,'\n') ; // get user's guess

		if ( isWinner(line, thisGame->key ) ) {
			MessageBox(NULL, "YOU WIN!!", "MasterMind!", MB_OK);
			cout << "YOU WIN!!!" << endl  ;
			score +=10 ;
			break ;
		}
		else ;
		{
			if (score > 0) {
				score-- ;
				score += 10; 
			}
			system("cls") ;
		}
	
		updateGame(line, rowcount, score, thisGame) ;
		//cout <<"testing "<< thisGame->game[0][0] << endl ;
		//cin >> i ;

		rowcount++ ;
		
		if ( isLoser(rowcount, thisGame->difficulty) ){
			cout << "YOU LOOSE!!!" << endl  ;
			cout << "Correct answer: " ;
			writeKey(thisGame->key) ;
			break ;
		}
		
		writeGameState(thisGame) ;
	}
	while (true) ;

	cout << endl << "Play again? Y/N " ;
	cin.get(cResponse,2) ;
	lKeepGoing = (cResponse[0] == 'Y') ? true : false ;
	cin.get() ;

return lKeepGoing ;

}



Share this post


Link to post
Share on other sites
Advertisement
You've got all sorts of problems :).

First, you have a bunch of functions defined in your header. These should only be declarations (the exception being inline functions and templates). You redeclare some of your functions in main.cpp.

You're setting yourself up for redefinition errors if you continue to do it that way :).

Also, on loss, you're declaring that the player is promiscuous which isn't so nice :(.

Share this post


Link to post
Share on other sites
Quote:
Original post by Maega
You've got all sorts of problems :).

Isn't that true of most game programmers? :)

Quote:

Also, on loss, you're declaring that the player is promiscuous which isn't so nice :(.

Grammer and I don't get along too well.

Share this post


Link to post
Share on other sites
Quote:
Original post by Maega
You've got all sorts of problems :).

First, you have a bunch of functions defined in your header. These should only be declarations (the exception being inline functions and templates).


This is a feature I freaking hate about C++. If I want to have a function be inline, I have to include it in the class declaration - but that means importing tons of crap into my header file, making for nasty, bloated headers. Anyways, header files are supposed to be legible - having a bunch of procedural code violates their legibility - all they should contain is function signatures and comments, which is all that someone using the file needs to know. Inlining is a compiler optimisation - why should I have to make my header a mess to accommodate it?

Share this post


Link to post
Share on other sites
Here's a better version of the code with a little more functionality (it keeps a running score based on the difficulty and displays the game count, plus I think the code is cleaner)

main.h

#include <iostream>
#include <time.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctime>

using namespace std ;

// GLOBALS
const int ROW_WIDTH = 7 ;
const int MAX_DIFFICULTY = 20 ;
const int BASE_SCORE = 100 ;


typedef char Row[ROW_WIDTH] ;

struct gamestate{
Row game[MAX_DIFFICULTY] ;
Row key ;
int difficulty ;
int rowcount ;
int score ;
int totalScore ;
int gameCount ;
} ;

struct color{
char abbr ;
char longname[10] ;
int index ;
} ;

color colorCodes[ROW_WIDTH] ;



// FUNCTIONS
char keyTranslate(int i) ;
void setKey(Row keyRow) ;
bool checkPattern(const Row UserChoice, const Row key, Row returnRow ) ;
void updateGameState(const Row line, const int rowcount, const int score, gamestate *thisGame) ;
void writeToScreen(const gamestate *thisGame) ;
bool runGame(gamestate *thisGame) ;
void setDifficulty(gamestate *thisGame) ;
bool isWinner(const Row UserChoice, const Row key ) ;
void setTitle(char title[], char cScore[], char cGameCount[]) ;
void writeKey(const Row key);
void adjustScore(int &score, const gamestate *thisGame) ;
bool keepPlaying() ;
void initGame(gamestate *thisGame) ;
void resetGame(gamestate *thisGame) ;
void getNextGuess(Row &line) ;
void incrementRowCount(int &rowcount) ;
void initColors() ;
void updateGameCount(gamestate *thisGame) ;

//////////////////////////////////////////
inline COORD setPoint(const int x, const int y){
COORD point = {x,y} ;
return point ;
}

//////////////////////////////////////////
inline void writeInstructions(const int difficulty){
cout << "Enter " << ROW_WIDTH << " colors " ;
for (int i=1;i<ROW_WIDTH+1;i++)
cout << colorCodes.longname << " " ;
// cout << "(" << COLORS[0] << ")" ;
// for (int j=0;j<10;j++)
// cout << COLORS[j] << " " ;

cout << endl;
//R)ed (G)reen (B)lue (Y)ellow (W)hite (P)urple, (O)range " << endl ;
cout << "You have " << difficulty << " tries." << endl ;
}

//////////////////////////////////////////
inline bool isLoser(const int rowcount, int difficulty){
return rowcount == difficulty ;
}




main.cpp

#include "main.h"

// MAIN
void main()
{
char cScore[6] = "0", cCount[6] = "0", GAMETITLE[50] = "" ;
gamestate *thisGame = new gamestate ;

initGame(thisGame) ;

initColors() ;

do {
setTitle(GAMETITLE, itoa(thisGame->totalScore,cScore,10), itoa(thisGame->gameCount, cCount,10) ) ;

if ( !runGame(thisGame) ) break ;

system("cls") ;

} while (true) ;

delete thisGame ;

}

bool runGame(gamestate *thisGame) {

updateGameCount(thisGame) ;

cout << "Set difficulty (5 to 20)" << endl ;
setDifficulty(thisGame);

int i = 0, score = 0, rowcount = 0;

Row line ;

setKey(thisGame->key) ;
writeInstructions(thisGame->difficulty) ;

resetGame(thisGame) ;
do{
getNextGuess(line) ;

updateGameState(line, rowcount, score, thisGame) ;

incrementRowCount(rowcount) ;

if ( isWinner(line, thisGame->key ) ) {
MessageBox(NULL, "YOU WIN!!", "MasterMind!", MB_OK);
adjustScore(score, thisGame) ;
updateGameState(line, rowcount, score, thisGame) ;

break ;
}
else ;
{
system("cls") ;
}

if ( isLoser(rowcount, thisGame->difficulty) ){
cout << "YOU LOSE!!!" << endl ;
cout << "Correct answer: " ;
writeKey(thisGame->key) ;
break ;
}

writeToScreen(thisGame) ;
}
while (true) ;

if ( keepPlaying() ){
return true ;
}
else{
return false ;
}
}

//////////////////////////////////////////
void updateGameCount(gamestate *thisGame){
thisGame->gameCount++;
}

//////////////////////////////////////////
void initColors() {
colorCodes[0].abbr = 'R' ;
strcpy(colorCodes[0].longname, "(R)ed") ;

colorCodes[1].abbr = 'G' ;
strcpy(colorCodes[1].longname, "(G)reen") ;

colorCodes[2].abbr = 'B' ;
strcpy(colorCodes[2].longname, "(B)lue") ;

colorCodes[3].abbr = 'Y' ;
strcpy(colorCodes[3].longname, "(Y)ellow") ;

colorCodes[4].abbr = 'W' ;
strcpy(colorCodes[4].longname, "(W)hite") ;

colorCodes[5].abbr = 'P' ;
strcpy(colorCodes[5].longname, "(P)urple") ;

colorCodes[6].abbr = 'O' ;
strcpy(colorCodes[6].longname, "(O)range") ;

for (int i = 0; i< ROW_WIDTH; i++) {
colorCodes.index = i+1 ;
}
}

//////////////////////////////////////////
void getNextGuess(Row &line){
COORD point = setPoint(0, 3) ;
HANDLE stdCon = GetStdHandle(STD_OUTPUT_HANDLE);

SetConsoleCursorPosition(stdCon, point);
cin.get(line, ROW_WIDTH+1) ; cin.ignore(2000,'\n') ;
}

//////////////////////////////////////////
void incrementRowCount(int &rowcount){
rowcount++ ;
}

//////////////////////////////////////////
void resetGame(gamestate *thisGame) {

thisGame->rowcount = 0 ;
thisGame->score = 0 ;

}

//////////////////////////////////////////
void initGame(gamestate *thisGame){

thisGame->rowcount = 0 ;
thisGame->score = 0 ;
thisGame->gameCount = 1 ;
thisGame->totalScore = 0 ;
}

//////////////////////////////////////////
bool keepPlaying() {
char cResponse[3] = " " ;
cout << endl << "Play again? Y/N " ;
cin.get(cResponse,2) ; cin.ignore(2000,'\n') ;

return (cResponse[0] == 'Y' || cResponse[0] == 'y') ? true : false ;
}

//////////////////////////////////////////
void adjustScore(int &score, const gamestate *thisGame) {
score += BASE_SCORE ;
score += (( MAX_DIFFICULTY - thisGame->difficulty ) * 10) ; // add difficulty points
score -= thisGame->rowcount ; // subtract a point for each try
}

//////////////////////////////////////////
char keyTranslate(int i){

for (int j=0; j<ROW_WIDTH+1;j++){
if (i == colorCodes[j].index)
return colorCodes[j].abbr ;
}
return 'X' ;
}

//////////////////////////////////////////
void setKey(Row keyRow)
{
srand(time(NULL)) ; // seek should give us a random number all day
for (int i=0;i < ROW_WIDTH; i++) {
keyRow = ( keyTranslate( (rand() % ROW_WIDTH) + 1) ); }
}

//////////////////////////////////////////
void writeKey(const Row key) {
for (int i=0;i < ROW_WIDTH; i++)
cout << key << " " ;
cout << endl ;
}

//////////////////////////////////////////
bool isWinner(const Row UserChoice, const Row key ) {

bool win = false ;
int iNumCorrect = 0 ;

for (int i=0;i < ROW_WIDTH; i++) {
if ( UserChoice == key ){
// returnRow = key ;
iNumCorrect++ ;
}
}

if (iNumCorrect == ROW_WIDTH)
win = true ;

return win ;
}

//////////////////////////////////////////
void updateGameState(const Row line, const int rowcount, const int score, gamestate *thisGame){
if (line){
for (int i=0;i<ROW_WIDTH;i++){
thisGame->game[rowcount] = line ;
}
}

thisGame->rowcount = rowcount ;
thisGame->score = score ;
thisGame->totalScore += thisGame->score ;

}

//////////////////////////////////////////
void setTitle(char title[], char cScore[], char cGameCount[]){
char GAMETITLE[50] = "MasterMind! score = " ;
strcat(GAMETITLE, cScore) ;
strcat(GAMETITLE, " Game #") ;
strcat(GAMETITLE, cGameCount) ;
SetConsoleTitle(GAMETITLE);
}

//////////////////////////////////////////
void setDifficulty(gamestate *thisGame){
char tries[3] = " " ;

cin.get(tries,2) ;cin.ignore(2000, '\n') ;
thisGame->difficulty = atoi(tries) ;

if ( thisGame->difficulty < 5 ) thisGame->difficulty = 5 ;
if ( thisGame->difficulty > 20 ) thisGame->difficulty = 20 ;

system("cls");

//cin.get() ;
}

//////////////////////////////////////////
void writeToScreen(const gamestate *thisGame){

writeInstructions(thisGame->difficulty) ;

HANDLE stdCon = GetStdHandle(STD_OUTPUT_HANDLE);
COORD point ;

const int RED_ORANGE = 12 ;

for (int i=0;i<thisGame->rowcount+1;i++)
{
point = setPoint(30,i + 3);
SetConsoleCursorPosition(stdCon, point);

for (int j=0;j<ROW_WIDTH;j++){
switch (thisGame->game[j]){
case 'R': SetConsoleTextAttribute(stdCon,FOREGROUND_RED | FOREGROUND_INTENSITY);
break ;
case 'G': SetConsoleTextAttribute(stdCon,FOREGROUND_GREEN | FOREGROUND_INTENSITY);
break;
case 'B': SetConsoleTextAttribute(stdCon,FOREGROUND_BLUE | FOREGROUND_INTENSITY);
break;
case 'P': SetConsoleTextAttribute(stdCon,FOREGROUND_BLUE | FOREGROUND_RED);
break;
case 'O': SetConsoleTextAttribute(stdCon, RED_ORANGE );
break;
case 'Y': SetConsoleTextAttribute(stdCon,FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY );
break;
case 'W': SetConsoleTextAttribute(stdCon,FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);
break;
default: SetConsoleTextAttribute(stdCon,FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);
}

cout << thisGame->game[j] << " " ; }

cout << " " ;

SetConsoleTextAttribute(stdCon,FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);

for (int j=0;j<ROW_WIDTH;j++){
if ( thisGame->game[j] == thisGame->key[j] )
cout << 'Y' << " " ;
else
cout << 'N' << " " ;

}
cout << endl;
}
}


Share this post


Link to post
Share on other sites
That is much better. Now I don't want to stab myself in the face :D

As a precaution, memset your char arrays with 0s. This will prevent you from leaving the null terminator off. Also, whenever copying, check the bounds. If you don't, you'll eventually code something that will cause a buffer overflow and wreak some havoc :D.

Share this post


Link to post
Share on other sites
Quote:
Original post by Maega
That is much better. Now I don't want to stab myself in the face :D


My good deed for the day :D

Quote:

As a precaution, memset your char arrays with 0s. This will prevent you from leaving the null terminator off.


I'm not sure I understand. Do you mean at initialization like this?

char cResponse[3] = " " ;
memset(cResponse, 0, 3) ;
puts(cResponse) ;



Quote:

Also, whenever copying, check the bounds. If you don't, you'll eventually code something that will cause a buffer overflow and wreak some havoc :D.


Does this mean checking if the sizeof() the second array isn't bigger than the sizeof() the first array?

Share this post


Link to post
Share on other sites
memset(array, 0, sizeof(array)) is better. You'll make less mistakes that way (if you miscount or something).

As for the second question, make sure the destination is big enough for the source to fit in.

Share this post


Link to post
Share on other sites
Next mission is to create a windowed version and use bitmaps for the pieces instead of text. I'll post that one when I'm done. :)

I wish there was a way to make it harder, though. Any ideas? Perhaps a time limit? That might be a cool project to get to learn about dealing with time.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!