/Connect4.py

https://github.com/JoryD/Connect4AI · Python · 323 lines · 280 code · 43 blank · 0 comment · 109 complexity · 9b5eef9447aa18aafff869de4654feaf MD5 · raw file

  1. import sys
  2. import time
  3. from copy import deepcopy
  4. from random import randint, shuffle
  5. BOARD_WIDTH = 7
  6. BOARD_HEIGHT = 6
  7. AI_STRENGTH = 200
  8. cellOptions = [" ", "Y", "R"]; # empty, Yellow symbol, Red symbol
  9. CIRCLE_INVALID = -1
  10. CIRCLE_EMPTY = 0
  11. CIRCLE_YELLOW = 1
  12. CIRCLE_RED = 2
  13. CIRCLE_DRAW = 3
  14. NO_WINNER = 4
  15. COLORAMA = False
  16. CLEARABLE = False
  17. RUSH = True
  18. emptyBoard = [[0 for x in range(BOARD_WIDTH)] for y in range(BOARD_HEIGHT)]
  19. BX = BOARD_WIDTH - 1
  20. BY = BOARD_HEIGHT - 1
  21. ttmList = []
  22. BANNER = ''' _/_/_/ _/ _/ _/
  23. _/ _/_/ _/_/_/ _/_/_/ _/_/ _/_/_/ _/_/_/_/ _/ _/
  24. _/ _/ _/ _/ _/ _/ _/ _/_/_/_/ _/ _/ _/_/_/_/
  25. _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/
  26. _/_/_/ _/_/ _/ _/ _/ _/ _/_/_/ _/_/_/ _/_/ _/ '''
  27. SMALL_BANNER = ''' _
  28. / _ ._ ._ _ __|_|_|_
  29. \_(_)| || |(/_(_ |_ | '''
  30. def Get(b, level, col):
  31. if col < 0 or col > BX or level < 0 or level > BY:
  32. return CIRCLE_INVALID
  33. return b[level][col]
  34. def Set(b, level, col, value):
  35. b[level][col] = value
  36. def ColIsFull(b, col):
  37. return (Get(b, BY, col) != CIRCLE_EMPTY)
  38. def Drop(b, col, value):
  39. if (ColIsFull(b,col) == CIRCLE_INVALID):
  40. return 0
  41. for level in range(0,BOARD_HEIGHT):
  42. if Get(b, level, col) == CIRCLE_EMPTY:
  43. Set(b, level, col, value)
  44. break
  45. return 1
  46. def printGameBoard(b):
  47. export = ""
  48. if CLEARABLE:
  49. os.system('cls||echo -e \\\\033c')
  50. if COLORAMA:
  51. export = export + colorama.Fore.YELLOW + SMALL_BANNER + colorama.Style.RESET_ALL + "\n"
  52. else:
  53. export = export + SMALL_BANNER + "\n"
  54. for level in range(BY, -1, -1):
  55. export = export + str(level)
  56. for col in range(0, BOARD_WIDTH):
  57. color = Get(b, level, col)
  58. if COLORAMA:
  59. if color == CIRCLE_YELLOW:
  60. export = export + colorama.Fore.YELLOW+"[Y]"+colorama.Style.RESET_ALL
  61. elif color == CIRCLE_RED:
  62. export = export + colorama.Fore.RED+"[R]"+colorama.Style.RESET_ALL
  63. else:
  64. export = export + "[ ]"
  65. else:
  66. export = export + "[" + str(cellOptions[color]) + "]"
  67. export = export + "\n"
  68. export = export + " "
  69. for col in range(0, BOARD_WIDTH):
  70. export = export + " " + str(col) + " "
  71. print(export)
  72. def GetWinner(b):
  73. empty = 0
  74. sp = 0
  75. for level in range(BY, -1, -1):
  76. for col in range(0, BX):
  77. color = Get(b, level, col)
  78. if (color == CIRCLE_EMPTY):
  79. empty = empty + 1
  80. continue
  81. directory = [[1,0],[0,1],[1,1],[-1,1]]
  82. for d in range(0, 4):
  83. start_col = col
  84. start_level = level
  85. while(Get(b, start_level-directory[d][1], start_col-directory[d][0]) == color):
  86. start_col = start_col - directory[d][0]
  87. start_level = start_level - directory[d][1]
  88. count = 0
  89. while(Get(b, start_level, start_col) == color):
  90. count = count + 1
  91. start_col = start_col + directory[d][0]
  92. start_level = start_level + directory[d][1]
  93. if (count >= 4):
  94. return color
  95. if(empty <= BOARD_HEIGHT * BOARD_WIDTH):
  96. return CIRCLE_EMPTY
  97. return CIRCLE_DRAW
  98. def RandomGame(b, tomove):
  99. for i in range(0, BOARD_HEIGHT * BOARD_WIDTH):
  100. potentialMoves = [x for x in range(0,BOARD_WIDTH)]
  101. shuffle(potentialMoves)
  102. for move in potentialMoves:
  103. if(not ColIsFull(b, move)):
  104. nextMove = move
  105. break
  106. if (Drop(b, nextMove, tomove)):
  107. if(tomove == CIRCLE_YELLOW):
  108. tomove = CIRCLE_RED
  109. else:
  110. tomove = CIRCLE_YELLOW
  111. winner = GetWinner(b)
  112. if (winner != CIRCLE_EMPTY):
  113. return winner
  114. return CIRCLE_DRAW
  115. def SuggestMove(b, tomove, simulations=AI_STRENGTH):
  116. best = -1
  117. best_ratio = 0
  118. if COLORAMA:
  119. if tomove == CIRCLE_YELLOW:
  120. print(colorama.Fore.YELLOW + "YELLOW IS THINKING" + colorama.Style.RESET_ALL)
  121. elif tomove == CIRCLE_RED:
  122. print(colorama.Fore.RED + "RED IS THINKING" + colorama.Style.RESET_ALL)
  123. for move in range(0,BX+1):
  124. ttm = time.time()
  125. if (ColIsFull(b,move)):
  126. continue
  127. won = 0
  128. lost = 0
  129. draw = 0
  130. print_neutral = 1
  131. for j in range(0, simulations):
  132. copy = deepcopy(b)
  133. Drop(copy, move, tomove);
  134. if (GetWinner(copy) == tomove):
  135. return move
  136. if (tomove == CIRCLE_YELLOW):
  137. nextPlayer = CIRCLE_RED
  138. else:
  139. nextPlayer = CIRCLE_YELLOW
  140. winner = RandomGame(copy, nextPlayer)
  141. if (winner == CIRCLE_YELLOW or winner == CIRCLE_RED):
  142. if (winner == tomove):
  143. won = won + 1
  144. else:
  145. lost = lost + 1
  146. else:
  147. draw = draw + 1
  148. if j == AI_STRENGTH/2 and RUSH == True:
  149. ratio = float(won)/(lost+won+1);
  150. if(ratio+0.05 <= best_ratio and best != -1):
  151. if COLORAMA:
  152. print(colorama.Fore.RED + "X" + colorama.Style.RESET_ALL, end = " ")
  153. print_neutral = 0
  154. break
  155. ratio = float(won)/(lost+won+1);
  156. if(ratio > best_ratio or best == -1):
  157. best = move
  158. best_ratio = ratio
  159. if COLORAMA:
  160. print(colorama.Fore.GREEN + "$" + colorama.Style.RESET_ALL, end = " ")
  161. print_neutral = 0
  162. if COLORAMA and print_neutral == 1:
  163. print(colorama.Fore.YELLOW + "?" + colorama.Style.RESET_ALL, end = " ")
  164. ttmList.append(time.time() - ttm)
  165. print("Move", move, ":", round(ratio*100,1), "draws:", draw, "ttm:", round(ttmList[-1],1), "attm:", round(sum(ttmList)/len(ttmList),1))
  166. return best
  167. def sim():
  168. board = deepcopy(emptyBoard)
  169. while(1):
  170. printGameBoard(board)
  171. if showPotentialWinner(board): return
  172. Drop(board,SuggestMove(board, CIRCLE_RED, AI_STRENGTH), CIRCLE_RED)
  173. if showPotentialWinner(board): return
  174. printGameBoard(board)
  175. Drop(board, SuggestMove(board, CIRCLE_YELLOW, AI_STRENGTH), CIRCLE_YELLOW)
  176. def singlePlayer(whoStarts=1):
  177. board = deepcopy(emptyBoard)
  178. if whoStarts == 1:
  179. while(1):
  180. printGameBoard(board)
  181. if showPotentialWinner(board): return
  182. Drop(board, getMoveFromPlayer(board), CIRCLE_RED)
  183. printGameBoard(board)
  184. if showPotentialWinner(board): return
  185. computer_move = SuggestMove(board, CIRCLE_YELLOW, AI_STRENGTH)
  186. Drop(board, computer_move, CIRCLE_YELLOW)
  187. elif whoStarts == 2:
  188. while(1):
  189. printGameBoard(board)
  190. if showPotentialWinner(board): return
  191. Drop(board, SuggestMove(board, CIRCLE_YELLOW, AI_STRENGTH), CIRCLE_YELLOW)
  192. printGameBoard(board)
  193. if showPotentialWinner(board): return
  194. Drop(board, getMoveFromPlayer(board, CIRCLE_RED), CIRCLE_RED)
  195. def showPotentialWinner(board):
  196. winner = GetWinner(board)
  197. if winner != CIRCLE_EMPTY and winner != CIRCLE_DRAW:
  198. printGameBoard(board)
  199. if COLORAMA:
  200. if winner == CIRCLE_YELLOW:
  201. print(colorama.Fore.YELLOW+" winner: YELLOW" + colorama.Style.RESET_ALL)
  202. else:
  203. print(colorama.Fore.RED+" winner: RED" + colorama.Style.RESET_ALL)
  204. return True
  205. else:
  206. print("winner: ", winner)
  207. return True
  208. elif winner == CIRCLE_DRAW:
  209. printGameBoard(board)
  210. print("DRAW!")
  211. return False
  212. def getMoveFromPlayer(b, color):
  213. potentialPlayerMove = -1
  214. while(potentialPlayerMove == -1):
  215. try:
  216. potentialPlayerMove = int(input("Player "+ str(color) +", state your move: "))
  217. except ValueError:
  218. print("Invalid move, try again!")
  219. potentialPlayerMove = -1
  220. continue
  221. if potentialPlayerMove == 9:
  222. return SuggestMove(b, color)
  223. if (potentialPlayerMove > (BX) or potentialPlayerMove < 0):
  224. print("Invalid move, try again!")
  225. potentialPlayerMove = -1
  226. if (potentialPlayerMove != -1 and ColIsFull(b, potentialPlayerMove) != CIRCLE_EMPTY):
  227. print("Invalid move, try again!")
  228. potentialPlayerMove = -1
  229. return potentialPlayerMove
  230. def twoPlayer():
  231. board = deepcopy(emptyBoard)
  232. while(1):
  233. printGameBoard(board)
  234. if showPotentialWinner(board): return
  235. Drop(board, getMoveFromPlayer(board, CIRCLE_RED), CIRCLE_RED)
  236. printGameBoard(board)
  237. potentialPlayerMove = -1
  238. if showPotentialWinner(board): return
  239. Drop(board, getMoveFromPlayer(board, CIRCLE_YELLOW), CIRCLE_YELLOW)
  240. if __name__== "__main__":
  241. print(BANNER)
  242. print("By Jory Detwiler v1.0.0.0 | https://github.com/JoryD")
  243. if "--easy" in sys.argv:
  244. AI_STRENGTH = 250
  245. elif "--medium" in sys.argv or "--med" in sys.argv:
  246. AI_STRENGTH = 500
  247. elif "--hard" in sys.argv:
  248. AI_STRENGTH = 1000
  249. elif "--insane" in sys.argv:
  250. AI_STRENGTH = 2500
  251. elif "--master" in sys.argv:
  252. AI_STRENGTH = 5000
  253. elif "--demigod" in sys.argv:
  254. AI_STRENGTH = 10000
  255. elif "--god" in sys.argv:
  256. AI_STRENGTH = 100000
  257. if "--norush" in sys.argv:
  258. RUSH = False
  259. elif "--rush" in sys.argv:
  260. RUSH = True
  261. if "--pretty" in sys.argv:
  262. try:
  263. import colorama
  264. colorama.init()
  265. COLORAMA = 1
  266. except ImportError:
  267. print("You do not have COLORAMA installed. No colors for you :(")
  268. print("Try `pip install colorama`, `python pip install colorama`, `python3 pip install colorama`, or maybe `pip3 install colorama`")
  269. if "--clearable" in sys.argv:
  270. import os
  271. CLEARABLE = 1
  272. if "--singleplayer" in sys.argv:
  273. if "--second" in sys.argv:
  274. singlePlayer(2)
  275. else:
  276. singlePlayer(1)
  277. elif "--multiplayer" in sys.argv:
  278. twoPlayer()
  279. else:
  280. sim()