Reputation: 125
I have the following situation. I have a game and two bots, who play this game against each other. All the communication is done via stdin and stdout. Now I want to write an engine, which lets the bots play against each other and decides, which bot won the game. To do this, I need to run the bots from my engine and communicate with them through stdin and stdout.
So, to start a bot, I have done the following:
from subprocess import Popen, PIPE, STDOUT
bot = Popen(['/path/to/bot1'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)
Now I want to transmit the game state to the bot and request a move from my bot. So first, I want to write some lines of text to this process, and after that my engine should wait some time and read the result from the process. Note, that the bot is always running and does not terminate itself.
How can this be achieved?
Upvotes: 2
Views: 114
Reputation: 2053
Unfortunately, it may not be as easy as reading from bot.stdout
and writing to bot.stdin
. Reason being, some programs do not flush to STDOUT
until the program is done executing if it's a child process. The way to get around this is to spawn it under a pseudo-terminal. Here's an example using the pty
module:
bot1.py
#!/usr/bin/env python
name = raw_input("What is your name?\n")
print "Hello {}, my name is Bot1".format(name)
raw_input() # Bot2: Hello Bot1
bot2.py
#!/usr/bin/env python
raw_input() # Bot1: What is your name?
print "Bot2"
greeting = raw_input() # Bot1: Hello Bot2, my name is Bot1
print "Hello {}".format(greeting.split()[-1])
run.py
#!/usr/bin/env python
import os
import pty
import termios
class Bot:
def __init__(self, name, program):
self.name = name
# Fork a process in a pseudo-terminal
self._pid, self._fd = pty.fork()
if self._pid == 0:
# Child process, replace with program
os.execvp(program, [program])
# Turn off echoing for child file descriptor, otherwise it will spew the
# child's output to the parent's STDOUT.
attribs = termios.tcgetattr(self._fd)
attribs[3] = attribs[3] & ~termios.ECHO
termios.tcsetattr(self._fd, termios.TCSANOW, attribs)
def speak(self, bot):
output = self.read()
bot.write(output)
print '{} -> {}: {}'.format(self.name, bot.name, output)
def read(self):
return os.read(self._fd, 1024).strip()
def write(self, line):
os.write(self._fd, line + "\n")
if __name__ == '__main__':
bot1 = Bot('Bot1', './bot1.py')
bot2 = Bot('Bot2', './bot2.py')
# Bot1 -> Bot2: What is your name?
bot1.speak(bot2)
# Bot2 -> Bot1: Bot2
bot2.speak(bot1)
# Bot1 -> Bot2: Hello Bot2, my name is Bot1
bot1.speak(bot2)
# Bot2 -> Bot1: Hello Bot1
bot2.speak(bot1)
output
$ ./run.py
Bot1 -> Bot2: What is your name?
Bot2 -> Bot1: Bot2
Bot1 -> Bot2: Hello Bot2, my name is Bot1
Bot2 -> Bot1: Hello Bot1
It's a simple example, but hopefully it's enough to get you started.
Upvotes: 3
Reputation: 43495
Since you use PIPE
, the Popen
object bot
contains three attributes (stdin
, stdout
amd stderr
) that are streams.
So you can just use bot.stdout.read()
and bot.stdin.write()
.
as Aldehir mentions, you might have to flush
the streams that you write to, so that bot
actually receives something.
The reason that the documentation advises to use communicate
(which is totally unsuitable in this case) is that the OS pipe buffers might fill up if the subprocess or you don't read data fast/often enough, causing a deadlock.
Unless you are sending large quantities of data at high speed I don't thing this will be a problem.
Upvotes: 2