Partially-Observable Connect-4
This bot takes all sure wins, and falls back to block the rivals, second guess them vertically and horizontally or make random moves.
import pprint, math, collections, copy def zsani_bot_2(view, turn, state): if state == None: #first own turn - always for for middle state = (1, 2) if turn == 0 else (2, 1) #(my_symbol, your symbol) #print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0])) return 3, state #locate obvious points for i in range (1, 6): #skip first row for j in range(len(view[i])): #TODO: Optimise with zip. Go for clarity now if view[i][j] != 0 and view[i-1][j] == 0: view[i-1][j] = state[1] enemy_points = math.floor(turn/2) ++enemy_points if state[0] == 2 else enemy_points known_points = sum([i.count(state[1]) for i in view]) missing_points = enemy_points - known_points #get sure wins in any direction for j in range(0, 7): #every column for i in range(4, -1, -1): if view[i][j] !=0: break #find highest known filled point if (not missing_points or i+1 in {1, 3, 5}): view1 = copy.deepcopy(view) attempt = apply_move(view1, state[0], j) if attempt == WON: # print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' winner move') return j, state #block sure enemy wins in any direction for j in range(0, 7): for i in range(4, -1, -1): if view[i][j] !=0: break #find highest known filled point if (not missing_points or (i+1 in {1, 3, 5})): view1 = copy.deepcopy(view) attempt = apply_move(view1, state[1], j) if attempt == WON: # print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' saving move') return j, state #block walls for i in range(0, 3): #impossible to get 4 in a row when the column is full for j in range(0, 6): if view[i][j] != 0 and view[i][j] == view[i+1][j] and view[i+2][j] == view[i+3][j] == 0: # print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' column move') return j, state #block platforms if posessing perfect information on row below and drop point for i in range(0, 5): for j in range(0, 3): stats = collections.Counter([view[i][j], view[i][j+1], view[i][j+2], view[i][j+3]]) if stats[0] == 2 and (stats[state[0]] == 2 or stats[state[0]] == 2): for k in range(0, 3): if view[i][j+k] == 0: break if (i == 0 or view[i-1][j+k] != 0) and (not missing_points or i in {1, 3, 5}): #print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' platform move') return j+k, state else: for l in range (k, 3): if view[i][j+l] == 0: break if (i == 0 or view[i-1][j+l] != 0) and (not missing_points or i in {1, 3, 5}): # print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' platform move') return j+l, state #fallback -> random while True: j = random.randrange(0, 7) if view[-1][j] == 0: #print(pprint.pformat(view) + ' Turn: ' + str(turn) + ' Player: ' + str(state[0]) + ' random move') return j, state
Thank you for fixing run_game!
Changelog:
- v2 adds horizontal blocking - if, in a row of 4, there are two empty spots and two spots filled by the same player, it will attempt to fill one of them to have three in a row/block the opponents row, which will hopefully be capitalized upon in the following turns.
normalBot plays upon the assumption that spots in the middle are more valuable than spots on the ends. Thus, it uses a normal distribution centered in the middle to determine its choices.
def normalBot(view, turn, state):
randomNumber = round(np.random.normal(3, 1.25))
fullColumns = []
for i in range(7):
if view[-1][i] != 0:
fullColumns.append(i)
while (randomNumber > 6) or (randomNumber < 0) or (randomNumber in fullColumns):
randomNumber = round(np.random.normal(3, 1.25))
return randomNumber, state