# 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