1
\$\begingroup\$

The code below works. The timings are as close as I can get to the system not packing out.

Basically telnet to a server to play tic-tac-toe. Automated it. Bottom left is 1, middle left is 2, middle right is 3 etc

Once a game is finished, I say "yes" and go again.

Anyway I can optimise it?

#!/usr/bin/env python
from telnetlib import Telnet
import sys
from pprint import pprint
from random import randint
HOST = '127.0.0.1'
PORT = 6666
tn = Telnet(HOST, PORT)
board=['','','','','','','','','']
winLines = [[0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6]]
def main():
 startup()
 while True:
 readBoard()
 selectMove()
 tn.close()
def startup():
 print tn.read_until("WordsWeNeverSee",0.1) 
 tn.write("O\n") 
 print tn.read_until("go first.", 0.1) 
def foobar():
 tn.write("yes\n")
 print tn.read_until("WordsWeNeverSee",0.1) 
 tn.write("O\n") 
 readBoard()
def readBoard():
 tIn = tn.read_until("WordsWeNeverSee",0.3)
 tLines = tIn.split('\n') 
 sys.stdout.write(tIn)
 tLines = tLines[::-1]
 if len(tLines)==13:
 row = tLines[3].replace(' ', '').split('|')
 #X|X|O
 board[0] = row[0]
 board[1] = row[1]
 board[2] = row[2]
 row = tLines[7].replace(' ', '').split('|')
 #X|X|O
 board[3] = row[0]
 board[4] = row[1]
 board[5] = row[2]
 row = tLines[11].replace(' ', '').split('|')
 #X|X|O
 board[6] = row[0]
 board[7] = row[1]
 board[8] = row[2]
 elif len(tLines)==14:
 row = tLines[3].replace(' ', '').split('|')
 #X|X|O
 board[0] = row[0]
 board[1] = row[1]
 board[2] = row[2]
 row = tLines[7].replace(' ', '').split('|')
 #X|X|O
 board[3] = row[0]
 board[4] = row[1]
 board[5] = row[2]
 row = tLines[11].replace(' ', '').split('|')
 #X|X|O
 board[6] = row[0]
 board[7] = row[1]
 board[8] = row[2]
 else:
 readBoard()
 if tIn.find("play again") != -1:
 foobar()
def countCharInCells(char, tup): # X, [2,4,6]
 count = 0
 for n in tup:
 if board[n] == char:
 count += 1
 return count
def findEmpty(tup): # [2,4,6]
 for n in tup:
 if board[n] == '':
 return n
 return -1
def selectMove():
 xCount = board.count('X') 
 # Strong first turn move
 if xCount == 0:
 tn.write('9')
 return
 # Best response to center start
 if board.count('X') == 1:
 if board[4] == 'X':
 tn.write('9')
 return
 # See if we can win
 for wl in winLines: #[[0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6]]
 if countCharInCells('O', wl) == 2:
 choice = findEmpty(wl)
 if choice != -1:
 tn.write(str(choice+1))
 return
 # Try to block opposition winning moves
 for wl in winLines:
 if countCharInCells('X', wl) == 2:
 choice = findEmpty(wl)
 if choice != -1:
 tn.write(str(choice+1))
 return
 # Try to snag a corner
 choice = findEmpty([0,2,6,8])
 if choice != -1:
 tn.write(str(choice+1))
 return
 # Pick a random valid square when stumped
 while True:
 choice = randint(0,8)
 if board[choice] == '':
 tn.write(str(choice+1))
 return
if __name__ == "__main__":
 main()
 main()
200_success
145k22 gold badges190 silver badges478 bronze badges
asked Apr 23, 2016 at 23:42
\$\endgroup\$
1
  • 4
    \$\begingroup\$ Did you intentionally call main() twice? \$\endgroup\$ Commented Apr 24, 2016 at 2:30

1 Answer 1

1
\$\begingroup\$

I am not particularly a python expert but can offer some feedback from a fairly generic perspective.

  • There are no useful (plain English) comments until line 105. There is definitely room to improve the commenting of this code.

  • Unless StackExchange is affecting the formatting, this doesn't appear to adhere to PEP 8 (the definitive Python style guide). For example, you are using three spaces per indentation level, not four.

  • As @200_success spotted in a comment, you are calling main() twice at the end: is this intentional? I cannot understand why this would be.

  • foobar() does not sound like a sensible name for a function: it gives me absolutely no clue as to what the function's purpose is.

  • What is the significance of "WordsWeNeverSee"? This might be well documented in the Telnet library but it might be helpful to offer a brief explanation here, so somebody can follow this code without knowing that library well. Failing that, at least a reference to the main documentation would be good.

  • Your main game engine architecture smells funny to me. When main() first calls readBoard(), this can then lead to readBoard() being called recursively, and/or control passing to foobar() which then calls readBoard() again. This feels like it might lead to a deeply-nested call stack. Without following the logic through carefully I have no idea what the rationale is for this, but it seems unlikely to be the best design approach.

  • Relatedly, your key functions readBoard() and selectMove() take no input and return no output. They are operating on data structures that exist globally, aside from their function scope - i.e. they are having side effects. This also seems unlikely to be the best approach; I'm not really qualified to say what the best or most-pythonic way might be, but a more object-oriented approach could be one way to go.

  • readBoard() seems to have two blocks of identical code, inside the first if and following elif. In other words, you're saying: if len(tLines)==13, then do some stuff; if len(tLines)==14, then do the same stuff; else do something different. Can you see the quick way to simplify this code?

answered Apr 26, 2016 at 13:02
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.