Al Simulation
The round() Function
The round() function will round a float number to the nearest whole float number. Try entering the following into the interactive shell:
>>> round(10.0) 10.0 >>> round(10.2) 10.0 >>> round(8.7) 9.0 >>> round(4.5) 5.0 >>> round(3.5) 4.0 >>> round(3.4999) 3.0 >>> round(2.5422, 2) 2.54 >>>
As you can see, whenever the fraction part of a number is .5 or greater, the number is rounded up. Otherwise, the number is rounded down. The round() function also has an optional parameter, where you can specify to what place you wish to round the number to. For example, the expression round(2.5422, 2) evaluates to 2.54.
Displaying the Statistics
291. numGames = float(numGames) 292. xpercent = round(((xwins / numGames) * 100), 2) 293. opercent = round(((owins / numGames) * 100), 2) 294. tiepercent = round(((ties / numGames) * 100), 2) 295. print('X wins %s games (%s%%), O wins %s games (%s%%), ties for %s games (%s%%) of %s games total.' % (xwins, xpercent, owins, opercent, ties, tiepercent, numGames))
The code at the bottom of our program will show the user how many wins X and O had, how many ties there were, and how what percentages these make up. Statistically, the more games you run, the more accurate your percentages will be. If you only ran ten games, and X won three of them, then it would seem that X's algorithm only wins 30% of the time. However, if you run a hundred, or even a thousand games, then you may find that X's algorithm wins closer to 50% (that is, half) of the games.
To find the percentages, we divide the number of wins or ties by the total number of games. We convert numGames to a float to ensure we do not use integer division in our calculation. Then we multiply the result by 100. However, we may end up with a number like 66.66666666666667. So we pass this number to the round() function with the second parameter of 2 to limit the precision to two decimal places, so it will return a float like 66.67 instead (which is much more readable).
Let's try another experiment. Run AISim2.py again, but this time have it run a hundred games:
Sample Run of AISim2.py
Welcome to Reversi! Enter number of games to run: 100 Game #0: X scored 42 points. O scored 18 points. Game #1: X scored 26 points. O scored 37 points. Game #2: X scored 34 points. O scored 29 points. Game #3: X scored 40 points. O scored 24 points. ...skipped for brevity... Game #96: X scored 22 points. O scored 39 points. Game #97: X scored 38 points. O scored 26 points. Game #98: X scored 35 points. O scored 28 points. Game #99: X scored 24 points. O scored 40 points. X wins 46 games (46.0%), O wins 52 games (52.0%), ties for 2 games (2.0%) of 100.0 games total.
Depending on how fast your computer is, this run might have taken a about a couple minutes. We can see that the results of all one hundred games still evens out to about fifty-fifty, because both X and O are using the same algorithm to win.
Comparing Different AI Algorithms
Let's add some new functions with new algorithms. But first click on File, then Save As, and save this file as AISim3.py. Before the print('Welcome to Reversi!') line, add these functions:
AISim3.py
This code can be downloaded from http://inventwithpython.com/AISim3.py
If you get errors after typing this code in, compare it to the book's code with the online
diff tool at http://inventwithpython.com/diff or email the author at
245. def getRandomMove(board, tile): 246. # Return a random move. 247. return random.choice( getValidMoves(board, tile) ) 248 . 249 . 250. def isOnSide(x, y): 251. return x == 0 or x == 7 or y == 0 or y ==7 252 . 253 . 254. def getCornerSideBestMove(board, tile) : 255. # Return a corner move, or a side move, or the best move. 256. possibleMoves = getValidMoves(board, tile) 257. 258. # randomize the order of the possible moves 259. random.shuffle(possibleMoves) 260 . 261. # always go for a corner if available. 262. for x, y in possibleMoves: 263. if isOnCorner(x, y): 264. return [x, y] 265 . 266. # if there is no corner, return a side move. 267. for x, y in possibleMoves: 268. if isOnSide(x, y): 269. return [x, y] 270 . 271. return getComputerMove(board, tile) 272. 273. 274. def getSideBestMove(board, tile) : 275. # Return a corner move, or a side move, or the best move. 276. possibleMoves = getValidMoves(board, tile) 277. 278. # randomize the order of the possible moves 279. random.shuffle(possibleMoves) 280. 281. # return a side move, if available 282. for x, y in possibleMoves: 283. if isOnSide(x, y): 284. return [x, y] 285 . 286. return getComputerMove(board, tile) 287. 288. 289. def getWorstMove(board, tile) : 290. # Return the move that flips the least number of tiles. 291. possibleMoves = getValidMoves(board, tile) 292 . 293. # randomize the order of the possible moves 294. random.shuffle(possibleMoves) 295 . 296. # Go through all the possible moves and remember the best scoring move 297. worstScore = 64 298. for x, y in possibleMoves: 299. dupeBoard = getBoardCopy(board) 300. makeMove(dupeBoard, tile, x, y) 301. score = getScoreOfBoard(dupeBoard)[tile] 302. if score < worstScore: 303. worstMove = [x, y] 304. worstScore = score 305 . 306. return worstMove 307. 308. 309. def getCornerWorstMove(board, tile) : 310. # Return a corner, a space, or the move that flips the least number of tiles. 311. possibleMoves = getValidMoves(board, tile) 312 . 313. # randomize the order of the possible moves 314. random.shuffle(possibleMoves) 315 . 316. # always go for a corner if available. 317. for x, y in possibleMoves: 318. if isOnCorner(x, y): 319. return [x, y] 320 . 321. return getWorstMove(board, tile) 322. 323. 324. 325. print('Welcome to Reversi!')