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()
\$\endgroup\$
  • 4
    \$\begingroup\$ Did you intentionally call main() twice? \$\endgroup\$ – 200_success Apr 24 '16 at 2:30
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?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.