CPG/TicTacToe/src/player.nim

152 lines
5.2 KiB
Nim

# Copyright 2022 Mattia Giambirtone & All Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import board
import ../util/matrix
export board
export matrix
import std/strutils
import std/segfaults
import std/terminal
import std/random
import std/os
import std/exitprocs
template clearScreen =
eraseScreen()
setCursorPos(0, 0)
proc play(moves: Move) =
## Plays a game of tic tac toe
## against the user
clearScreen()
var game = newTicTacToe()
var moves = moves
var location: tuple[row, col: int]
var index: int
var self, enemy: TileKind
stdout.styledWrite(fgGreen, styleBright, "Wanna start first? ", fgYellow ,"[Y/n] ")
if readLine(stdin).strip(chars={'\n'}).toLowerAscii() notin ["y", "yes"]:
self = TileO
enemy = TileX
else:
self = TileX
enemy = TileO
location = where(moves.state, moves.state != game.map, 3).index(self.uint8)
game.place(self, location.row, location.col)
clearScreen()
styledEcho fgCyan, styleBright, "Computer chose ", fgYellow, $game.map.getIndex(location.row, location.col)
clearScreen()
while game.get() == Playing:
styledEcho fgBlue, styleBright, "Tic Tac Bot v1.0"
echo game, "\n"
styledEcho fgMagenta, styleBright, "You are ", fgBlue, $TileKind(enemy)
stdout.styledWrite(fgRed, styleBright, "Make your move ", fgBlue, "(", fgYellow, "1", fgGreen, "~", fgYellow, "8", fgBlue, ")", fgRed, ": ")
flushFile(stdout)
try:
index = int(parseBiggestInt(readLine(stdin).strip(chars={'\n'})))
location = ind2sub(index, game.map.shape)
dec(index)
except ValueError:
clearScreen()
styledEcho fgRed, styleBright, "Invalid move"
continue
if index notin 0..8 or TileKind(game.map[location.row, location.col]) != TileEmpty:
clearScreen()
styledEcho fgRed, styleBright, "Invalid move"
continue
game.place(enemy, location.row, location.col)
clearScreen()
if game.winner() == enemy:
echo game, "\n"
styledEcho fgGreen, styleBright, "Human wins!"
return
if game.winner() == self:
echo game, "\n"
styledEcho fgRed, styleBright, "Computer wins!"
return
if game.get() == Draw:
break
# Find best move and advance move tree
moves = moves.next[game.map.getIndex(location.row, location.col)].findBest(true, turn=self).move
location = where(moves.state, moves.state != game.map, 3).index(self.uint8)
game.place(self, location.row, location.col)
clearScreen()
if game.get() != Draw:
styledEcho fgCyan, styleBright, "Computer chose ", fgYellow, $(game.map.getIndex(location.row, location.col) + 1)
if game.winner() == enemy:
echo game, "\n"
styledEcho fgGreen, styleBright, "Human wins!"
return
if game.get() == Draw:
break
echo game
styledEcho fgYellow, styleBright, "It's a draw!"
proc hook {.noconv.} =
echo ""
quit(0)
when isMainModule:
randomize()
addExitProc(resetAttributes)
setControlCHook(hook)
if isTrueColorSupported():
enableTrueColors()
var path = getCacheDir() / "ttb"
path.createDir()
path = path / "cache.bin"
var moves: Move
# Since generating the tree is pretty expensive, we cache
# it the first time we generate it (since it's not like it changes
# anyway) so that we don't have to rebuild it every time
if not fileExists(path):
styledEcho fgCyan, styleBright, "Generating move tree"
moves = generateMoves(build(@[uint8(0), 0, 0, 0, 0, 0, 0, 0, 0]).map, TileX)
styledEcho fgCyan, styleBright, "Caching data to disk..."
var fp = open(path, fmWrite)
discard fp.writeBytes(moves.dumpBytes(), 0, 8799135)
fp.close()
else:
styledEcho fgCyan, styleBright, "Loading previously cached move tree"
var fp = open(path, fmRead)
var data: seq[byte] = @[]
for _ in 0..<8799135:
data.add(byte(0))
discard fp.readBytes(data, 0, 8799135)
moves = data.loadBytes()
fp.close()
while true:
try:
play(moves)
stdout.styledWrite(fgGreen, styleBright, "Again? ", fgYellow ,"[Y/n] ")
flushFile(stdout)
if readLine(stdin).strip(chars={'\n'}).toLowerAscii() notin ["no", "n"]:
break
except IOError:
break
except EOFError:
break
except NilAccessDefect:
stdout.styledWriteLine(fgRed, styleBright, "Segmentation fault")
break