Advertisement

number of matching chars in 2 strings

Started by November 18, 2018 11:20 AM
10 comments, last by rmsgrey 6 years ago

I'm trying to make a terminal hacking game the same as fallout, but I'm struggling to get the number of characters matching between the user input and the correct password. I've tried searching but so far I haven't found any simple solutions. Please can someone suggest a way? I've also included the full class code below for any tips on improving it

 


void computer::terminal::create_pass()
{
	int lower, upper;
	switch (diff)
	{
	case difficulty::easy: lower = 2; upper = 4; break;
	case difficulty::medium: lower = 5; upper = 7; break;
	case difficulty::hard: lower = 8; upper = 12; break;
	}
	int a = rand_eng(lower, upper);
	fill_vec(a);
	this->fill_addresses(a);
	this->fill_characters();
	int vec_size = passwds.size();
	int b = rand_eng(0, vec_size-1);
	answer = passwds[b];
	return;
}

void computer::terminal::fill_addresses(int amount)
{
	int ran{};
	std::string text{};
	for (int i = 0; i <= amount; i++)
	{
		ran = rand_eng(100, 999);
		text = "0xF" + std::to_string(ran);

		bool dupe{ false };
		std::vector<std::string>::iterator it = adds.begin();
		while (it != adds.end())
		{
			if (*it == text)
			{
				dupe = true;
				break;
			}
			++it;
		}
		if (dupe == false)
		{
			adds.push_back(text);
		}
		else
			this->fill_addresses(amount);
	}
	return;
}

void computer::terminal::fill_characters()
{
	std::vector<char> temp = { '?', '/', '\\', '-', '_', '[', ']', '$', '%', '&', '*', '"', '<', '#', '>' };
	int ran{};
	for (int i = 0; i <= 14; i++)
	{
		ran = rand_eng(0, 14);
		chars.push_back(temp[ran]);
	}
	return;
}

void computer::terminal::fill_vec(int amount)
{
	std::vector<std::string> temp{ "biscuit", "jupiter", "mars", "wellington", "statue", "radio", "sponge", "universe", "capture", "penny", "thunder", "hurricane", "knight", "bishop", "queen", "king"};
	
	int r{ 0 };
	for (int i = 0; i <= amount; i++)
	{
		bool dupe{ false };
		r = rand_eng(0, 15);
		std::string s = temp[r];

		auto it = passwds.begin();
		while (it != passwds.end())
		{
			if (*it == s)
			{
				dupe = true;
				break;
			}
			++it;
		}
		if (dupe == false)
		{
			passwds.push_back(s);
		}
	}
	return;
}

void computer::terminal::login(bool first_login)
{
	CLEAR;
	std::cout << "ROBCO INDUSTRIES (TM) TERMLINK PROTOCOL\nENTER PASSWORD NOW\n\n" << tries << " ATTEMPT(S) LEFT: ";
	for (int i = 1; i <= tries; i++) { std::cout << "|  "; } std::cout << "\n\n";

	std::random_device shuffle_device;

	for (int i = 0; i != passwds.size(); ++i)
	{
		print(adds[i]); 
		std::cout << '\t';

		int before = rand_eng(0, 10);
		int end = rand_eng(0, 10);

		std::shuffle(chars.begin(), chars.end(), std::mt19937(shuffle_device()));
		for (int i = 0; i <= before; i++)
		{
			std::cout << chars[i];
		}

		print(passwds[i]);
		if (answer == passwds[i])
			print("THIS IS THE ANSWER");

		std::shuffle(chars.begin(), chars.end(), std::mt19937(shuffle_device()));
		for (int i = 0; i <= end; i++)
		{
			std::cout << chars[i];
		}

		std::cout << '\n';
	}
	print("\n\nCHARACTERS MATCH: "); std::cout << chars_correct;
	if (first_login)
		guess();
	else
		return;
}

bool computer::terminal::guess_valid(const std::string& guess)
{
	auto validity_check = [&guess](const std::string& pw) { return guess == pw; };
	return std::any_of(passwds.begin(), passwds.end(), validity_check);
}

void computer::terminal::check_valid(std::string& s)
{
	while (!guess_valid(s))
	{
		print("\n\nINVALID PASSWORD");
		std::this_thread::sleep_for(std::chrono::seconds(1));
		std::cout << "\n\n> ";
		std::cin.clear();
		std::getline(std::cin, s);
	}
}

void computer::terminal::guess()
{
	std::cout << "\n\n> ";
	std::string s{};
	std::cin.ignore();
	std::getline(std::cin, s);

	check_valid(s);

	while (tries > 0)
	{
		if (pass_correct(s))
		{
			print("\n\nPASSWORD ACCEPTED");
			std::this_thread::sleep_for(std::chrono::seconds(1));
			return;
		}
		else
		{
			print("\n\nWRONG PASSWORD");
			std::this_thread::sleep_for(std::chrono::seconds(1));

			std::vector<std::string>::iterator it = passwds.begin();
			while (it != passwds.end())
			{
				if (*it == s)
				{
					passwds.erase(it);
					break;
				}
				++it;
			}
			tries--;
			std::cout << "\n\n";
			login(false);
		}
		std::cout << "\n\n> ";
		std::cin.clear();
		std::getline(std::cin, s);
		check_valid(s);
	}

	CLEAR;
	print("\n\nTERMINAL LOCKED. PLEASE CONTACT YOUR SYSTEM ADMINISTRATOR");
	std::this_thread::sleep_for(std::chrono::seconds(3));
}

 

This doesn't address your question directly, but just out of curiosity, what are 'print' and 'CLEAR'?

Advertisement

just functions to clear the screen and print characters slower like an old terminal

 

#define CLEAR system("cls")

void print(std::string s)
{
    for (auto& c : s)
    {
        std::cout << c;
        std::this_thread::sleep_for(std::chrono::milliseconds(5));
    }
}

Regarding your question about matching characters, for me at least it would take some time to analyze your code as it's presented. Maybe someone else will comment on that, but meanwhile here's a couple suggestions: use the debugger to step through your code to figure out what it's doing and where it's going wrong (assuming it's going wrong), and/or post a small, self-contained example that includes only the functionality of interest (that would make it easier for others to analyze the code and maybe try it out themselves).

Quote

I've also included the full class code below for any tips on improving it

Here's a few suggestions. Issues of coding style and convention can sometimes prompt strong reactions from people, so let me clarify that I'm only offering my personal opinions in response to your explicit request for feedback. Also, I'm going to mention some best practices and conventions here without going into great detail on the reasoning behind them (otherwise this post would probably be excessively long). I'm happy to elaborate on anything though, and you can find plenty of information on these topics elsewhere.

- In C++, the use of macros is generally discouraged unless it's the only solution or arguably the optimal solution. (At or near the top of the list of reasons for this is that macros don't respect scope.) In your code for example, for the 'clear' functionality, in C++ you'd typically use a function for that rather than a macro.

- If you haven't already, you might look further into the use of 'const' in C++. Opinions differ as to how liberally 'const' should be used. Personally I prefer to use it almost everywhere possible, including function arguments and local variables where mutability isn't needed. In any case, I'd suggest investigating that further and coming to your own conclusions as to what conventions and practices you prefer (again, if you haven't already).

- It looks like you're using pre-increment for loop counters in some cases and post-increment in others. Although the chances of the choice having any practical impact in the given context are probably about zero, there are arguments for pre-increment being preferable unless post-increment is needed. In any case, I'd suggest using one or the other exclusively, just for clarity and consistency.

- Some would argue that braces should always be used for control structures even when there's only one statement.

I see a few other little things, but the above items are most significant, I think. Again, you can find plenty of discussion of all these topics online and elsewhere if you're interested in investigating them further.

thanks Zakwayda, I appreciate the feedback very much.

Regarding my question, there is no code actually doing the check yet, as I'm unsure as to how to go about doing it.

 

Basiaclly if the user input is 'bars' and the correct answer is 'mars' I need the program to say 3 characters match, but I don't know how best to write a function to count this, although thinking about it again now, possibly I'll have a look at passing a function the above in 2 vectors of chars, iterate through the answer for each character in the user input, and count the matches... I've probably just answered my own question

The answer will depend on a few specifics. For these questions, assume the correct phrase is "mars".

What is the expected result if the input is "sram"? (All input characters are correct, but order is not correct.)

What is the expected result if the input is "mar"? (All input characters are correct, but correct phrase not solved.)

What is the expected result if the input is "marss", "mmars", etc.? (All characters from the correct phrase are present and in the correct order -- but with additional characters.)

What is the expected result if the input is "MARS"? (Case sensitivity.)

 

An example solution could be something like (pseudo):


auto minCount = min(correctPhrase.count(), guessedPhrase.count());
int numMatching = 0;
for (int i = 0; i < minCount; i++)
{
    if (correctPhrase[i] == guessedPhrase[i])
    {
        ++numMatching;
    }
}

But like mentioned, it depends on the specifics of the expected results.

Hello to all my stalkers.

Advertisement
3 hours ago, ICanC said:

Basiaclly if the user input is 'bars' and the correct answer is 'mars' I need the program to say 3 characters match, but I don't know how best to write a function to count this,

"longest substring match" gave me https://en.wikipedia.org/wiki/Longest_common_substring_problem as first hit. This could however be overkill for your application,

There are also algorithms for fuzzy string matching, ie matching strings, but there may be 'problems' in it, like changed or missing characters, Eghttps://en.wikipedia.org/wiki/Levenshtein_distance  is one example.

Fallout 3 and 4's hacking comparison is EXTREMELY simple.  Comparisons are done on the same character position in both words, and all words in one 'screen' are the same length.  Word length is increased when the terminal is more difficult, and decreased if the player's hacking skill is high.


int same = 0;
for (int i=0; i<wordLength; ++i)
{
    if (wordA[i] == wordB[i])
    {
        same++;
    }
}

 

thanks all, slightly different to fallout, I just wanted to get the number of letters that exist in the answer, irrespective of where they are.

 

I came up with this, which seems to work, but is probably over complicated :

 


void computer::terminal::get_chars_correct(std::string guess)
{
	int count{ 0 };
	std::vector<char> pass;
	for (char c : answer)
	{
		pass.push_back(c);
	}
	auto check_match = [](const char pc, const char c) {return pc == c; };
	std::vector<char> counted{};
	std::vector<char>::iterator it;
	for (char c : guess)
	{
		it = pass.begin();
		while (it != pass.end())
		{
			bool b = check_match(*it, c);
			if (b)
			{
				if (std::find(counted.begin(), counted.end(), c) == counted.end()) //check that this letter hasn't already been accounted for
				{
					++count;
					counted.push_back(c);
				}
			}
			++it;
		}
	}
	chars_correct = count;
}

 

If you have a small, known set of allowed characters, you can use an array indexed by character as an unordered map:

 


// The following assumes that ALL chars are >= 'a' and <= 'z'.
int countPerChar[26] = {0};
for (char c : answer)
{
    countPerChar[c-'a']++;
}

int count = 0;
for (char c : guess)
{
    if (countPerChar[c-'a'] > 0)
    {
        countPerChar[c-'a']--;
        count++;
    }
}

 

This topic is closed to new replies.

Advertisement