commit c5e00c07ab89bf743020c23806e2d1f4e48c9953 Author: Daniel Tsvetkov Date: Sun Dec 30 10:31:00 2018 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f377c56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +venv +.idea diff --git a/main.py b/main.py new file mode 100644 index 0000000..4c728cc --- /dev/null +++ b/main.py @@ -0,0 +1,115 @@ +""" +Naive cows and bulls brute force implementation. +Generates all possible numbers in a pool +""" +import random + + +def number_has_repeating_or_zero(number): + """ + Check the digits in the number and if any is repeating or 0, return True, False otherwise + """ + str_number = str(number) + used_numbers = [] + for digit in str_number: + if digit == '0' or digit in used_numbers: + return True + used_numbers += [digit] + return False + + +def count_cows(number, guess): + """ + cows are how many digits in the guess are in the number but *NOT* correct position + """ + cows = 0 + for pos, digit in enumerate(number): + if digit in guess and digit != guess[pos]: + cows += 1 + return cows + + +def count_bulls(number, guess): + """ + bulls are how many digits in the guess are the same and in the correct position + """ + bulls = 0 + for pos, digit in enumerate(number): + if digit == guess[pos]: + bulls += 1 + return bulls + + +def is_number_guess_cb_match(number, guess, cows, bulls): + """ + The crux of the algo - the number and guess combo must match + the number of cows and bulls + """ + guess_cows = count_cows(number, guess) + guess_bulls = count_bulls(number, guess) + return guess_cows == cows and guess_bulls == bulls + + +def main(): + """ + Generate all non-digit-repeating possible numbers between 1111 and 9999 excluding 0 + that means that the first possible is 1234 and last is 9876. + Then chose a random number from the pool as the first guess, wait for input for cows and bulls, + prune the pool from impossible matches and repeat until 4 bulls + """ + number_pool = [i for i in range(1234, 9877) if not number_has_repeating_or_zero(i)] + assert len(number_pool) == 3024 # 9*8*7*6 = 3024 + + # input loop + done = False + print("Write cows and bulls as two digits separated by blank space, e.g. 2 0") + while not done: + print("There are {} options left. Press c to see them".format(len(number_pool))) + guess = random.choice(number_pool) + # input verification loop + while True: + guess_result = input("{} ?".format(guess)) + guess_result = guess_result.strip().strip() + if guess_result in ['c', 'C']: + print(number_pool) + continue + # input verification - must be 2 digits between 0 and 4 + splitted_guess = guess_result.split() + if len(splitted_guess) != 2: + print("wrong input") + continue + cows, bulls = splitted_guess + if not cows.isdigit() or not bulls.isdigit() or \ + not 0 <= int(cows) <= 4 or not 0 <= int(bulls) <= 4: + print("wrong input") + continue + cows, bulls = int(cows), int(bulls) + if bulls == 4: + print("Done! Your number is {}".format(guess)) + done = True + break + # generate new pool - all that don't match can't possibly be the number so eliminate them + number_pool = [x for x in number_pool if is_number_guess_cb_match(str(x), str(guess), cows, bulls)] + # do a new guess + break + +def test(): + assert number_has_repeating_or_zero(1234) is False + assert number_has_repeating_or_zero(1123) is True + assert number_has_repeating_or_zero(1322) is True + assert number_has_repeating_or_zero(1233) is True + assert number_has_repeating_or_zero(1230) is True + + assert count_cows("1234", "4321") == 4 + assert count_cows("1234", "5678") == 0 + assert count_cows("1234", "1258") == 0 # but 2 bulls + assert count_cows("1234", "6581") == 1 + assert count_bulls("1234", "1234") == 4 + assert count_bulls("1234", "1258") == 2 + assert count_bulls("1234", "4321") == 0 + assert count_bulls("1234", "4132") == 1 + + +if __name__ == '__main__': + test() + main()