[SOLVED] Unlocking a Safe Code

Dan1

Member
Hello!

I'm trying to make code that will check a 4-digit numerical combination that you put in to a safe and give you a clue once you've entered the combination.

What I am trying to do is make the return show an "X" if you have one of the digits in the right place, then an "O" if you have a digit correct but in the wrong position

The code I have works if all 4 digits in the correct combination are different but...
The problem I'm having, if the right combination contains more than one of the same digit (Eg: "0141" then it doesn't return the right outcome when you use the a different quantity of digits (Using the example above, if I guess "1440", it'll give the outcome "XOOO", where it should return "XOO" as the second "4" is not in the first combination, so it's not correct and shouldn't return an "X" or "O")

Code:
global.gu = your guess
global.ra = right answer/combination
global.rp = response
global.rc = response counter

    if global.gu[1] = global.ra[1] then
    {
    global.rp[global.rc] = "X"
    global.gr[1]=1
    global.rc+=1
    }
    if global.gu[2] = global.ra[2] then
    {
    global.rp[global.rc] = "X"
    global.gr[2]=1
    global.rc+=1
    }
    if global.gu[3] = global.ra[3] then
    {
    global.rp[global.rc] = "X"
    global.gr[3]=1
    global.rc+=1
    }
    if global.gu[4] = global.ra[4] then
    {
    global.rp[global.rc] = "X"
    global.gr[4]=1
    global.rc+=1
    }
    
    if global.gu[1] != global.ra[1] then
    {
        if global.gu[1] = global.ra[2] && global.gr[2] != 1 then global.gr[2] = 1
        if global.gu[1] = global.ra[3] && global.gr[3] != 1 then global.gr[3] = 1
        if global.gu[1] = global.ra[4] && global.gr[4] != 1 then global.gr[4] = 1
        if global.gu[1] = global.ra[2] || global.gu[1] = global.ra[3] || global.gu[1] = global.ra[4] then
        {
        global.rp[global.rc] = "O"
        global.rc+=1
        }
    }
    
    if global.gu[2] != global.ra[2] then
    {
        if global.gu[2] = global.ra[1] && global.gr[1] != 1 then global.gr[1] = 1
        if global.gu[2] = global.ra[3] && global.gr[3] != 1 then global.gr[3] = 1
        if global.gu[2] = global.ra[4] && global.gr[4] != 1 then global.gr[4] = 1
        if global.gu[2] = global.ra[1] || global.gu[2] = global.ra[3] || global.gu[2] = global.ra[4] then
        {
        global.rp[global.rc] = "O"
        global.rc+=1
        }
    }
    
    if global.gu[3] != global.ra[3] then
    {
        if global.gu[3] = global.ra[2] && global.gr[2] != 1 then global.gr[2] = 1
        if global.gu[3] = global.ra[1] && global.gr[1] != 1 then global.gr[1] = 1
        if global.gu[3] = global.ra[4] && global.gr[4] != 1 then global.gr[4] = 1
        if global.gu[3] = global.ra[2] || global.gu[3] = global.ra[1] || global.gu[3] = global.ra[4] then
        {
        global.rp[global.rc] = "O"
        global.rc+=1
        }
    }
    
    if global.gu[4] != global.ra[4] then
    {
        if global.gu[4] = global.ra[2] && global.gr[2] != 1 then global.gr[2] = 1
        if global.gu[4] = global.ra[3] && global.gr[3] != 1 then global.gr[3] = 1
        if global.gu[4] = global.ra[1] && global.gr[1] != 1 then global.gr[1] = 1
        if global.gu[4] = global.ra[2] || global.gu[4] = global.ra[3] || global.gu[4] = global.ra[1] then
        {
        global.rp[global.rc] = "O"
        global.rc+=1
        }
}

Any thoughts on how to get this to return the correct response?

Much appreciated,
Dan :)
 

Hyomoto

Member
I'm just going to flat out say, the easiest way to do this would be to break your combination up into an array, or separate variables but before I get into that I'll show you a way you can achieve this with just the two numbers. You need to set up a variable length_of_combination to make this script work to let it know how many digits it should be checking:
Code:
var _combination = 1234, _entered = 0231

result = 0;
for ( var _index = 0; _index < length_of_combination; _index++ ) {
  if ( _combination div power( 10, _index ) mod 10 ) == ( _entered div power( 10, _index ) mod 10 ) { result |= 1 << _index }
 
}
This produces a bit mask that shows which digits are correct in the current combination. You can prove this returns the correct results by drawing them to the screen, and you can adjust the number of digits that are in the combination by setting the appropriate value. This is useful because you can simply compare the two numbers to see if they match to see if the combination entered was correct, and if you want to decode the bit mask it's fairly simple as well.
Code:
for ( var _i = 0; _i < length_of_combination; _i++ ) {
    if result & ( 1 << _i ) > 0 { draw_text( 128 - _i * 32, 128, "X" ) }
    else { draw_text( 64 + _i * 32, 128, "_" ) }

}
While I'm guessing you may not be comfortable, or familiar with bit manipulation, this is a simple bit masking technique that has widespread and valuable uses. I strongly suggest you read https://www.yoyogames.com/blog/46 and see if you can figure out what I've done here.

That said, your use of an array is fine, but I'm not sure what most of your code is supposed to be doing. So let's see if we can't simplify it into something more manageable that gets the results you are looking for. I'm going to use three arrays: one to store the entered combination, one to store the combination itself, and one to keep track of the results:
Code:
for ( var _i = 0; _i < array_length_1d( combination ); _i++ ) {
  if combination[ _i ] = entered[ _i ] { result[ _i ] = "X" } else { result[ _i ] = "O" }

}
Not bad, eh? A little easier, a lot more manageable and if there's a bug somewhere it's probably really easy to find in three lines of code. We are literally just checking if each digit in our combination matches what the player entered, and then setting the result to either X or O. This is probably little closer to what you are looking for and it's probably the better way to accomplish whatever you are doing. However, when it comes to combinations, learning bit masking is a very helpful tool so I have included that example here. Have fun and keep coding!
 
Last edited:

TheouAegis

Member
Code:
for ( var _i = 0; _i < array_length_1d( combination ); _i++ ) {
if combination[ _i ] = entered[ _i ] { result[ _i ] = "X" } else { result[ _i ] = "O" }

}
Not quite the same. O is correct digit, wrong location.

The first thing you'd want to do is find all the correct matches.
Code:
var _s1 = array_length_1d(combination);
for ( var _i = 0; _i < _s; _i++ ) {
  if combination[ _i ] = entered[ _i ] { result[ _i ] = "X" } 
}
The reason you'd run this separately from the other checks is because you're going to need to loop through arrays many times for this {correct/almost/wrong} scheme.

Then the next thing you'd need is to find the ones that were in the combination but not in the right place, but you need to make sure duplicates aren't counted.
Code:
var counted; counted[_s-1] =  0;
for(var _i=0; _i<_s; _i++) {
    if result[_i] == "X" { counted[_i]=1; continue; }
    for(var _n=0; _n<_s; _n++) {
        if _n == _i continue;
        if result[_n] == "X" { counted[_n]=1; continue; }
        if combination[_n] == entered[_i] {
            result[_i] = "O";
            counted[_n] = 1;
            continue;
        }
    }
}

I might have missed something, but my gf is hounding me to get ready for work, so this is all I could come up with in a rush.
 

Hyomoto

Member
Hmm, I misread what you are trying to accomplish. In fact, upon re-reading it I have trouble visualizing what it's supposed to be doing. I'm guessing this is a puzzle game and you are trying to guess the combination based on these X's and O's? I think there's not enough information about this to really get a good conclusion for what you want, what about the combination "0010" if the player enters "0001" should I get "OOXX" or "OOX"? I have two correct digits, and two wrong digits that should be somewhere else. Then again, I also have three 0's, so should I get, "OX"? And most importantly, what order should I present these in? X then O or O then X?

Maybe I'm over thinking it here, but I'd guess that's why your code isn't working the way you want it to, you may just not have a definitive solution to your problem.
 

Bukmand

Member
Well I didn't try it but here:
Code:
Code[0]="0141"; //Max result

Code[1]="0";
Code[2]="1";
Code[3]="4";
Code[4]="1";

for(i=0; i<5; i++) {
   if string_char_at(Code[0], i)=Code[i] {
      Pass=true;
   } else {
      Pass=false;
   }
}
if Pass=true { Answer="X" } else Answer="O"
draw_text(100, 100, Answer);
I don't know how you insert a text but you get the idea
 
V

VansiusProductions

Guest
I won't write out the exact code on how to do this, but I think what you should do is that when you're checking for O, make an if statement so that if that digit is being represented as X already, don't check the O. This is because it's useless as X means right digit and right place, as O means right digit only. And if the digit is being represented as O, but can also be represented by X (like in your example), than set it to X.
 

Dan1

Member
Hey Hyomoto & Bukmand,

Thanks for that, the coding is much easier there but what you're returning there is an "X" if the number is right and an "O" if the number is wrong - is there any way I can change this so that the "X" represents that a number is in the correct place in the combination, an "O" means the number is in the combination but in the incorrect position and it would simply not add to the result if the number is not in the combination at all?

And VansiusProductions,

That's what I've tried to do with the code above using the global.gr variable (I think) but it hasn't seemed to have worked

Thanks for all your help as well :)
 

Dan1

Member
Hey TheouAegis,

I did try your code like this:

Code:
var _s1 = array_length_1d(global.ra);
for ( var _i = 0; _i < _s; _i++ ) {
  if global.ra[ _i ] = global.gu[ _i ] { global.rp[ _i ] = "X" }
}

var counted; counted[_s-1] =  0;
for(var _i=0; _i<_s; _i++) {
    if global.rp[_i] == "X" { counted[_i]=1; continue; }
    for(var _n=0; _n<_s; _n++) {
        if _n == _i continue;
        if global.rp[_n] == "X" { counted[_n]=1; continue; }
        if global.ra[_n] == global.gu[_i] {
            global.rp[_i] = "O";
            counted[_n] = 1;
            continue;
        }
    }
}
But it returned this error:

Code:
___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Key Press Event for <Space> Key
for object ob_Controller:

Variable ob_Controller._s(100003, -2147483648) not set before reading it.
 at gml_Object_ob_Controller_KeyPressed_SPACE_1 (line 2) - for ( var _i = 0; _i < _s; _i++ ) {
############################################################################################
It does look like it might work though, any idea how to fix this?
 
Last edited:

TheouAegis

Member
Haha i thought you might catch the error and remove it.

Change var _s1=0; to var _s=0; and that should take care of that.
 

Dan1

Member
Most of my coding skills are fairly basic haha - I did think that was out of place but went with it anyway!
I've just removed it now and it still seems to be getting some wrong though? For example, with the combination being "0141" - it returns "OOOX" on a guess "4411", when really, it should return "OOX" - any ideas?
I really appreciate your help :)
 

TheouAegis

Member
@Dan1 Try this. I had my doubts about the last conditional when I wrote up the code and I still doubt it was right. I think I got my variables swapped.

Code:
var _s1 = array_length_1d(global.ra);
for ( var _i = 0; _i < _s; _i++ ) {
  if global.ra[ _i ] = global.gu[ _i ] { global.rp[ _i ] = "X" }
}

var counted; counted[_s-1] =  0;
for(var _i=0; _i<_s; _i++) {
    if global.rp[_i] == "X" { counted[_i]=1; continue; }
    for(var _n=0; _n<_s; _n++) {
        if _n == _i continue;
        if global.rp[_n] == "X" { counted[_n]=1; continue; }
        if global.ra[_i] == global.gu[_n] {
            global.rp[_i] = "O";
            counted[_n] = 1;
            continue;
        }
    }
}
 

Dan1

Member
Thanks @TheouAegis - but that's still returning the wrong result, when the right combination is "0141" and you enter "1440", it's giving back XOOO when it should return XOO as one of those "4"'s isn't in the combination :/
 

TheouAegis

Member
Code:
var _s = array_length_1d(global.ra);
for ( var _i = 0; _i < _s; _i++ ) {
  if global.ra[ _i ] = global.gu[ _i ] { global.rp[ _i ] = "X" }
}

var counted; counted[_s-1] =  0;
for(var _i=0; _i<_s; _i++) {
    if global.rp[_i] == "X" { counted[_i]=1; continue; }
    for(var _n=0; _n<_s; _n++) {
        if _n == _i continue;
        if global.rp[_n] == "X" { counted[_n]=1; continue; }
        if global.ra[_n] == global.gu[_i] && !counted[_n] {
            global.rp[_i] = "O";
            counted[_n] = 1;
            continue;
        }
    }
}

Forgot to check if a pass character was checked. Sorry, really drunk right now.
 
Last edited:

Dan1

Member
Hope you enjoyed your drinking @TheouAegis! Haha :)
I think we're getting closer now but there's still one issue, using the same example, with 0141 being the right combination, if I guess 1410, it returns "OOO", not "OOOO"?
Sorry to be such a pain with this but I really appreciate all of your help in this!
 

Hyomoto

Member
Alright, I'm back. After testing your case, I've come to the conclusion that you need two pieces of information: what digits are in the right spot, and what digits are in the combination but not in the right spot. Am I right so far? Here is a simple solution. I'm going to use two arrays, actual and enter to hold the actual combination and the entered combination, and an integer, number_of_digits, to handle variable combination length.

This block of code is going to create a bit mask of all the unique numbers in the combination. If you are not familiar with bit operations, @Mike Dailly, one of the GM creators wrote an excellent tech blog on the subject. Otherwise you can hit the 'I believe' button and chalk this up to pure ****ing magic. This will allow us to quickly check if a number is in the combination without cycling through each number, so it's a useful convenience. Next we do what everyone else has suggested and use a loop to compare the entered combination to the actual combination and set up our clue. If the number doesn't match, it will compare it to our _mask and if the digit is in the combination, it will add it to the 'right' pool which is then used to add that number of X's to the end of the combo.
Code:
var _mask = 0, _right = 0, _string = "";
// this code will set up a mask of our digits
for ( var _i = 0; _i < number_of_digits; _i++ ) {
    _mask |= 1 << actual[ _i ];

}
// this code gives us our string
for ( var _i = 0; _i < number_of_digits; _i++ ) {
    if actual[ _i ] == enter[ _i ] { _string += "X" }
    else if ( 1<< enter[ _i ] ) & _mask > 0 { _right += 1 }
 
}
_string += string_repeat( "O", _right );
In your example of 0141, if you input 1410 it will output, "OOOO". If you input 1440 you will get back XOOO. Come to think of it, I have an idea. Let me get back to you.
 
Last edited:

TheouAegis

Member
I have an idea for an alternative, because I'm not sure why this isn't printing out all 4 characters (just looked over it again). On my way to work, but I may post something on my lunch break. It's not much different, just an alternate way of checking all the digits the second time around.
 

Hyomoto

Member
Here. You need to set up two arrays, actual and enter for the actual combination and the entered combination, and one value, number_of_digits for the length of the combination.
Code:
var _totals = 0, _mask = 0, _right = 0;
_totals[ 9 ] = 0;

my_string = "";
// this counts our digits and builds a number mask
for ( var _i = 0; _i < number_of_digits; _i++ ) {
    _totals[ actual[ _i ] ] += 1;
    _mask |= 1 << actual[ _i ];

}
// this code gives us our string
for ( var _i = 0; _i < number_of_digits; _i++ ) {
    var _index    = enter[ _i ];
    var _bit    = 1 << _index;
      
    if actual[ _i ] == enter[ _i ] {
        _totals[ _index ] -= 1;
        my_string += "X";
    } else if ( 1 << enter[ _i ] ) & _mask > 0 {
        if _totals[ _index ] > 0 { _right += 1; _totals[ _index ] -= 1 }
          
    }

}
my_string += string_repeat( "O", _right );
The way it works is first it makes a bitmask of all the digits in the combination for easy comparisons and counts the number of times each digit appears. Then it moves onto checking. If your digit is correct, it reduces that digit's total by 1 and adds an X to your result. If it isn't, it checks if that number is in the combination, and if the number of that digit in the combination is still greater than zero, it adds one to the number of '_right' digits. Once checking is complete, it uses string_repeat to add O's to the combination so that you get X's, then O's in your final string. I'll be honest, you've been kind of inconsistent about what should return what, but I think those were just mistakes on your part. This, however, should do exactly what you want. If not, I'm going to need you to put together a truth table showing what inputs should result in what outputs before I can even consider taking another crack at it.
 

TheouAegis

Member
Well, OP could be asking how to have it yield a cryptic clue, such as if all it showed you was "X" you'd know you got 1 right but which one; or if it showed you "OX" you'd know the first one was wrong but you wouldn't know if it was in the wrong place or completely wrong, and likewise you'd know either 2nd, 3rd or 4th was right but not which, and if it ended up being the 4th was right, then you wouldn't even know if it was the 1st, 2nd or 3rd which was just in the wrong place. That seems pretty pointless, to me.

Or he could be asking for the more logical " XOO" style where nothing would mean it was completely wrong but at least you'd know which one was completely wrong.
 

TheouAegis

Member
Hope you enjoyed your drinking @TheouAegis! Haha :)
I think we're getting closer now but there's still one issue, using the same example, with 0141 being the right combination, if I guess 1410, it returns "OOO", not "OOOO"?
Sorry to be such a pain with this but I really appreciate all of your help in this!

*facepalm*
Remove the if _n == _i continue; line from my code. It's making the 1st character only show up if it's correct, not if it's in the wrong position.
 

Dan1

Member
Well the idea being you would have multiple guesses and the clues would mean you'd have to use a bit of logic using the process of elimination - an "X" would indicate that one of the numbers you've guessed is correct AND in the correct position and an "O" would indicate that one of the numbers you've guessed is in the combination but not in the correct position - the letters would not show which of the numbers was in the combination or the right place, the idea being that you'd have to narrow it down and try to unlock the safe in as few moves or as quickly as possible - I hope that helps

I'll just try removing the "if _n == _i continue;" line now and see if that works :)
 

Dan1

Member
It seems that removing if _n == _i continue almost works, but still doesn't return "OOOO" when all 4 numbers are correct but all 4 numbers are in the wrong position - with the "0141" example, entering "1410" should return "OOOO" as all 4 numbers are correct but they are all in the wrong order, but this still returns "OOO"

@Hyomoto - I've tried to explain what I'm hoping to get this code to do and I'm sorry if it's not been clear but if you'd like me to explain any part of it I can
 

Hyomoto

Member
All I can say is you can try my code. I'll upload the project I used to make/test it, and it should return what you wanted based on what you posted (you might recognize the test combo). I built it so that it will return exactly what you wanted based on your previous replies but if it doesn't for some reason, I'll need a truth table to 'fix' it.

Here's the project

It's pretty self-explanatory. Use the left and right arrows to select a digit, press up and down to change it. The correct combination is on the top, your entered combination on the bottom, and obviously it shows the string it returns.
 
Last edited:

Dan1

Member
That works perfectly! And that's using the latest code you'd provided?

Code:
var _totals = 0, _mask = 0, _right = 0;
_totals[ 9 ] = 0;

my_string = "";
// this counts our digits and builds a number mask
for ( var _i = 0; _i < number_of_digits; _i++ ) {
   _totals[ actual[ _i ] ] += 1;
    _mask |= 1 << actual[ _i ];

}
// this code gives us our string
for ( var _i = 0; _i < number_of_digits; _i++ ) {
    var _index    = enter[ _i ];
    var _bit    = 1 << _index;
     
    if actual[ _i ] == enter[ _i ] {
        _totals[ _index ] -= 1;
        my_string += "X";
    } else if ( 1 << enter[ _i ] ) & _mask > 0 {
        if _totals[ _index ] > 0 { _right += 1; _totals[ _index ] -= 1 }
         
    }

}
my_string += string_repeat( "O", _right );
 

Dan1

Member
Ah! Fantastic! It's working!!!
Massive thank you to you both for going to such trouble to help me with this!!!
:D
 
Top