Открыть список
Как стать автором
Обновить

Игра для программистов — Быки и коровы

Спортивное программирование
Привет Хабралюди. Я расскажу вам, как на Питоне написать движок и примерный бот игры для программистов. Игры для программистов — это такие игры, в которые играют не люди, а программы.


Мы будем писать вариант игры «Быки и коровы», по-английски которая называется «Bulls and Cows». Правила оригинальной игры между двумя людьми можете прочитать тут.
Правила нашей игры:
  1. Движок задумывает число, оно n значное и цифры в нем не могут повторяться. Движок сообщает программе-игроку число n
  2. Программа-игрок предполагает какое-то число, сообщает его движку
  3. Движок сообщает программе-игроку, сколько цифр из числа программы-игрока есть в задуманном и стоят на своем месте (Быки), и сколько есть в задуманно, но не стоят на своем месте (коровы).
  4. Если число не угаданно, возврат к п.2


Программа-игрок будет общаться с движком посредством stdin/out. Наша спецификация предполагает, что движок запускает переданные ему в качестве аргументов программы-игроки.

Движок



Напишем вначале функциональные части:

Copy Source | Copy HTML
  1. #!/usr/bin/python
  2. import random
  3. class Game(object):
  4. def __init__(self, number=6):
  5. if number > 10:
  6. exit()
  7. self.number = number
  8. self.secret = self.make_secret(self.number)
  9. self.main()
  10. def is_right_secret(self, secret):
  11. """check all digits are different"""
  12. dct = {}
  13. for digit in secret:
  14. try:
  15. if dct[digit]:
  16. return False
  17. except KeyError:
  18. dct[digit] = True
  19. return True
  20. def make_secret(self, number):
  21. """generate number-counted secret"""
  22. while True:
  23. result = ""
  24. for i in range(number):
  25. result += "%d" % random.randint( 0,9)
  26. if self.is_right_secret(result):
  27. return result


Здесь мы задумываем число, которое программы-игроки будут отгадывать. Также мы проверяем, чтобы все цифры в числе были разными. затем мы запускаем Game.main — основной цикл программы.

Также необходимо дописать функцию подсчета быков и коров:

Copy Source | Copy HTML
  1. def count_bulls_cows(self, number, secret):
  2. """count bulls and cows in number/secret"""
  3. cows, bulls = 0, 0
  4. dct = {}
  5. for i in range(len(secret)):
  6. if secret[i]==number[i]:
  7. bulls += 1
  8. else:
  9. dct[secret[i]] = True
  10. try:
  11. if dct[number[i]]:
  12. cows += 1
  13. except KeyError:
  14. pass
  15. return bulls, cows


Теперь введем константы, которые будут обозначать кодовые слова для общения с программами игроками, и некоторые другие:

Copy Source | Copy HTML
  1. MESS_NUMBER_DIGITS = "number_digits" #start message
  2. MESS_GUESS = "guess!"
  3. MESS_COWS = "cows:" #If the matching digits on different positions, they are "cows"
  4. MESS_BULLS = "bulls:" #If the matching digits are on their right positions, they are "bulls"
  5. MESS_BULLS_COWS_SPLIT = "|" #Splitter between cows and bulls message
  6. MAX_TURN_NUMBER = 1000


Теперь опишем игровой процесс в main(): (привожу с отладочными сообщениями)

Copy Source | Copy HTML
  1. def main(self):
  2. for player in self.players:
  3. player.connect()
  4. player.send("%s %d" % (MESS_NUMBER_DIGITS, self.number))
  5. print ("%s %d" % (MESS_NUMBER_DIGITS, self.number))
  6. starttime = time.time()
  7. while True:
  8. if player.turns > MAX_TURN_NUMBER:
  9. print ">>>> Player '%s' turns timeout" % player.name
  10. player.disconnect()
  11. break
  12. answer = player.recieve()
  13. print ">>>> Answer is %s" % answer
  14. if len(self.secret)!=len(answer):
  15. self.stop_game("wrong number")
  16. bulls, cows = self.count_bulls_cows(answer, self.secret)
  17. if bulls==self.number:
  18. player.time = time.time() - starttime
  19. player.send("%s" % MESS_GUESS)
  20. player.disconnect()
  21. print ">>>> Player disconected"
  22. break
  23. else:
  24. player.turns += 1
  25. player.send("%s %d%s%s %d" % (MESS_BULLS, bulls,\
  26. MESS_BULLS_COWS_SPLIT, MESS_COWS, cows))
  27. print(">>>> %s %d%s%s %d" % (MESS_BULLS, bulls,\
  28. MESS_BULLS_COWS_SPLIT, MESS_COWS, cows) )
  29. winner = self.players[ 0]
  30. for player in self.players:
  31. if player.turns<winner.turns:
  32. winner = player
  33. print ">>>> Winner name: %s" % winner.name


Тут все понятно. По очереди играем с каждым игроком. Вначале отправляем количество цифр в загаданном числе. Засекаем время. Принимаем число-догадку игрока, если не угадал (число быков не равно числу цифр в числе) — отправляем число быков/коров и увеличиваем число ходов, потребовавшеясе игроку на отгадку, если угадал — останавливаем таймер, пишем ему что он выиграл, отключаемся от игрока, переходим к следующему.

В конце выбираем победителя.

Опишем машинерию соединения с процессами программ-игроков в классе Player:

Copy Source | Copy HTML
  1. class Player(object):
  2. def __init__(self, name, progname):
  3. self.name = name
  4. self.turns = 0
  5. self.time = -1
  6. self.progname = progname
  7. def connect(self):
  8. self.__proc = subprocess.Popen(self.progname, stdin=subprocess.PIPE,\
  9. stdout=subprocess.PIPE)
  10. def send(self, sendstring):
  11. self.__proc.stdin.write(sendstring+'\n')
  12. def recieve(self)
  13. return self.__proc.stdout.readline().strip('\n')
  14. def disconnect(self)
  15. self.__proc.terminate()


Немного перепишем Game.__init__, чтобы инициализировать игроков:

Copy Source | Copy HTML
  1. def __init__(self, number, players_progs):
  2. if number > 10:
  3. exit()
  4. self.number = number
  5. self.secret = self.make_secret(self.number)
  6. self.players = []
  7. for playername in players_progs:
  8. self.players.append(Player(playername, players_progs[playername]))
  9. self.main()


Тупой бот



Теперь надо потестировать получившееся. Для тестов нам нужна программа-игрок, самая простая. Она же потом будет api — классом, от которого настоящие программы-игроки будут наследоваться
Создадим в папке проекта подпапку api, а в нем файл mooapi.py:

Copy Source | Copy HTML
  1. #!/usr/bin/python
  2. import random
  3. import sys
  4. MESS_NUMBER_DIGITS = "number_digits" #start message
  5. MESS_GUESS = "guess!"
  6. MESS_COWS = "cows:" #If the matching digits on different positions, they are "cows"
  7. MESS_BULLS = "bulls:" #If the matching digits are on their right positions, they are "bulls"
  8. MESS_BULLS_COWS_SPLIT = "|" #Splitter between cows and bulls message
  9. class MooPlayer(object):
  10. ##technical functions
  11. def send(self, st):
  12. sys.stdout.write("%s\n"%st)
  13. sys.stdout.flush()
  14. def recieve(self):
  15. return sys.stdin.readline().strip("\n")
  16. def number_split(self, st):
  17. num = st.split(' ')
  18. if num[ 0]==MESS_NUMBER_DIGITS:
  19. return int(num[1])
  20. else:
  21. exit()
  22. def answer_split(self, st):
  23. try:
  24. bullst, cowst = st.split(MESS_BULLS_COWS_SPLIT)
  25. except ValueError:
  26. exit()
  27. bulls, cows = bullst.split(' '), cowst.split(' ')
  28. if bulls[ 0]==MESS_BULLS and cows[ 0]==MESS_COWS:
  29. return bulls[1], cows[1]
  30. else:
  31. exit()
  32. def main(self):
  33. self.number = self.number_split(self.recieve())
  34. while True:
  35. try:
  36. self.oldguess = guess
  37. guess = self.guess(cows=cows, bulls=bulls, firstrun=False)
  38. except NameError:
  39. guess = self.guess(firstrun=True)
  40. self.send(guess)
  41. answer = self.recieve()
  42. if answer == MESS_GUESS:
  43. exit()
  44. else:
  45. bulls, cows = self.answer_split(answer)
  46. ##some functions
  47. def is_right_secret(self, secret):
  48. """check all digits are different"""
  49. dct = {}
  50. for digit in secret:
  51. try:
  52. if dct[digit]:
  53. return False
  54. except KeyError:
  55. dct[digit] = True
  56. return True
  57. def generate_number(self, number):
  58. """generate number-counted secret"""
  59. while True:
  60. result = ""
  61. for i in range(number):
  62. result += "%d" % random.randint( 0,9)
  63. if self.is_right_secret(result):
  64. return result
  65. ############ Main guess function ###########
  66. def guess(self, firstrun, bulls= 0, cows= 0):
  67. """This is main api function. firstrun means that it is first time run,<br/> bulls/cows is number of bulls and cows from last time.<br/> also you have:<br/> self.number - number of digits<br/> self.oldguess - old guess try<br/> """
  68. return self.generate_number(self.number)
  69. if __name__ == "__main__":
  70. mooplayer = MooPlayer()
  71. mooplayer.main()


Тут все просто, часть кода скопированна из модуля движка, анализа никакого не происходит, просто угадывается случайным числом.
Тому, кто будет писать программу-игрока, нужно будет импортировать модуль mooapi, наследовать свой класс от MooPlayer и переопределить функцию MooPlayer.guess()

Почему я не импортировал модуль движка — для наглядности, тем, кто будет писать апи для других языков программирования.
Теперь можно запустить программу-игрока и попробовать поиграть с ней в быки и коровы:

>>number_digits 4
5032
>>bulls: 0|cows: 0
4123
>>bulls: 0|cows: 0
2890
>>bulls: 0|cows: 0
2691
>>guess!


(>>) обозначает ввод с клавиатуры

Запускаем



теперь добавим в конец модуля движка:

Copy Source | Copy HTML
  1. if __name__ == "__main__":
  2. game = Game(2, {'Player1':"api/mooapi.py", 'Player2':"api/mooapi.py", 'HabraMan':"api/mooapi.py" })


Это запустит игру с двухзначными числами, тремя игроками с одинаковыми программами api/mooapi.py. С двухзначными — это чтобы наша тупая программа смогла угадать.

Запустим модуль движка:
>>>> bulls: 0|cows: 1
>>>> Answer is 67
>>>> bulls:1 cows:0
>>>> bulls: 1|cows: 0
>>>> Answer is 87
>>>> bulls:0 cows:0
>>>> bulls: 0|cows: 0
>>>> Answer is 49
>>>> bulls:0 cows:0
>>>> bulls: 0|cows: 0
>>>> Answer is 54
>>>> bulls:0 cows:0
>>>> bulls: 0|cows: 0
>>>> Answer is 82
>>>> bulls:1 cows:0
>>>> bulls: 1|cows: 0
>>>> Answer is 21
>>>> bulls:0 cows:0
>>>> bulls: 0|cows: 0
>>>> Answer is 62
>>>> bulls:2 cows:0
>>>> Player disconected
>>>> Winner name: HabraMan


Работает!
В следующем посте я расскажу, как сделать программу полезной — прикрутить к ней парсинг аргументов коммандной строки и вывод всего добра в xml, в котором хавает ввод наш будущий флеш-проигрыватель на нашем будущем сайте.

Ссылка на полные тексты программ

Радость от движка заключается в том, что его можно будет прикрутить на мультиюзерный сайт, который мы сейчас пишем.

Вы можете стать первым, кто реализует алгоритм достопочтенного Кнута в нашем боте.
Теги:спортивное программированиетуториалhabrawarspythonбыки и коровы
Хабы: Спортивное программирование
Всего голосов 38: ↑22 и ↓16 +6
Просмотры19.2K

Похожие публикации

Senior Python Engineer, Remote
от 4 000 до 6 000 $PandaDocМожно удаленно
Инженер-разработчик (С, POSIX)
от 100 000 до 250 000 ₽AurigaМожно удаленно
Python разработчик (middle+)
до 200 000 ₽MONSМоскваМожно удаленно
Программист Golang (Senior/ Middle+ )
от 150 000 до 250 000 ₽X-KeeperМоскваМожно удаленно

Лучшие публикации за сутки