すみません、自分にはまだε-greedy法を説明できないので、実装したコードだけ載せます。
試行回数は10000回にしました。
///追記///
試行回数100回でも「勝ち確の場所には置く」「相手のリーチは防ぐ」という最低限のことはしてくれていました。
↓ソースコード
◯✕ゲーム
import math
import random
import numpy as np
import copy
root2=math.sqrt(2)
epsilon=0.5
#ボード、空き→0,◯→3、×→4
board=[0,0,0,
       0,0,0,
       0,0,0]
class player:
    def __init__(self):
        self.disc=0
        self.opp_disc=0
class board_place:
    def __init__(self):
        self.coor=0#座標
        self.win=0
        self.tie=0
        self.lose=0
        self.reward=0#報酬期待値
        
turn1=player()
turn1.disc=3
turn1.opp_disc=4
turn2=player()
turn2.disc=4
turn2.opp_disc=3
can_place=[0,1,2,3,4,5,6,7,8]
#勝利判定(決着→1、まだ→0)
def win_defeat(board,disc):
    if board[0]+board[1]+board[2]==disc*3:
        return 1
    elif board[0]+board[3]+board[6]==disc*3:
        return 1
    elif board[0]+board[4]+board[8]==disc*3:
        return 1
    elif board[2]+board[4]+board[6]==disc*3:
        return 1
    elif board[2]+board[5]+board[8]==disc*3:
        return 1
    elif board[6]+board[7]+board[8]==disc*3:
        return 1
    elif board[1]+board[4]+board[7]==disc*3:
        return 1
    elif board[3]+board[4]+board[5]==disc*3:
        return 1
    else:
        return 0
#引き分け判定(引き分け→1,まだ→0)
def drow(board):
    cnt=0
    for i in range(9):
        if board[i]!=0:
            cnt+=1
    if cnt==9:
        return 1
    else:
        return 0
#盤面表示
def show_board(board):
    for i in range(9):
        print(board[i],end="")
        if i==2 or i==5:
            print("")
        elif i==8:
            print("\n")
        else:
            print(" ",end="")
#実行
def run(board,player,place):
    global can_place
    board[int(place)]=player.disc
    can_place.remove(int(place))
    show_board(board)
#仮の実行
def trial_run(board,player,place,reward_place):#placeは最初に置く場所
    tmp_can_place=copy.deepcopy(can_place)#コピー
    tmp_board=copy.deepcopy(board)#コピー
    tmp_place=reward_place[place].coor#コピー
    num=0
    #最初の一回
    tmp_board[int(tmp_place)]=player.disc
    tmp_can_place.remove(tmp_place)
    result_win=win_defeat(tmp_board,player.disc)
    result_drow=drow(tmp_board)
    if result_win==1:
        reward_place[place].win+=1
        num=1
    elif result_drow==1:
        reward_place[place].tie+=1
        num=1
    if num==0:#決着がまだ着いてない
        while True:
            #相手の番
            tmp_place=random.choice(tmp_can_place)
            tmp_board[int(tmp_place)]=player.opp_disc
            tmp_can_place.remove(tmp_place)
            result_win=win_defeat(tmp_board,player.opp_disc)
            result_drow=drow(tmp_board)
            if result_win==1:
                reward_place[place].lose+=1
                break
            elif result_drow==1:
                reward_place[place].tie+=1
                break
            #本人の番
            tmp_place=random.choice(tmp_can_place)
            tmp_board[int(tmp_place)]=player.disc
            tmp_can_place.remove(tmp_place)
            result_win=win_defeat(tmp_board,player.disc)
            result_drow=drow(tmp_board)
            if result_win==1:
                reward_place[place].win+=1
                break
            elif result_drow==1:
                reward_place[place].tie+=1
                break
    
def epsilon_run(board,player):#勝ち数、引き分け、負け数の合計を返す
    global epsilon
    max_reward=-100#ソート用、報酬期待値の最大値
    reward_place=[]#置ける場所ごとの報酬をクラスで設定
    for i in range(9):
            reward_place.append(i)#reward_placeで場所ごとの報酬期待値を管理、置ける場所のみクラスに格納
            if can_place.count(i)==1:
                reward_place[i]=board_place()
                reward_place[i].coor=i
    for place in can_place:#置けるすべての場所で5回ずつ実行
        for i in range(5):
            trial_run(board,player,place,reward_place)
        if reward_place[place].lose==0:#報酬期待値の計算
            reward_place[place].reward=(reward_place[place].win+reward_place[place].tie/2)/5
        else:
            reward_place[place].reward=(reward_place[place].win+reward_place[place].tie/2-reward_place[place].lose)/5
        if reward_place[place].reward>max_reward:#報酬期待値の最大値を探す
            max_reward=reward_place[place].reward
            max_coor=reward_place[place].coor
    for i in range(9995):
        coor=np.random.choice(["max","other"],p=[1-epsilon,epsilon])
        if coor=="max":#1-εの確率で実行
            place=max_coor
        else:
            place=random.choice(can_place)
        trial_run(board,player,place,reward_place)
        if reward_place[place].lose==0:#報酬期待値の計算
            reward_place[place].reward=(reward_place[place].win+reward_place[place].tie/2)/5
        else:
            reward_place[place].reward=(reward_place[place].win+reward_place[place].tie/2-reward_place[place].lose)/5
        if reward_place[place].reward>max_reward:#報酬期待値の最大値を探す
            max_reward=reward_place[place].reward
            max_coor=reward_place[place].coor
        epsilon*=0.99
            
    return reward_place[place].win,reward_place[place].tie,reward_place[place].lose,place
show_board(board)
for i in range(5):
    win,tie,lose,place=epsilon_run(board,turn1)
    win=win/100
    tie=tie/100
    lose=lose/100
    print(f"勝ち:{win}%、引き分け:{tie}%、負け:{lose}%です。\n")
    run(board,turn1,place)
    result=win_defeat(board,3)
    if result==1:
        print("◯の勝ちです")
        break
    if i==4:
        print("引き分けです")
        break
    place=input("どこに置きますか?")
    run(board,turn2,place)
    result=win_defeat(board,4)
    if result==1:
        print("×の勝ちです")
        break
