diff --git a/README copy.md b/README copy.md deleted file mode 100644 index b08a344..0000000 --- a/README copy.md +++ /dev/null @@ -1 +0,0 @@ -# games-api diff --git a/docker-compose.yml b/docker-compose.yml index 0f155e9..700dc68 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,10 +6,8 @@ services: container_name: games-api environment: - NODE_ENV=production - - REDIS_HOST=${REDIS_HOST:-redis} - - REDIS_PORT=${REDIS_PORT:-1118} - - REDIS_USERNAME=${REDIS_USERNAME:-admin} - - REDIS_PASSWORD=${REDIS_PASSWORD:-default} + - MONGODB=${MONGODB:-mongodb://username:password@host:port} + - BROKER_URL=${BROKER_URL:-ws://localhost:8070} restart: unless-stopped # options: always, unless-stopped, on-failure ports: - '8060:8060' # Expose port 8060 on the host diff --git a/games_ai/doc.md b/games_ai/doc.md new file mode 100644 index 0000000..8a02dd0 --- /dev/null +++ b/games_ai/doc.md @@ -0,0 +1,39 @@ +# install + +brew update +brew upgrade + +pip3 --version +pip 21.2.4 from /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/site-packages/pip (python 3.9) + +- pip3 install mlx + +# logs + +==> node@20 +node@20 is keg-only, which means it was not symlinked into /opt/homebrew, +because this is an alternate version of another formula. + +If you need to have node@20 first in your PATH, run: +echo 'export PATH="/opt/homebrew/opt/node@20/bin:$PATH"' >> /Users/pedrodarma/.zshrc + +For compilers to find node@20 you may need to set: +export LDFLAGS="-L/opt/homebrew/opt/node@20/lib" +export CPPFLAGS="-I/opt/homebrew/opt/node@20/include" +==> ruby +By default, binaries installed by gem will be placed into: +/opt/homebrew/lib/ruby/gems/3.4.0/bin + +You may want to add this to your PATH. + +ruby is keg-only, which means it was not symlinked into /opt/homebrew, +because macOS already provides this software and installing another version in +parallel can cause all kinds of trouble. + +If you need to have ruby first in your PATH, run: +echo 'export PATH="/opt/homebrew/opt/ruby/bin:$PATH"' >> /Users/pedrodarma/.zshrc + +For compilers to find ruby you may need to set: +export LDFLAGS="-L/opt/homebrew/opt/ruby/lib" +export CPPFLAGS="-I/opt/homebrew/opt/ruby/include" +pedrodarma@Mini-Mini-Darma games-api % diff --git a/games_ai/generate_dataset.py b/games_ai/generate_dataset.py new file mode 100644 index 0000000..60e7bd1 --- /dev/null +++ b/games_ai/generate_dataset.py @@ -0,0 +1,94 @@ +# print("Hello from games_ai/play.py") + +from pymongo import MongoClient +import numpy as np +import pandas as pd + + +POSITION_MAP = { + "a0": 0, "a1": 1, "a2": 2, + "b0": 3, "b1": 4, "b2": 5, + "c0": 6, "c1": 7, "c2": 8, +} + +def convert_games_to_samples(): + client = MongoClient("mongodb://admin:G49vm222-3d02ksc!@192.168.15.17:27018/") + db = client["logs"] + # games = db["qtt_logs"].find({"status": "finished", "winner": {"$ne": None}}) + games = db["qtt_logs"].find({"status": "finished", "type": {"$eq": "game_over_win"}}) + + samples = [] + + print("Converting games to samples...") + # print("Total games found:", games.total()) + + for game in games: + board = [0]*9 + winner = game.get("winner") + x_id = game.get("playerXId") + o_id = game.get("playerOId") + + # print(game) + + x_moves = [POSITION_MAP[m] for m in game.get("playerXMoves", [])] + o_moves = [POSITION_MAP[m] for m in game.get("playerOMoves", [])] + + # print("X moves:", x_moves) + # print("O moves:", o_moves) + + board = [0]*9 + for i in range(len(x_moves)): + board[x_moves[i]] = 1 + + for i in range(len(o_moves)): + board[o_moves[i]] = -1 + + state = board.copy() + + print("Final board state:", state) + + # # Reconstroi o jogo movimento a movimento + # for i in range(max(len(x_moves), len(o_moves))): + # # Jogada do X + # if i < len(x_moves): + # move = x_moves[i] + # state = board.copy() + # samples.append({ + # "board": state, + # "player": 1, + # "move": move, + # "winner": 1 if winner == x_id else -1 + # }) + # board[move] = 1 + + # # Jogada do O + # if i < len(o_moves): + # move = o_moves[i] + # state = board.copy() + # samples.append({ + # "board": state, + # "player": -1, + # "move": move, + # "winner": 1 if winner == o_id else -1 + # }) + # board[move] = -1 + + return samples + +# Exemplo de uso: +if __name__ == "__main__": + samples = convert_games_to_samples() + print(samples[:5]) # mostra as 5 primeiras amostras + + rows = [] + for s in samples: + rows.append({ + **{f"cell_{i}": v for i, v in enumerate(s["board"])}, + "player": s["player"], + "move": s["move"], + "winner": s["winner"] + }) + + df = pd.DataFrame(rows) + df.to_csv("tictactoe_dataset.csv", index=False) + print("Dataset salvo com", len(df), "amostras") diff --git a/games_ai/generate_dataset_copy copy.py b/games_ai/generate_dataset_copy copy.py new file mode 100644 index 0000000..e6e1ac2 --- /dev/null +++ b/games_ai/generate_dataset_copy copy.py @@ -0,0 +1,75 @@ +# print("Hello from games_ai/play.py") + +from pymongo import MongoClient +import numpy as np +import pandas as pd + + +POSITION_MAP = { + "a0": 0, "a1": 1, "a2": 2, + "b0": 3, "b1": 4, "b2": 5, + "c0": 6, "c1": 7, "c2": 8, +} + +def convert_games_to_samples(): + client = MongoClient("mongodb://admin:G49vm222-3d02ksc!@192.168.15.17:27018/") + db = client["logs"] + # games = db["qtt_logs"].find({"status": "finished"}) + games = db["qtt_logs"].find({"status": "finished", "type": {"$eq": "game_over_win"}}) + + samples = [] + + for game in games: + board = [0]*9 + winner = game.get("winner") + x_id = game.get("playerXId") + o_id = game.get("playerOId") + + x_moves = [POSITION_MAP[m] for m in game.get("playerXMoves", [])] + o_moves = [POSITION_MAP[m] for m in game.get("playerOMoves", [])] + + # Reconstroi o jogo movimento a movimento + for i in range(max(len(x_moves), len(o_moves))): + # Jogada do X + if i < len(x_moves): + move = x_moves[i] + state = board.copy() + samples.append({ + "board": state, + "player": 1, + "move": move, + "winner": 1 if winner == x_id else -1 + }) + board[move] = 1 + + # Jogada do O + if i < len(o_moves): + move = o_moves[i] + state = board.copy() + samples.append({ + "board": state, + "player": -1, + "move": move, + "winner": 1 if winner == o_id else -1 + }) + board[move] = -1 + + return samples + +# Exemplo de uso: +if __name__ == "__main__": + samples = convert_games_to_samples() + print(samples[:5]) # mostra as 5 primeiras amostras + + rows = [] + for s in samples: + rows.append({ + **{f"cell_{i}": v for i, v in enumerate(s["board"])}, + "player": s["player"], + "move": s["move"], + "winner": s["winner"] + }) + + df = pd.DataFrame(rows) + df.to_csv("tictactoe_dataset.csv", index=False) + print("Dataset salvo com", len(df), "amostras") diff --git a/games_ai/generate_dataset_copy.py b/games_ai/generate_dataset_copy.py new file mode 100644 index 0000000..e6e1ac2 --- /dev/null +++ b/games_ai/generate_dataset_copy.py @@ -0,0 +1,75 @@ +# print("Hello from games_ai/play.py") + +from pymongo import MongoClient +import numpy as np +import pandas as pd + + +POSITION_MAP = { + "a0": 0, "a1": 1, "a2": 2, + "b0": 3, "b1": 4, "b2": 5, + "c0": 6, "c1": 7, "c2": 8, +} + +def convert_games_to_samples(): + client = MongoClient("mongodb://admin:G49vm222-3d02ksc!@192.168.15.17:27018/") + db = client["logs"] + # games = db["qtt_logs"].find({"status": "finished"}) + games = db["qtt_logs"].find({"status": "finished", "type": {"$eq": "game_over_win"}}) + + samples = [] + + for game in games: + board = [0]*9 + winner = game.get("winner") + x_id = game.get("playerXId") + o_id = game.get("playerOId") + + x_moves = [POSITION_MAP[m] for m in game.get("playerXMoves", [])] + o_moves = [POSITION_MAP[m] for m in game.get("playerOMoves", [])] + + # Reconstroi o jogo movimento a movimento + for i in range(max(len(x_moves), len(o_moves))): + # Jogada do X + if i < len(x_moves): + move = x_moves[i] + state = board.copy() + samples.append({ + "board": state, + "player": 1, + "move": move, + "winner": 1 if winner == x_id else -1 + }) + board[move] = 1 + + # Jogada do O + if i < len(o_moves): + move = o_moves[i] + state = board.copy() + samples.append({ + "board": state, + "player": -1, + "move": move, + "winner": 1 if winner == o_id else -1 + }) + board[move] = -1 + + return samples + +# Exemplo de uso: +if __name__ == "__main__": + samples = convert_games_to_samples() + print(samples[:5]) # mostra as 5 primeiras amostras + + rows = [] + for s in samples: + rows.append({ + **{f"cell_{i}": v for i, v in enumerate(s["board"])}, + "player": s["player"], + "move": s["move"], + "winner": s["winner"] + }) + + df = pd.DataFrame(rows) + df.to_csv("tictactoe_dataset.csv", index=False) + print("Dataset salvo com", len(df), "amostras") diff --git a/games_ai/play.py b/games_ai/play.py new file mode 100644 index 0000000..40daf1e --- /dev/null +++ b/games_ai/play.py @@ -0,0 +1,2 @@ +if __name__ == "__main__": + print("Hello from games_ai/play.py") \ No newline at end of file diff --git a/games_ai/requirements.txt b/games_ai/requirements.txt new file mode 100644 index 0000000..0e24e84 --- /dev/null +++ b/games_ai/requirements.txt @@ -0,0 +1,4 @@ +mlx +pymongo +numpy +pandas \ No newline at end of file diff --git a/games_ai/tictactoe_dataset.csv b/games_ai/tictactoe_dataset.csv new file mode 100644 index 0000000..a318df8 --- /dev/null +++ b/games_ai/tictactoe_dataset.csv @@ -0,0 +1,259 @@ +cell_0,cell_1,cell_2,cell_3,cell_4,cell_5,cell_6,cell_7,cell_8,player,move,winner +0,0,0,0,0,0,0,0,0,1,2,1 +0,0,1,0,0,0,0,0,0,-1,7,-1 +0,0,1,0,0,0,0,-1,0,1,1,1 +0,1,1,0,0,0,0,-1,0,-1,5,-1 +0,1,1,0,0,-1,0,-1,0,1,4,1 +0,1,1,0,1,-1,0,-1,0,-1,3,-1 +0,1,1,-1,1,-1,0,-1,0,1,0,1 +0,0,0,0,0,0,0,0,0,1,1,1 +0,1,0,0,0,0,0,0,0,-1,0,-1 +-1,1,0,0,0,0,0,0,0,1,4,1 +-1,1,0,0,1,0,0,0,0,-1,5,-1 +-1,1,0,0,1,-1,0,0,0,1,7,1 +0,0,0,0,0,0,0,0,0,1,3,1 +0,0,0,1,0,0,0,0,0,-1,8,-1 +0,0,0,1,0,0,0,0,-1,1,4,1 +0,0,0,1,1,0,0,0,-1,-1,5,-1 +0,0,0,1,1,-1,0,0,-1,1,2,1 +0,0,1,1,1,-1,0,0,-1,-1,0,-1 +-1,0,1,1,1,-1,0,0,-1,1,6,1 +0,0,0,0,0,0,0,0,0,1,0,1 +1,0,0,0,0,0,0,0,0,-1,5,-1 +1,0,0,0,0,-1,0,0,0,1,4,1 +1,0,0,0,1,-1,0,0,0,-1,7,-1 +1,0,0,0,1,-1,0,-1,0,1,8,1 +0,0,0,0,0,0,0,0,0,1,2,1 +0,0,1,0,0,0,0,0,0,-1,4,-1 +0,0,1,0,-1,0,0,0,0,1,8,1 +0,0,1,0,-1,0,0,0,1,-1,5,-1 +0,0,1,0,-1,-1,0,0,1,1,6,1 +0,0,1,0,-1,-1,1,0,1,-1,1,-1 +0,-1,1,0,-1,-1,1,0,1,1,7,1 +0,0,0,0,0,0,0,0,0,1,0,-1 +1,0,0,0,0,0,0,0,0,-1,7,1 +1,0,0,0,0,0,0,-1,0,1,4,-1 +1,0,0,0,1,0,0,-1,0,-1,8,1 +1,0,0,0,1,0,0,-1,-1,1,2,-1 +1,0,1,0,1,0,0,-1,-1,-1,6,1 +0,0,0,0,0,0,0,0,0,1,0,1 +1,0,0,0,0,0,0,0,0,-1,2,-1 +1,0,-1,0,0,0,0,0,0,1,4,1 +1,0,-1,0,1,0,0,0,0,-1,3,-1 +1,0,-1,-1,1,0,0,0,0,1,8,1 +0,0,0,0,0,0,0,0,0,1,4,1 +0,0,0,0,1,0,0,0,0,-1,1,-1 +0,-1,0,0,1,0,0,0,0,1,8,1 +0,-1,0,0,1,0,0,0,1,-1,2,-1 +0,-1,-1,0,1,0,0,0,1,1,0,1 +0,0,0,0,0,0,0,0,0,1,6,1 +0,0,0,0,0,0,1,0,0,-1,0,-1 +-1,0,0,0,0,0,1,0,0,1,8,1 +-1,0,0,0,0,0,1,0,1,-1,1,-1 +-1,-1,0,0,0,0,1,0,1,1,7,1 +0,0,0,0,0,0,0,0,0,1,4,1 +0,0,0,0,1,0,0,0,0,-1,3,-1 +0,0,0,-1,1,0,0,0,0,1,8,1 +0,0,0,-1,1,0,0,0,1,-1,1,-1 +0,-1,0,-1,1,0,0,0,1,1,0,1 +0,0,0,0,0,0,0,0,0,1,0,1 +1,0,0,0,0,0,0,0,0,-1,4,-1 +1,0,0,0,-1,0,0,0,0,1,8,1 +1,0,0,0,-1,0,0,0,1,-1,6,-1 +1,0,0,0,-1,0,-1,0,1,1,2,1 +1,0,1,0,-1,0,-1,0,1,-1,1,-1 +1,-1,1,0,-1,0,-1,0,1,1,5,1 +0,0,0,0,0,0,0,0,0,1,0,1 +1,0,0,0,0,0,0,0,0,-1,4,-1 +1,0,0,0,-1,0,0,0,0,1,8,1 +1,0,0,0,-1,0,0,0,1,-1,6,-1 +1,0,0,0,-1,0,-1,0,1,1,2,1 +1,0,1,0,-1,0,-1,0,1,-1,1,-1 +1,-1,1,0,-1,0,-1,0,1,1,5,1 +0,0,0,0,0,0,0,0,0,1,0,1 +1,0,0,0,0,0,0,0,0,-1,8,-1 +1,0,0,0,0,0,0,0,-1,1,4,1 +1,0,0,0,1,0,0,0,-1,-1,7,-1 +1,0,0,0,1,0,0,-1,-1,1,2,1 +1,0,1,0,1,0,0,-1,-1,-1,5,-1 +1,0,1,0,1,-1,0,-1,-1,1,6,1 +0,0,0,0,0,0,0,0,0,1,4,1 +0,0,0,0,1,0,0,0,0,-1,7,-1 +0,0,0,0,1,0,0,-1,0,1,8,1 +0,0,0,0,1,0,0,-1,1,-1,6,-1 +0,0,0,0,1,0,-1,-1,1,1,0,1 +0,0,0,0,0,0,0,0,0,1,8,1 +0,0,0,0,0,0,0,0,1,-1,7,-1 +0,0,0,0,0,0,0,-1,1,1,2,1 +0,0,1,0,0,0,0,-1,1,-1,1,-1 +0,-1,1,0,0,0,0,-1,1,1,0,1 +1,-1,1,0,0,0,0,-1,1,-1,3,-1 +1,-1,1,-1,0,0,0,-1,1,1,4,1 +0,0,0,0,0,0,0,0,0,1,6,1 +0,0,0,0,0,0,1,0,0,-1,0,-1 +-1,0,0,0,0,0,1,0,0,1,2,1 +-1,0,1,0,0,0,1,0,0,-1,4,-1 +-1,0,1,0,-1,0,1,0,0,1,8,1 +-1,0,1,0,-1,0,1,0,1,-1,1,-1 +-1,-1,1,0,-1,0,1,0,1,1,7,1 +0,0,0,0,0,0,0,0,0,1,4,1 +0,0,0,0,1,0,0,0,0,-1,1,-1 +0,-1,0,0,1,0,0,0,0,1,8,1 +0,-1,0,0,1,0,0,0,1,-1,5,-1 +0,-1,0,0,1,-1,0,0,1,1,0,1 +0,0,0,0,0,0,0,0,0,1,6,1 +0,0,0,0,0,0,1,0,0,-1,1,-1 +0,-1,0,0,0,0,1,0,0,1,8,1 +0,-1,0,0,0,0,1,0,1,-1,2,-1 +0,-1,-1,0,0,0,1,0,1,1,7,1 +0,0,0,0,0,0,0,0,0,1,0,1 +1,0,0,0,0,0,0,0,0,-1,7,-1 +1,0,0,0,0,0,0,-1,0,1,8,1 +1,0,0,0,0,0,0,-1,1,-1,4,-1 +1,0,0,0,-1,0,0,-1,1,1,1,1 +1,1,0,0,-1,0,0,-1,1,-1,5,-1 +1,1,0,0,-1,-1,0,-1,1,1,3,1 +1,1,0,1,-1,-1,0,-1,1,-1,2,-1 +1,1,-1,1,-1,-1,0,-1,1,1,6,1 +0,0,0,0,0,0,0,0,0,1,8,1 +0,0,0,0,0,0,0,0,1,-1,2,-1 +0,0,-1,0,0,0,0,0,1,1,7,1 +0,0,-1,0,0,0,0,1,1,-1,5,-1 +0,0,-1,0,0,-1,0,1,1,1,3,1 +0,0,-1,1,0,-1,0,1,1,-1,6,-1 +0,0,-1,1,0,-1,-1,1,1,1,1,1 +0,1,-1,1,0,-1,-1,1,1,-1,0,-1 +-1,1,-1,1,0,-1,-1,1,1,1,4,1 +0,0,0,0,0,0,0,0,0,1,7,-1 +0,0,0,0,0,0,0,1,0,-1,4,1 +0,0,0,0,-1,0,0,1,0,1,2,-1 +0,0,1,0,-1,0,0,1,0,-1,3,1 +0,0,1,-1,-1,0,0,1,0,1,5,-1 +0,0,1,-1,-1,1,0,1,0,-1,8,1 +0,0,1,-1,-1,1,0,1,-1,1,6,-1 +0,0,1,-1,-1,1,1,1,-1,-1,0,1 +0,0,0,0,0,0,0,0,0,1,0,-1 +1,0,0,0,0,0,0,0,0,-1,4,1 +1,0,0,0,-1,0,0,0,0,1,8,-1 +1,0,0,0,-1,0,0,0,1,-1,5,1 +1,0,0,0,-1,-1,0,0,1,1,6,-1 +1,0,0,0,-1,-1,1,0,1,-1,1,1 +1,-1,0,0,-1,-1,1,0,1,1,2,-1 +1,-1,1,0,-1,-1,1,0,1,-1,7,1 +0,0,0,0,0,0,0,0,0,1,7,1 +0,0,0,0,0,0,0,1,0,-1,5,-1 +0,0,0,0,0,-1,0,1,0,1,3,1 +0,0,0,1,0,-1,0,1,0,-1,8,-1 +0,0,0,1,0,-1,0,1,-1,1,1,1 +0,1,0,1,0,-1,0,1,-1,-1,6,-1 +0,1,0,1,0,-1,-1,1,-1,1,4,1 +0,0,0,0,0,0,0,0,0,1,4,1 +0,0,0,0,1,0,0,0,0,-1,6,-1 +0,0,0,0,1,0,-1,0,0,1,8,1 +0,0,0,0,1,0,-1,0,1,-1,2,-1 +0,0,-1,0,1,0,-1,0,1,1,0,1 +0,0,0,0,0,0,0,0,0,1,3,1 +0,0,0,1,0,0,0,0,0,-1,6,-1 +0,0,0,1,0,0,-1,0,0,1,7,1 +0,0,0,1,0,0,-1,1,0,-1,8,-1 +0,0,0,1,0,0,-1,1,-1,1,5,1 +0,0,0,1,0,1,-1,1,-1,-1,2,-1 +0,0,-1,1,0,1,-1,1,-1,1,1,1 +0,1,-1,1,0,1,-1,1,-1,-1,0,-1 +-1,1,-1,1,0,1,-1,1,-1,1,4,1 +0,0,0,0,0,0,0,0,0,1,4,-1 +0,0,0,0,1,0,0,0,0,-1,8,1 +0,0,0,0,1,0,0,0,-1,1,0,-1 +1,0,0,0,1,0,0,0,-1,-1,1,1 +1,-1,0,0,1,0,0,0,-1,1,2,-1 +1,-1,1,0,1,0,0,0,-1,-1,6,1 +1,-1,1,0,1,0,-1,0,-1,1,5,-1 +1,-1,1,0,1,1,-1,0,-1,-1,7,1 +0,0,0,0,0,0,0,0,0,1,0,1 +1,0,0,0,0,0,0,0,0,-1,3,-1 +1,0,0,-1,0,0,0,0,0,1,4,1 +1,0,0,-1,1,0,0,0,0,-1,5,-1 +1,0,0,-1,1,-1,0,0,0,1,8,1 +0,0,0,0,0,0,0,0,0,1,7,1 +0,0,0,0,0,0,0,1,0,-1,3,-1 +0,0,0,-1,0,0,0,1,0,1,4,1 +0,0,0,-1,1,0,0,1,0,-1,0,-1 +-1,0,0,-1,1,0,0,1,0,1,2,1 +-1,0,1,-1,1,0,0,1,0,-1,8,-1 +-1,0,1,-1,1,0,0,1,-1,1,1,1 +0,0,0,0,0,0,0,0,0,1,2,1 +0,0,1,0,0,0,0,0,0,-1,5,-1 +0,0,1,0,0,-1,0,0,0,1,4,1 +0,0,1,0,1,-1,0,0,0,-1,3,-1 +0,0,1,-1,1,-1,0,0,0,1,0,1 +1,0,1,-1,1,-1,0,0,0,-1,8,-1 +1,0,1,-1,1,-1,0,0,-1,1,6,1 +0,0,0,0,0,0,0,0,0,1,4,1 +0,0,0,0,1,0,0,0,0,-1,6,-1 +0,0,0,0,1,0,-1,0,0,1,8,1 +0,0,0,0,1,0,-1,0,1,-1,5,-1 +0,0,0,0,1,-1,-1,0,1,1,0,1 +0,0,0,0,0,0,0,0,0,1,0,1 +1,0,0,0,0,0,0,0,0,-1,5,-1 +1,0,0,0,0,-1,0,0,0,1,4,1 +1,0,0,0,1,-1,0,0,0,-1,2,-1 +1,0,-1,0,1,-1,0,0,0,1,8,1 +0,0,0,0,0,0,0,0,0,1,0,1 +1,0,0,0,0,0,0,0,0,-1,5,-1 +1,0,0,0,0,-1,0,0,0,1,4,1 +1,0,0,0,1,-1,0,0,0,-1,7,-1 +1,0,0,0,1,-1,0,-1,0,1,8,1 +0,0,0,0,0,0,0,0,0,1,4,1 +0,0,0,0,1,0,0,0,0,-1,2,-1 +0,0,-1,0,1,0,0,0,0,1,8,1 +0,0,-1,0,1,0,0,0,1,-1,6,-1 +0,0,-1,0,1,0,-1,0,1,1,0,1 +0,0,0,0,0,0,0,0,0,1,0,1 +1,0,0,0,0,0,0,0,0,-1,7,-1 +1,0,0,0,0,0,0,-1,0,1,8,1 +1,0,0,0,0,0,0,-1,1,-1,2,-1 +1,0,-1,0,0,0,0,-1,1,1,6,1 +1,0,-1,0,0,0,1,-1,1,-1,4,-1 +1,0,-1,0,-1,0,1,-1,1,1,3,1 +0,0,0,0,0,0,0,0,0,1,8,1 +0,0,0,0,0,0,0,0,1,-1,4,-1 +0,0,0,0,-1,0,0,0,1,1,5,1 +0,0,0,0,-1,1,0,0,1,-1,2,-1 +0,0,-1,0,-1,1,0,0,1,1,6,1 +0,0,-1,0,-1,1,1,0,1,-1,0,-1 +-1,0,-1,0,-1,1,1,0,1,1,7,1 +0,0,0,0,0,0,0,0,0,1,0,1 +1,0,0,0,0,0,0,0,0,-1,7,-1 +1,0,0,0,0,0,0,-1,0,1,1,1 +1,1,0,0,0,0,0,-1,0,-1,3,-1 +1,1,0,-1,0,0,0,-1,0,1,2,1 +0,0,0,0,0,0,0,0,0,1,2,1 +0,0,1,0,0,0,0,0,0,-1,6,-1 +0,0,1,0,0,0,-1,0,0,1,1,1 +0,1,1,0,0,0,-1,0,0,-1,7,-1 +0,1,1,0,0,0,-1,-1,0,1,0,1 +0,0,0,0,0,0,0,0,0,1,5,-1 +0,0,0,0,0,1,0,0,0,-1,4,1 +0,0,0,0,-1,1,0,0,0,1,3,-1 +0,0,0,1,-1,1,0,0,0,-1,6,1 +0,0,0,1,-1,1,-1,0,0,1,7,-1 +0,0,0,1,-1,1,-1,1,0,-1,2,1 +0,0,0,0,0,0,0,0,0,1,4,1 +0,0,0,0,1,0,0,0,0,-1,3,-1 +0,0,0,-1,1,0,0,0,0,1,8,1 +0,0,0,-1,1,0,0,0,1,-1,6,-1 +0,0,0,-1,1,0,-1,0,1,1,0,1 +0,0,0,0,0,0,0,0,0,1,5,1 +0,0,0,0,0,1,0,0,0,-1,2,-1 +0,0,-1,0,0,1,0,0,0,1,8,1 +0,0,-1,0,0,1,0,0,1,-1,4,-1 +0,0,-1,0,-1,1,0,0,1,1,6,1 +0,0,-1,0,-1,1,1,0,1,-1,1,-1 +0,-1,-1,0,-1,1,1,0,1,1,7,1 +0,0,0,0,0,0,0,0,0,1,8,1 +0,0,0,0,0,0,0,0,1,-1,5,-1 +0,0,0,0,0,-1,0,0,1,1,4,1 +0,0,0,0,1,-1,0,0,1,-1,7,-1 +0,0,0,0,1,-1,0,-1,1,1,2,1 +0,0,1,0,1,-1,0,-1,1,-1,0,-1 +-1,0,1,0,1,-1,0,-1,1,1,6,1 diff --git a/games_ai/tictactoe_model.safetensors b/games_ai/tictactoe_model.safetensors new file mode 100644 index 0000000..bcb0cfe Binary files /dev/null and b/games_ai/tictactoe_model.safetensors differ diff --git a/games_ai/train.py b/games_ai/train.py new file mode 100644 index 0000000..601ea70 --- /dev/null +++ b/games_ai/train.py @@ -0,0 +1,116 @@ +# train.py +import numpy as np +from pymongo import MongoClient +import mlx.core as mx +import mlx.nn as nn +import mlx.optimizers as optim +import json +import os + +# ======================== +# CONFIGURAÇÕES +# ======================== +MONGO_URI = os.getenv("MONGO_URI", "mongodb://admin:G49vm222-3d02ksc!@192.168.15.17:27018/") +DB_NAME = "logs" +COLLECTION = "qtt_logs" +# MODEL_PATH = "/models/tictactoe_model.mlx" +# MODEL_PATH = "/tictactoe_model.mlx" +MODEL_PATH = "./tictactoe_model.safetensors" + +# ======================== +# FUNÇÕES DE PREPARO +# ======================== + +def position_to_index(pos): + # Converte "a0", "b1", etc. em índice 0..8 + mapping = {'a': 0, 'b': 1, 'c': 2} + col = mapping[pos[0]] + row = int(pos[1]) + return row * 3 + col + +def board_from_moves(moves_x, moves_o): + board = np.zeros(9, dtype=np.float32) + for m in moves_x: + board[position_to_index(m)] = 1 + for m in moves_o: + board[position_to_index(m)] = -1 + return board + +def create_dataset(games): + X, y = [], [] + for g in games: + moves_x = g.get("playerXMoves", []) + moves_o = g.get("playerOMoves", []) + winner = g.get("winner") + + # Cria o estado final do tabuleiro + board = board_from_moves(moves_x, moves_o) + + # Gera um label simples: quem venceu + # 1 = X venceu, -1 = O venceu, 0 = empate + if winner == g.get("playerXId"): + label = 1 + elif winner == g.get("playerOId"): + label = -1 + else: + label = 0 + + X.append(board) + y.append(label) + + return np.array(X), np.array(y, dtype=np.float32).reshape(-1, 1) + +def mse_loss(pred, target): + return mx.mean((pred - target) ** 2) + +# ======================== +# DEFINIÇÃO DO MODELO +# ======================== + +class TicTacToeNet(nn.Module): + def __init__(self): + super().__init__() + self.fc1 = nn.Linear(9, 32) + self.fc2 = nn.Linear(32, 16) + self.fc3 = nn.Linear(16, 1) + self.relu = nn.ReLU() + + def __call__(self, x): + x = self.relu(self.fc1(x)) + x = self.relu(self.fc2(x)) + return mx.sigmoid(self.fc3(x)) # saída entre 0 e 1 + +# ======================== +# TREINAMENTO +# ======================== + +def train(): + print("Conectando ao MongoDB...") + client = MongoClient(MONGO_URI) + db = client[DB_NAME] + games = list(db[COLLECTION].find({"status": "finished"})) + print(f"{len(games)} partidas carregadas.") + + X, y = create_dataset(games) + print("Dataset:", X.shape, y.shape) + + model = TicTacToeNet() + optimizer = optim.Adam(learning_rate=0.001) + + def loss_fn(model, X, y): + preds = model(X) + return mx.mean((preds - y) ** 2) + + # Treino + for epoch in range(10000): + loss, grads = mx.value_and_grad(loss_fn)(model, mx.array(X), mx.array(y)) + optimizer.update(model, grads) + if epoch % 10 == 0: + print(f"Epoch {epoch}: loss={loss.item():.4f}") + + os.makedirs(os.path.dirname(MODEL_PATH), exist_ok=True) + model.save_weights(MODEL_PATH) + print("✅ Modelo salvo em:", MODEL_PATH) + +if __name__ == "__main__": + train() diff --git a/package.json b/package.json index 223024b..6dd4de5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "games-api", - "version": "1.0.1", + "version": "1.0.2", "description": "Games API", "main": "index", "scripts": { @@ -11,7 +11,7 @@ "test": "echo \"Error: no test specified\" && exit 1", "lint": "eslint . --ext .js,.ts --max-warnings=10", "lint:fix": "eslint --fix . --ext .js,.ts --max-warnings=10", - "typecheck": "NODE_ENV=test EXPO_PUBLIC_DEFAULT_ENV=test tsc --strict" + "typecheck": "NODE_ENV=test tsc --strict" }, "author": "Pedro Darma", "homepage": "https://github.com/pedrodarma/games-api#readme", diff --git a/src/_config/app-configuration.ts b/src/_config/app-configuration.ts index 8fec63e..b7dd29f 100644 --- a/src/_config/app-configuration.ts +++ b/src/_config/app-configuration.ts @@ -67,6 +67,7 @@ function getOptionalNumberEnv(key: string, defaultValue: number): number { } const parsed = parseInt(value, 10); if (isNaN(parsed)) { + // eslint-disable-next-line no-console console.warn( `Warning: ${key} is not a valid number. Using default value: ${defaultValue}`, ); diff --git a/src/contollers/logs/logs.controller.ts b/src/contollers/logs/logs.controller.ts index fb3746d..b63280f 100644 --- a/src/contollers/logs/logs.controller.ts +++ b/src/contollers/logs/logs.controller.ts @@ -1,4 +1,5 @@ import { MongoDB } from '@databases'; +import { Errors } from '@errors'; import { LogsRepository } from '@repositories'; import { Request, Response } from 'express'; @@ -12,11 +13,13 @@ export class LogsController { const gameKey = req.query.gameKey || req.params.gameKey || req.body.gameKey; - const repository = new LogsRepository(new MongoDB()); + const repository = new LogsRepository(MongoDB.initialize()); const logs = await repository.fetchLogs(gameKey); return res.status(200).json(logs); } catch (error) { + const _err = error instanceof Error ? error : new Error(error as any); + Errors.controllers(_err); return res.status(500).json({ error: 'Internal Server Error' }); } } @@ -30,11 +33,13 @@ export class LogsController { const gameKey = req.query.gameKey || req.params.gameKey || req.body.gameKey; - const repository = new LogsRepository(new MongoDB()); + const repository = new LogsRepository(MongoDB.initialize()); const logs = await repository.fetchLogs(gameKey); return res.status(200).json(logs); } catch (error) { + const _err = error instanceof Error ? error : new Error(error as any); + Errors.controllers(_err); return res.status(500).json({ error: 'Internal Server Error' }); } } @@ -48,7 +53,7 @@ export class LogsController { const gameKey = req.query.gameKey || req.params.gameKey || req.body.gameKey; - const repository = new LogsRepository(new MongoDB()); + const repository = new LogsRepository(MongoDB.initialize()); const success = await repository.addLog(gameKey, req.body); if (!success) { @@ -61,6 +66,8 @@ export class LogsController { .status(201) .json({ message: 'Log entry created successfully' }); } catch (error) { + const _err = error instanceof Error ? error : new Error(error as any); + Errors.controllers(_err); return res.status(500).json({ error: 'Internal Server Error' }); } } diff --git a/src/databases/mongodb/mongodb.ts b/src/databases/mongodb/mongodb.ts index 0d7e1ce..a70b10a 100644 --- a/src/databases/mongodb/mongodb.ts +++ b/src/databases/mongodb/mongodb.ts @@ -1,6 +1,7 @@ import { AppConfig } from '@config'; import { GameKeys, games } from '@constants/game_types'; -import { MongoClient } from 'mongodb'; +import { Errors } from '@errors'; +import { MongoClient, MongoClientOptions, MongoError } from 'mongodb'; const _collectionsToEnsure = Object.values(games).map((game) => { return `${game.id.toLowerCase()}_logs`; @@ -10,52 +11,99 @@ export class MongoDB { private static _instance: MongoDB; private _client: MongoClient; private _database: string = 'logs'; - - constructor(uri?: string) { + private _options: MongoClientOptions = { + maxPoolSize: 20, + wtimeoutMS: 2500, + retryReads: true, + retryWrites: true, + }; + + private constructor(uri?: string) { // Ex.: 'mongodb://username:password@host:port/databaseName'; const _uri = uri ?? AppConfig.database.mongodb.uri; - this._client = new MongoClient(_uri); + this._client = new MongoClient(_uri, this._options); } - public static initialize(): MongoDB { + public static initialize(uri?: string): MongoDB { if (!MongoDB._instance) { - MongoDB._instance = new MongoDB(); + MongoDB._instance = new MongoDB(uri); + MongoDB._instance._initialize(); } - MongoDB._instance._initialize(); return MongoDB._instance; } private async _initialize() { try { - await this._client.connect(); + await this._connectIfNeeded(); - const admin = this._client.db().admin(); - const dbs = await admin.listDatabases(); + await this._createDatabaseIfNotExists(); - const dbExists = dbs.databases.some((db) => db.name === this._database); - // console.log( - // dbExists - // ? `DB ${this._database} already exists.` - // : `DB ${this._database} does not exist.`, - // ); + await this._createCollectionIfNotExists(); + } catch { + // + } + } + private async _isConnected(): Promise { + try { + await this._client.db().admin().ping(); + return true; + } catch { + return false; + } + } + + private async _connect() { + await this._client.connect(); + } + + private async _disconnect() { + await this._client.close(); + } + + private async _connectIfNeeded() { + const connected = await this._isConnected(); + this._client.addListener; + if (!connected) { + await this._connect(); + } + } + + private async _createDatabaseIfNotExists() { + const admin = this._client.db().admin(); + const dbs = await admin.listDatabases(); + + const dbExists = dbs.databases.some((db) => db.name === this._database); + if (!dbExists) { + // eslint-disable-next-line no-console + console.log(`Creating database: ${this._database}`); + // Create the database by creating a collection const db = this._client.db(this._database); + await db.createCollection('init_collection'); + await db.collection('init_collection').drop(); + // eslint-disable-next-line no-console + console.log(`Database ${this._database} created.`); + } else { + // console.log(`Database ${this._database} already exists.`); + } + } + + private async _createCollectionIfNotExists() { + const db = this._client.db(this._database); - const existingCollections = await db.listCollections().toArray(); - const existingNames = existingCollections.map((c) => c.name); + const existingCollections = await db.listCollections().toArray(); + const existingNames = existingCollections.map((c) => c.name); - for (const name of _collectionsToEnsure) { - if (!existingNames.includes(name)) { - await db.createCollection(name); - console.log(`✅ Collection '${name}' created.`); - } else { - // console.log(`ℹ️ Collection '${name}' already exists.`); - } + for (const name of _collectionsToEnsure) { + if (!existingNames.includes(name)) { + await db.createCollection(name); + // eslint-disable-next-line no-console + console.log(`✅ Collection '${name}' created.`); + } else { + // console.log(`ℹ️ Collection '${name}' already exists.`); } - } catch (error) { - // } } @@ -71,9 +119,12 @@ export class MongoDB { if (result && result.insertedId) { return true; } - } catch { - console.log('Error inserting log into MongoDB'); - // + } catch (error) { + const _err = + error instanceof MongoError || error instanceof Error + ? error + : new Error(error as any); + throw Errors.database(_err); } return false; } @@ -88,13 +139,15 @@ export class MongoDB { if (result) { return result; } - } catch { - console.log('Error fetching logs from MongoDB'); + } catch (error) { + const _err = + error instanceof MongoError || error instanceof Error + ? error + : new Error(error as any); + throw Errors.database(_err); } return []; } - - // static async disconnect() {} } interface Props { diff --git a/src/errors/index.ts b/src/errors/index.ts new file mode 100644 index 0000000..778c63c --- /dev/null +++ b/src/errors/index.ts @@ -0,0 +1,48 @@ +import { MongoError } from 'mongodb'; + +export const enum ErrorTypes { + UNKNOWN_ERROR = 'An unknown error occurred.', + DATABASE_CONNECTION_FAILED = 'Failed to connect to the database.', + INVALID_INPUT = 'The provided input is invalid.', + NOT_FOUND = 'The requested resource was not found.', + AUTHENTICATION_FAILED = 'Authentication failed. Please check your credentials.', + PERMISSION_DENIED = 'You do not have permission to perform this action.', + SERVER_ERROR = 'An internal server error occurred.', + TIMEOUT_ERROR = 'The operation timed out. Please try again later.', +} + +export const Errors = { + database: _handle, + websocket: _handle, + controllers: _handle, +}; + +function _handle(error: Error | MongoError) { + let _error: Error; + if (error instanceof MongoError) { + _error = _handleMongoError(error); + } + if (error instanceof Error) { + _error = _handleError(error); + } + + return _error!; +} + +function _handleError(error: Error) { + const file = error.stack?.split('\n')[1] || 'unknown location'; + const line = file.match(/:(\d+):\d+\)?$/)?.[1] || 'unknown line'; + const message = `Error: ${error.message} at ${file.trim()} (line ${line})`; + + return new Error(message); +} + +function _handleMongoError(error: MongoError) { + const file = error.stack?.split('\n')[1] || 'unknown location'; + const line = file.match(/:(\d+):\d+\)?$/)?.[1] || 'unknown line'; + const message = `MongoError: ${error.message} (code: ${ + error.code + }) at ${file.trim()} (line ${line})`; + + return new Error(message); +} diff --git a/src/utils/delay.utils.ts b/src/utils/delay.utils.ts new file mode 100644 index 0000000..29a28bd --- /dev/null +++ b/src/utils/delay.utils.ts @@ -0,0 +1 @@ +export const delay = (ms: number) => new Promise((res) => setTimeout(res, ms)); diff --git a/src/utils/index.ts b/src/utils/index.ts index 6776979..ed56ed4 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,7 @@ import { IDUtils } from './id.utils'; export * from './id.utils'; +export * from './delay.utils'; export const Utils = { /** diff --git a/src/websocket/websocket.ts b/src/websocket/websocket.ts index 3ea5f07..e09710a 100644 --- a/src/websocket/websocket.ts +++ b/src/websocket/websocket.ts @@ -1,5 +1,6 @@ import { AppConfig } from '@config'; import { MongoDB } from '@databases'; +import { Errors } from '@errors'; import { LogQuickTacToe, WebSocketMessage } from '@models'; import { LogsRepository } from '@repositories'; import WebSocket from 'ws'; @@ -16,8 +17,8 @@ export class WebsocketClient { public static initialize(uri?: string): WebsocketClient { if (!WebsocketClient._instance) { WebsocketClient._instance = new WebsocketClient(); + WebsocketClient._instance._initialize(uri); } - WebsocketClient._instance._initialize(uri); return WebsocketClient._instance; } @@ -34,6 +35,7 @@ export class WebsocketClient { try { const _uri = uri ?? AppConfig.websocket.brokerUrl; if (!this._client) { + // eslint-disable-next-line no-console console.log(`Connecting to WebSocket at ${_uri}/${this._channel}`); this._client = new WebSocket(`${_uri}/${this._channel}`); @@ -46,12 +48,13 @@ export class WebsocketClient { } } } catch (error) { - // + const _err = error instanceof Error ? error : new Error(error as any); + Errors.websocket(_err); } } } -function _onOpen(ev: WebSocket.Event) { +function _onOpen(_: WebSocket.Event) { // eslint-disable-next-line no-console // console.log('WebSocket connection opened:', ev); } @@ -70,8 +73,6 @@ function _onMessage(this: WebSocket, event: WebSocket.MessageEvent) { return; } - // const message = JSON.parse(event.data) as WebSocketMessage; - if (message.type === 'event' && message.data.event === 'new_server') { // eslint-disable-next-line no-console console.log('WebSocket registered message:', message); @@ -83,25 +84,25 @@ function _onMessage(this: WebSocket, event: WebSocket.MessageEvent) { console.log('WebSocket log message:', message); const data = message.data as LogQuickTacToe; - const repository = new LogsRepository(new MongoDB()); + const repository = new LogsRepository(MongoDB.initialize()); repository.addLog(data.gameKey, data); return; } } -function _onClose(ev: WebSocket.Event) { +function _onClose(_: WebSocket.Event) { // eslint-disable-next-line no-console - console.log('WebSocket connection closed:', ev); + // console.log('WebSocket connection closed:', ev); setTimeout(() => { WebsocketClient.reconnect(); }, 5000); } -function _onError(ev: WebSocket.Event) { +function _onError(_: WebSocket.Event) { // eslint-disable-next-line no-console - console.error('WebSocket error occurred:', ev); + // console.error('WebSocket error occurred:', ev); setTimeout(() => { WebsocketClient.reconnect(); diff --git a/tsconfig.json b/tsconfig.json index 1a6f821..cdbbbbb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,18 +10,39 @@ ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, "declaration": true, // Generate d.ts files "paths": { - "@constants": ["./src/_constants"], - "@constants/game_types": ["./src/_constants/_games"], - "@databases": ["./src/databases"], - "@controllers": ["./src/controllers"], - "@models": ["./src/models"], - "@repositories": ["./src/repositories"], - "@routes": ["./src/routes.ts"], + "@constants": [ + "./src/_constants" + ], + "@constants/game_types": [ + "./src/_constants/_games" + ], + "@databases": [ + "./src/databases" + ], + "@controllers": [ + "./src/controllers" + ], + "@models": [ + "./src/models" + ], + "@repositories": [ + "./src/repositories" + ], + "@routes": [ + "./src/routes.ts" + ], + "@errors": [ + "./src/errors" + ], // "@services": [ // "./src/services" // ], - "@utils": ["./src/utils"], - "@config": ["./src/_config"] + "@utils": [ + "./src/utils" + ], + "@config": [ + "./src/_config" + ] // "@middlewares": ["./src/middlewares"], // "@interfaces": ["./src/interfaces"], // "@types": ["./src/types"], @@ -96,10 +117,14 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, - "exclude": ["_website/*", "dist/*", "website/*"], + "exclude": [ + "_website/*", + "dist/*", + "website/*" + ], "ts-node": { "experimentalSpecifierResolution": "node", "transpileOnly": true, "esm": true } -} +} \ No newline at end of file