Привет Хабралюди. Я расскажу вам, как на Питоне написать движок и примерный бот игры для программистов. Игры для программистов — это такие игры, в которые играют не люди, а программы.
Мы будем писать вариант игры «Быки и коровы», по-английски которая называется «Bulls and Cows». Правила оригинальной игры между двумя людьми можете прочитать тут.
Правила нашей игры:
Программа-игрок будет общаться с движком посредством stdin/out. Наша спецификация предполагает, что движок запускает переданные ему в качестве аргументов программы-игроки.
Напишем вначале функциональные части:
Здесь мы задумываем число, которое программы-игроки будут отгадывать. Также мы проверяем, чтобы все цифры в числе были разными. затем мы запускаем Game.main — основной цикл программы.
Также необходимо дописать функцию подсчета быков и коров:
Теперь введем константы, которые будут обозначать кодовые слова для общения с программами игроками, и некоторые другие:
Теперь опишем игровой процесс в main(): (привожу с отладочными сообщениями)
Тут все понятно. По очереди играем с каждым игроком. Вначале отправляем количество цифр в загаданном числе. Засекаем время. Принимаем число-догадку игрока, если не угадал (число быков не равно числу цифр в числе) — отправляем число быков/коров и увеличиваем число ходов, потребовавшеясе игроку на отгадку, если угадал — останавливаем таймер, пишем ему что он выиграл, отключаемся от игрока, переходим к следующему.
В конце выбираем победителя.
Опишем машинерию соединения с процессами программ-игроков в классе Player:
Немного перепишем Game.__init__, чтобы инициализировать игроков:
Теперь надо потестировать получившееся. Для тестов нам нужна программа-игрок, самая простая. Она же потом будет api — классом, от которого настоящие программы-игроки будут наследоваться
Создадим в папке проекта подпапку api, а в нем файл mooapi.py:
Тут все просто, часть кода скопированна из модуля движка, анализа никакого не происходит, просто угадывается случайным числом.
Тому, кто будет писать программу-игрока, нужно будет импортировать модуль mooapi, наследовать свой класс от MooPlayer и переопределить функцию MooPlayer.guess()
Почему я не импортировал модуль движка — для наглядности, тем, кто будет писать апи для других языков программирования.
Теперь можно запустить программу-игрока и попробовать поиграть с ней в быки и коровы:
(>>) обозначает ввод с клавиатуры
теперь добавим в конец модуля движка:
Это запустит игру с двухзначными числами, тремя игроками с одинаковыми программами api/mooapi.py. С двухзначными — это чтобы наша тупая программа смогла угадать.
Запустим модуль движка:
Работает!
В следующем посте я расскажу, как сделать программу полезной — прикрутить к ней парсинг аргументов коммандной строки и вывод всего добра в xml, в котором хавает ввод наш будущий флеш-проигрыватель на нашем будущем сайте.
Ссылка на полные тексты программ
Радость от движка заключается в том, что его можно будет прикрутить на мультиюзерный сайт, который мы сейчас пишем.
Вы можете стать первым, кто реализует алгоритм достопочтенного Кнута в нашем боте.
Мы будем писать вариант игры «Быки и коровы», по-английски которая называется «Bulls and Cows». Правила оригинальной игры между двумя людьми можете прочитать тут.
Правила нашей игры:
- Движок задумывает число, оно n значное и цифры в нем не могут повторяться. Движок сообщает программе-игроку число n
- Программа-игрок предполагает какое-то число, сообщает его движку
- Движок сообщает программе-игроку, сколько цифр из числа программы-игрока есть в задуманном и стоят на своем месте (Быки), и сколько есть в задуманно, но не стоят на своем месте (коровы).
- Если число не угаданно, возврат к п.2
Программа-игрок будет общаться с движком посредством stdin/out. Наша спецификация предполагает, что движок запускает переданные ему в качестве аргументов программы-игроки.
Движок
Напишем вначале функциональные части:
Copy Source | Copy HTML
-
- #!/usr/bin/python
- import random
-
- class Game(object):
- def __init__(self, number=6):
- if number > 10:
- exit()
- self.number = number
- self.secret = self.make_secret(self.number)
- self.main()
-
- def is_right_secret(self, secret):
- """check all digits are different"""
- dct = {}
- for digit in secret:
- try:
- if dct[digit]:
- return False
- except KeyError:
- dct[digit] = True
- return True
-
- def make_secret(self, number):
- """generate number-counted secret"""
- while True:
- result = ""
- for i in range(number):
- result += "%d" % random.randint( 0,9)
- if self.is_right_secret(result):
- return result
Здесь мы задумываем число, которое программы-игроки будут отгадывать. Также мы проверяем, чтобы все цифры в числе были разными. затем мы запускаем Game.main — основной цикл программы.
Также необходимо дописать функцию подсчета быков и коров:
Copy Source | Copy HTML
-
- def count_bulls_cows(self, number, secret):
- """count bulls and cows in number/secret"""
- cows, bulls = 0, 0
- dct = {}
- for i in range(len(secret)):
- if secret[i]==number[i]:
- bulls += 1
- else:
- dct[secret[i]] = True
- try:
- if dct[number[i]]:
- cows += 1
- except KeyError:
- pass
- return bulls, cows
Теперь введем константы, которые будут обозначать кодовые слова для общения с программами игроками, и некоторые другие:
Copy Source | Copy HTML
-
- MESS_NUMBER_DIGITS = "number_digits" #start message
- MESS_GUESS = "guess!"
- MESS_COWS = "cows:" #If the matching digits on different positions, they are "cows"
- MESS_BULLS = "bulls:" #If the matching digits are on their right positions, they are "bulls"
- MESS_BULLS_COWS_SPLIT = "|" #Splitter between cows and bulls message
- MAX_TURN_NUMBER = 1000
Теперь опишем игровой процесс в main(): (привожу с отладочными сообщениями)
Copy Source | Copy HTML
- def main(self):
- for player in self.players:
- player.connect()
- player.send("%s %d" % (MESS_NUMBER_DIGITS, self.number))
- print ("%s %d" % (MESS_NUMBER_DIGITS, self.number))
- starttime = time.time()
- while True:
- if player.turns > MAX_TURN_NUMBER:
- print ">>>> Player '%s' turns timeout" % player.name
- player.disconnect()
- break
- answer = player.recieve()
- print ">>>> Answer is %s" % answer
- if len(self.secret)!=len(answer):
- self.stop_game("wrong number")
- bulls, cows = self.count_bulls_cows(answer, self.secret)
- if bulls==self.number:
- player.time = time.time() - starttime
- player.send("%s" % MESS_GUESS)
- player.disconnect()
- print ">>>> Player disconected"
- break
- else:
- player.turns += 1
- player.send("%s %d%s%s %d" % (MESS_BULLS, bulls,\
- MESS_BULLS_COWS_SPLIT, MESS_COWS, cows))
- print(">>>> %s %d%s%s %d" % (MESS_BULLS, bulls,\
- MESS_BULLS_COWS_SPLIT, MESS_COWS, cows) )
-
- winner = self.players[ 0]
- for player in self.players:
- if player.turns<winner.turns:
- winner = player
- print ">>>> Winner name: %s" % winner.name
Тут все понятно. По очереди играем с каждым игроком. Вначале отправляем количество цифр в загаданном числе. Засекаем время. Принимаем число-догадку игрока, если не угадал (число быков не равно числу цифр в числе) — отправляем число быков/коров и увеличиваем число ходов, потребовавшеясе игроку на отгадку, если угадал — останавливаем таймер, пишем ему что он выиграл, отключаемся от игрока, переходим к следующему.
В конце выбираем победителя.
Опишем машинерию соединения с процессами программ-игроков в классе Player:
Copy Source | Copy HTML
-
- class Player(object):
- def __init__(self, name, progname):
- self.name = name
- self.turns = 0
- self.time = -1
- self.progname = progname
-
- def connect(self):
- self.__proc = subprocess.Popen(self.progname, stdin=subprocess.PIPE,\
- stdout=subprocess.PIPE)
-
- def send(self, sendstring):
- self.__proc.stdin.write(sendstring+'\n')
-
- def recieve(self)
- return self.__proc.stdout.readline().strip('\n')
-
- def disconnect(self)
- self.__proc.terminate()
-
Немного перепишем Game.__init__, чтобы инициализировать игроков:
Copy Source | Copy HTML
- def __init__(self, number, players_progs):
- if number > 10:
- exit()
- self.number = number
- self.secret = self.make_secret(self.number)
-
- self.players = []
-
- for playername in players_progs:
- self.players.append(Player(playername, players_progs[playername]))
-
- self.main()
-
Тупой бот
Теперь надо потестировать получившееся. Для тестов нам нужна программа-игрок, самая простая. Она же потом будет api — классом, от которого настоящие программы-игроки будут наследоваться
Создадим в папке проекта подпапку api, а в нем файл mooapi.py:
Copy Source | Copy HTML
-
- #!/usr/bin/python
- import random
- import sys
-
- MESS_NUMBER_DIGITS = "number_digits" #start message
- MESS_GUESS = "guess!"
- MESS_COWS = "cows:" #If the matching digits on different positions, they are "cows"
- MESS_BULLS = "bulls:" #If the matching digits are on their right positions, they are "bulls"
- MESS_BULLS_COWS_SPLIT = "|" #Splitter between cows and bulls message
-
-
-
- class MooPlayer(object):
-
- ##technical functions
- def send(self, st):
- sys.stdout.write("%s\n"%st)
- sys.stdout.flush()
-
- def recieve(self):
- return sys.stdin.readline().strip("\n")
-
-
- def number_split(self, st):
- num = st.split(' ')
- if num[ 0]==MESS_NUMBER_DIGITS:
- return int(num[1])
- else:
- exit()
-
- def answer_split(self, st):
- try:
- bullst, cowst = st.split(MESS_BULLS_COWS_SPLIT)
- except ValueError:
- exit()
- bulls, cows = bullst.split(' '), cowst.split(' ')
- if bulls[ 0]==MESS_BULLS and cows[ 0]==MESS_COWS:
- return bulls[1], cows[1]
- else:
- exit()
-
- def main(self):
- self.number = self.number_split(self.recieve())
-
- while True:
- try:
- self.oldguess = guess
- guess = self.guess(cows=cows, bulls=bulls, firstrun=False)
- except NameError:
- guess = self.guess(firstrun=True)
- self.send(guess)
- answer = self.recieve()
- if answer == MESS_GUESS:
- exit()
- else:
- bulls, cows = self.answer_split(answer)
-
-
- ##some functions
- def is_right_secret(self, secret):
- """check all digits are different"""
- dct = {}
- for digit in secret:
- try:
- if dct[digit]:
- return False
- except KeyError:
- dct[digit] = True
- return True
-
- def generate_number(self, number):
- """generate number-counted secret"""
- while True:
- result = ""
- for i in range(number):
- result += "%d" % random.randint( 0,9)
- if self.is_right_secret(result):
- return result
-
- ############ Main guess function ###########
- def guess(self, firstrun, bulls= 0, cows= 0):
- """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/> """
- return self.generate_number(self.number)
-
-
- if __name__ == "__main__":
- mooplayer = MooPlayer()
- 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
-
- if __name__ == "__main__":
- 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, в котором хавает ввод наш будущий флеш-проигрыватель на нашем будущем сайте.
Ссылка на полные тексты программ
Радость от движка заключается в том, что его можно будет прикрутить на мультиюзерный сайт, который мы сейчас пишем.
Вы можете стать первым, кто реализует алгоритм достопочтенного Кнута в нашем боте.