MasterMind source code. Any criticism welcome.

Started by
9 comments, last by GameDev.net 19 years, 6 months ago
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 ;

}



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 :(.
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.
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?
-- Single player is masturbation.
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 ;// GLOBALSconst 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] ;// FUNCTIONSchar 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"// MAINvoid 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;		}}
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.

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?

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.
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.
About void main :)

This topic is closed to new replies.

Advertisement