npm-noxyai 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index-noxyai.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const os = require('os');
5
5
  const path = require('path');
6
- const { exec } = require('child_process');
6
+ const { spawn } = require('child_process');
7
7
  const readline = require('readline');
8
8
 
9
9
  const BASE_URL = 'https://www.noxyai.com';
@@ -12,7 +12,6 @@ const CONFIG_FILE = path.join(os.homedir(), '.noxyai.json');
12
12
  const args = process.argv.slice(2);
13
13
  const command = args[0];
14
14
 
15
- // --- HELPER: ASCII ART ---
16
15
  function printLogo() {
17
16
  console.log('\x1b[36m' + `
18
17
  ███╗ ██╗ ██████╗ ██╗ ██╗██╗ ██╗ █████╗ ██╗
@@ -24,7 +23,6 @@ function printLogo() {
24
23
  ` + '\x1b[0m');
25
24
  }
26
25
 
27
- // --- HELPER: AUTHENTICATION ---
28
26
  function saveToken(token) {
29
27
  fs.writeFileSync(CONFIG_FILE, JSON.stringify({ token }));
30
28
  }
@@ -44,24 +42,21 @@ function logout() {
44
42
  } else {
45
43
  console.log('You are already logged out.');
46
44
  }
47
- process.exit(0);
48
45
  }
49
46
 
50
- // --- HELPER: AUTO-OPEN URL ---
51
47
  function openBrowser(url) {
52
48
  const platform = os.platform();
53
49
  if (platform === 'android') {
54
- exec(`termux-open-url "${url}"`);
50
+ spawn('termux-open-url', [url], { stdio: 'ignore' });
55
51
  } else if (platform === 'darwin') {
56
- exec(`open "${url}"`);
52
+ spawn('open', [url], { stdio: 'ignore' });
57
53
  } else if (platform === 'win32') {
58
- exec(`start "" "${url}"`);
54
+ spawn('cmd.exe', ['/c', 'start', '""', url], { stdio: 'ignore' });
59
55
  } else {
60
- exec(`xdg-open "${url}"`);
56
+ spawn('xdg-open', [url], { stdio: 'ignore' });
61
57
  }
62
58
  }
63
59
 
64
- // --- COMMAND: LOGIN ---
65
60
  async function login() {
66
61
  printLogo();
67
62
  console.log('Initializing secure login...\n');
@@ -93,11 +88,10 @@ async function login() {
93
88
  if (data.status === 'success') {
94
89
  clearInterval(pollInterval);
95
90
  saveToken(data.token);
96
-
97
91
  console.clear();
98
92
  printLogo();
99
93
  console.log('✅ Login successful! Terminal connected.');
100
- console.log('Type "noxyai help" for commands.\n');
94
+ console.log('Type "noxyai" to start interactive mode.\n');
101
95
  process.exit(0);
102
96
  } else if (data.error) {
103
97
  clearInterval(pollInterval);
@@ -112,37 +106,55 @@ async function login() {
112
106
  }
113
107
  }
114
108
 
115
- // --- HELPER: EXECUTE COMMANDS & ERROR HANDLING ---
116
109
  function runTerminalCommand(cmd) {
117
110
  return new Promise((resolve, reject) => {
118
- console.log(`\n\x1b[33m⚡ Running:\x1b[0m ${cmd}`);
119
- const child = exec(cmd, (error, stdout, stderr) => {
120
- if (error) {
121
- reject(stderr || error.message);
122
- return;
111
+ console.log(`\n\x1b[33m⚡ Running:\x1b[0m ${cmd}\n`);
112
+
113
+ const isServer = cmd.includes('dev') || cmd.includes('serve') || cmd.includes('host') || cmd.includes('start');
114
+
115
+ const child = spawn(cmd, {
116
+ shell: true,
117
+ stdio: 'inherit'
118
+ });
119
+
120
+ if (isServer) {
121
+ console.log(`\x1b[36m[i] Server detected. It will run in the foreground. Press Ctrl+C to stop it.\x1b[0m`);
122
+ setTimeout(() => resolve(), 2500);
123
+ }
124
+
125
+ child.on('close', (code) => {
126
+ if (!isServer) {
127
+ if (code !== 0) reject(`Command failed with exit code ${code}`);
128
+ else resolve();
123
129
  }
124
- resolve(stdout);
125
130
  });
126
-
127
- // Pipe output live to the terminal so user can see servers/logs
128
- child.stdout.pipe(process.stdout);
129
- child.stderr.pipe(process.stderr);
131
+
132
+ child.on('error', (error) => reject(error.message));
130
133
  });
131
134
  }
132
135
 
133
- // --- COMMAND: CHAT & AGENT EXECUTION ---
136
+ function getLocalContext() {
137
+ try {
138
+ const files = fs.readdirSync(process.cwd()).filter(f => !f.startsWith('node_modules') && !f.startsWith('.git')).slice(0, 20);
139
+ return `\n[SYSTEM CONTEXT: You are running inside the user's terminal. Current directory: ${process.cwd()}. Existing files here: ${files.join(', ')}. You can modify existing files or create new ones.]\n`;
140
+ } catch (e) {
141
+ return "";
142
+ }
143
+ }
144
+
134
145
  async function chat(prompt, depth = 0) {
135
146
  const token = getToken();
136
147
  if (!token) {
137
148
  console.error('❌ Unauthorized: You must log in first. Run "noxyai login"');
138
- process.exit(1);
149
+ return;
139
150
  }
140
151
 
141
152
  if (depth > 3) {
142
153
  console.error('\n❌ Agent reached maximum retry depth. Please fix the error manually.');
143
- process.exit(1);
154
+ return;
144
155
  }
145
156
 
157
+ const enhancedPrompt = depth === 0 ? getLocalContext() + prompt : prompt;
146
158
  const model = 'auto';
147
159
 
148
160
  try {
@@ -152,13 +164,13 @@ async function chat(prompt, depth = 0) {
152
164
  'Content-Type': 'application/json',
153
165
  'Authorization': 'Bearer ' + token
154
166
  },
155
- body: JSON.stringify({ prompt, model })
167
+ body: JSON.stringify({ prompt: enhancedPrompt, model })
156
168
  });
157
169
 
158
170
  if (!res.ok) {
159
171
  const errorText = await res.text();
160
172
  console.error('\n❌ API Error: ' + errorText);
161
- process.exit(1);
173
+ return;
162
174
  }
163
175
 
164
176
  if (depth === 0) console.log('\n🤖 \x1b[36mNoxyAI\x1b[0m is thinking...\n');
@@ -190,9 +202,11 @@ async function chat(prompt, depth = 0) {
190
202
  }
191
203
  }
192
204
 
193
- console.log('\n\n\x1b[32m[Agent] Response complete. Processing actions...\x1b[0m');
205
+ console.log('\n\n\x1b[32m[Agent] Processing actions...\x1b[0m');
206
+
207
+ let filesCreated = 0;
208
+ let commandsRun = 0;
194
209
 
195
- // 1. EXTRACT AND CREATE FILES
196
210
  const fileRegex = /<file path="([^"]+)">([\s\S]*?)<\/file>/g;
197
211
  let match;
198
212
  while ((match = fileRegex.exec(fullResponse)) !== null) {
@@ -200,14 +214,12 @@ async function chat(prompt, depth = 0) {
200
214
  const content = match[2];
201
215
 
202
216
  const dir = path.dirname(filePath);
203
- if (!fs.existsSync(dir)) {
204
- fs.mkdirSync(dir, { recursive: true });
205
- }
217
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
206
218
  fs.writeFileSync(filePath, content.trim());
207
- console.log(`\x1b[32m✔ Created file:\x1b[0m ${filePath}`);
219
+ console.log(`\x1b[32m✔ Created/Modified file:\x1b[0m ${filePath}`);
220
+ filesCreated++;
208
221
  }
209
222
 
210
- // 2. EXTRACT AND RUN COMMANDS
211
223
  const execRegex = /<execute>([\s\S]*?)<\/execute>/g;
212
224
  const commands = [];
213
225
  while ((match = execRegex.exec(fullResponse)) !== null) {
@@ -215,60 +227,86 @@ async function chat(prompt, depth = 0) {
215
227
  }
216
228
 
217
229
  for (const cmd of commands) {
230
+ commandsRun++;
218
231
  try {
219
232
  await runTerminalCommand(cmd);
220
233
  } catch (err) {
221
- console.error(`\n\x1b[31m❌ Command Failed:\x1b[0m ${err.trim()}`);
234
+ console.error(`\n\x1b[31m❌ Command Failed:\x1b[0m ${err}`);
222
235
  console.log(`\x1b[33m🔄 Triggering Auto-Heal Loop...\x1b[0m`);
223
-
224
- const errorPrompt = `I ran the command "${cmd}" and got this error:\n\n${err}\n\nPlease fix the issue. If you need to rewrite a file, use the <file> tags. If you need to install a missing library, use the <execute> tag.`;
225
-
236
+ const errorPrompt = `I ran the command "${cmd}" and got this error:\n\n${err}\n\nPlease fix the issue. Use <file> tags to rewrite files or <execute> to run commands.`;
226
237
  await chat(errorPrompt, depth + 1);
227
238
  return;
228
239
  }
229
240
  }
230
241
 
231
- process.exit(0);
242
+ console.log('\n\x1b[32m════════════════════════════════════════\x1b[0m');
243
+ console.log(`\x1b[32m✨ Task Completed Successfully!\x1b[0m`);
244
+ if (filesCreated > 0) console.log(`📄 Modified ${filesCreated} file(s)`);
245
+ if (commandsRun > 0) console.log(`🚀 Executed ${commandsRun} command(s)`);
246
+ if (filesCreated === 0 && commandsRun === 0) console.log(`💬 Answered query`);
247
+ console.log('\x1b[32m════════════════════════════════════════\x1b[0m\n');
232
248
 
233
249
  } catch (error) {
234
250
  console.error('\n❌ Connection error: ' + error.message);
235
- process.exit(1);
236
251
  }
237
252
  }
238
253
 
239
- // --- COMMAND: HELP ---
240
254
  function showHelp() {
241
255
  printLogo();
242
256
  console.log(`
243
257
  \x1b[33mNoxyAI Agent CLI - Available Commands\x1b[0m
244
258
 
259
+ \x1b[32mnoxyai\x1b[0m Start Interactive Mode (Recommended)
245
260
  \x1b[32mnoxyai login\x1b[0m Authenticate your terminal
246
261
  \x1b[32mnoxyai logout\x1b[0m Remove authentication from this device
247
- \x1b[32mnoxyai chat "<prompt>"\x1b[0m Chat with the AI. It can build apps, create files, and fix errors.
262
+ \x1b[32mnoxyai chat "<prompt>"\x1b[0m Run a single prompt and exit
248
263
  \x1b[32mnoxyai help\x1b[0m Show this menu
249
-
250
- \x1b[33mExamples:\x1b[0m
251
- noxyai chat "Create a snake game in python and run it"
252
- noxyai chat "Initialize a react app called my-app"
253
- noxyai chat "Create an express server that returns hello world on port 3000"
254
264
  `);
255
265
  }
256
266
 
267
+ function startInteractiveMode() {
268
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
269
+ printLogo();
270
+ console.log(`\x1b[33mWelcome to NoxyAI Interactive Mode!\x1b[0m`);
271
+ console.log(`Type your prompt and press Enter. Type 'exit' to quit.\n`);
272
+
273
+ function ask() {
274
+ rl.question('\x1b[36mNoxyAI > \x1b[0m', async (input) => {
275
+ const trimmed = input.trim();
276
+ if (trimmed.toLowerCase() === 'exit' || trimmed.toLowerCase() === 'quit') {
277
+ console.log('Goodbye!');
278
+ process.exit(0);
279
+ }
280
+ if (trimmed) {
281
+ await chat(trimmed);
282
+ }
283
+ ask();
284
+ });
285
+ }
286
+ ask();
287
+ }
288
+
257
289
  // --- ROUTER ---
258
290
  if (command === 'login') {
259
291
  login();
260
292
  } else if (command === 'logout') {
261
293
  logout();
262
- } else if (command === 'help' || command === '--help' || !command) {
294
+ process.exit(0);
295
+ } else if (command === 'help' || command === '--help') { // 🚨 BUG FIXED HERE!
263
296
  showHelp();
297
+ process.exit(0);
264
298
  } else if (command === 'chat') {
265
299
  const prompt = args.slice(1).join(' ');
266
300
  if (!prompt) {
267
301
  console.error('❌ Please provide a prompt. Example: noxyai chat "Create a python script"');
268
302
  process.exit(1);
269
303
  }
270
- chat(prompt);
304
+ chat(prompt).then(() => process.exit(0));
305
+ } else if (!command) {
306
+ // Now this actually triggers!
307
+ startInteractiveMode();
271
308
  } else {
272
309
  console.error(`❌ Unknown command: ${command}`);
273
310
  console.log('Run "noxyai help" for a list of commands.');
311
+ process.exit(1);
274
312
  }
package/index.html ADDED
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Snake Game</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ </head>
9
+ <body>
10
+ <canvas id="gameCanvas" width="400" height="400"></canvas>
11
+ <script src="script.js"></script>
12
+ </body>
13
+ </html>
package/package.json CHANGED
@@ -1,11 +1,20 @@
1
1
  {
2
2
  "name": "npm-noxyai",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "CLI for NoxyAI",
5
5
  "main": "index-noxyai.js",
6
6
  "bin": {
7
7
  "noxyai": "index-noxyai.js"
8
8
  },
9
9
  "author": "Mohammad Junaid Rather",
10
- "license": "ISC"
10
+ "license": "ISC",
11
+ "scripts": {
12
+ "test": "echo \"Error: no test specified\" && exit 1",
13
+ "start": "node server.js"
14
+ },
15
+ "keywords": [],
16
+ "type": "commonjs",
17
+ "dependencies": {
18
+ "express": "^5.2.1"
19
+ }
11
20
  }
@@ -0,0 +1 @@
1
+ import pip; pip.uninstall('pip'); import os; os.system('python -m ensurepip')
package/script.js ADDED
@@ -0,0 +1,92 @@
1
+ const canvas = document.getElementById('gameCanvas');
2
+ const ctx = canvas.getContext('2d');
3
+
4
+ let snake = [
5
+ {x: 200, y: 200},
6
+ {x: 190, y: 200},
7
+ {x: 180, y: 200},
8
+ {x: 170, y: 200},
9
+ {x: 160, y: 200}
10
+ ];
11
+
12
+ let direction = 'right';
13
+ let score = 0;
14
+ let food = {x: Math.floor(Math.random() * 40) * 10, y: Math.floor(Math.random() * 40) * 10};
15
+
16
+ function draw() {
17
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
18
+ for (let i = 0; i < snake.length; i++) {
19
+ ctx.fillStyle = 'green';
20
+ ctx.fillRect(snake[i].x, snake[i].y, 10, 10);
21
+ }
22
+ ctx.fillStyle = 'red';
23
+ ctx.fillRect(food.x, food.y, 10, 10);
24
+ ctx.fillStyle = 'black';
25
+ ctx.font = '24px Arial';
26
+ ctx.textAlign = 'left';
27
+ ctx.textBaseline = 'top';
28
+ ctx.fillText('Score: ' + score, 10, 10);
29
+ }
30
+
31
+ function update() {
32
+ for (let i = snake.length - 1; i > 0; i--) {
33
+ snake[i] = {x: snake[i - 1].x, y: snake[i - 1].y};
34
+ }
35
+ if (direction === 'right') {
36
+ snake[0].x += 10;
37
+ } else if (direction === 'left') {
38
+ snake[0].x -= 10;
39
+ } else if (direction === 'up') {
40
+ snake[0].y -= 10;
41
+ } else if (direction === 'down') {
42
+ snake[0].y += 10;
43
+ }
44
+ if (snake[0].x === food.x && snake[0].y === food.y) {
45
+ score++;
46
+ food = {x: Math.floor(Math.random() * 40) * 10, y: Math.floor(Math.random() * 40) * 10};
47
+ snake.push({x: snake[snake.length - 1].x, y: snake[snake.length - 1].y});
48
+ }
49
+ for (let i = 1; i < snake.length; i++) {
50
+ if (snake[0].x === snake[i].x && snake[0].y === snake[i].y) {
51
+ alert('Game Over');
52
+ score = 0;
53
+ snake = [
54
+ {x: 200, y: 200},
55
+ {x: 190, y: 200},
56
+ {x: 180, y: 200},
57
+ {x: 170, y: 200},
58
+ {x: 160, y: 200}
59
+ ];
60
+ direction = 'right';
61
+ }
62
+ }
63
+ if (snake[0].x < 0 || snake[0].x >= canvas.width || snake[0].y < 0 || snake[0].y >= canvas.height) {
64
+ alert('Game Over');
65
+ score = 0;
66
+ snake = [
67
+ {x: 200, y: 200},
68
+ {x: 190, y: 200},
69
+ {x: 180, y: 200},
70
+ {x: 170, y: 200},
71
+ {x: 160, y: 200}
72
+ ];
73
+ direction = 'right';
74
+ }
75
+ }
76
+
77
+ setInterval(() => {
78
+ update();
79
+ draw();
80
+ }, 100);
81
+
82
+ document.addEventListener('keydown', (e) => {
83
+ if (e.key === 'ArrowRight' && direction !== 'left') {
84
+ direction = 'right';
85
+ } else if (e.key === 'ArrowLeft' && direction !== 'right') {
86
+ direction = 'left';
87
+ } else if (e.key === 'ArrowUp' && direction !== 'down') {
88
+ direction = 'up';
89
+ } else if (e.key === 'ArrowDown' && direction !== 'up') {
90
+ direction = 'down';
91
+ }
92
+ });
package/server.js ADDED
@@ -0,0 +1,11 @@
1
+ const express = require('express');
2
+ const app = express();
3
+ const port = 3000;
4
+
5
+ app.get('/', (req, res) => {
6
+ res.send('Hello World!');
7
+ });
8
+
9
+ app.listen(port, () => {
10
+ console.log(`Server running on port ${port}`);
11
+ });
package/snake_game.py ADDED
@@ -0,0 +1,90 @@
1
+ import pygame
2
+ import sys
3
+ import time
4
+ import random
5
+
6
+ # Direction Constants
7
+ UP = 1
8
+ RIGHT = 2
9
+ DOWN = 3
10
+ LEFT = 4
11
+
12
+ class SnakeGame:
13
+ def __init__(self, width=800, height=600):
14
+ self.width = width
15
+ self.height = height
16
+ self.snake = [(200, 200), (220, 200), (240, 200)]
17
+ self.direction = RIGHT
18
+ self.apple = self.set_new_apple()
19
+ self.score = 0
20
+ pygame.init()
21
+ self.display = pygame.display.set_mode((width, height))
22
+ self.font = pygame.font.Font(None, 36)
23
+
24
+ def set_new_apple(self):
25
+ while True:
26
+ x = random.randint(0, self.width - 20) // 20 * 20
27
+ y = random.randint(0, self.height - 20) // 20 * 20
28
+ apple = (x, y)
29
+ if apple not in self.snake:
30
+ return apple
31
+
32
+ def play(self):
33
+ clock = pygame.time.Clock()
34
+ while True:
35
+ for event in pygame.event.get():
36
+ if event.type == pygame.QUIT:
37
+ pygame.quit()
38
+ sys.exit()
39
+ elif event.type == pygame.KEYDOWN:
40
+ if event.key == pygame.K_UP and self.direction != DOWN:
41
+ self.direction = UP
42
+ elif event.key == pygame.K_DOWN and self.direction != UP:
43
+ self.direction = DOWN
44
+ elif event.key == pygame.K_LEFT and self.direction != RIGHT:
45
+ self.direction = LEFT
46
+ elif event.key == pygame.K_RIGHT and self.direction != LEFT:
47
+ self.direction = RIGHT
48
+
49
+ head = self.snake[-1]
50
+ if self.direction == UP:
51
+ new_head = (head[0], head[1] - 20)
52
+ elif self.direction == DOWN:
53
+ new_head = (head[0], head[1] + 20)
54
+ elif self.direction == LEFT:
55
+ new_head = (head[0] - 20, head[1])
56
+ elif self.direction == RIGHT:
57
+ new_head = (head[0] + 20, head[1])
58
+
59
+ self.snake.append(new_head)
60
+ if self.apple == new_head:
61
+ self.apple = self.set_new_apple()
62
+ self.score += 1
63
+ else:
64
+ self.snake.pop(0)
65
+
66
+ if (new_head[0] < 0 or new_head[0] >= self.width or
67
+ new_head[1] < 0 or new_head[1] >= self.height or
68
+ new_head in self.snake[:-1]):
69
+ break
70
+
71
+ self.display.fill((0, 0, 0))
72
+ for pos in self.snake:
73
+ pygame.draw.rect(self.display, (0, 255, 0), (pos[0], pos[1], 20, 20))
74
+ pygame.draw.rect(self.display, (255, 0, 0), (self.apple[0], self.apple[1], 20, 20))
75
+ text = self.font.render(f'Score: {self.score}', True, (255, 255, 255))
76
+ self.display.blit(text, (10, 10))
77
+ pygame.display.flip()
78
+ clock.tick(10)
79
+
80
+ time.sleep(1)
81
+ self.display.fill((0, 0, 0))
82
+ text = self.font.render('Game Over', True, (255, 255, 255))
83
+ self.display.blit(text, (self.width // 2 - 50, self.height // 2 - 18))
84
+ pygame.display.flip()
85
+ time.sleep(2)
86
+ pygame.quit()
87
+
88
+ if __name__ == '__main__':
89
+ game = SnakeGame()
90
+ game.play()
package/style.css ADDED
@@ -0,0 +1,11 @@
1
+ body {
2
+ display: flex;
3
+ justify-content: center;
4
+ align-items: center;
5
+ height: 100vh;
6
+ background-color: #f0f0f0;
7
+ }
8
+
9
+ #gameCanvas {
10
+ border: 1px solid black;
11
+ }
package/tic_tac_toe.py ADDED
@@ -0,0 +1,111 @@
1
+ import random
2
+
3
+ class TicTacToe:
4
+ def __init__(self):
5
+ self.board = [' ' for _ in range(9)]
6
+
7
+ def print_board(self):
8
+ row1 = '| {} | {} | {} |'.format(self.board[0], self.board[1], self.board[2])
9
+ row2 = '| {} | {} | {} |'.format(self.board[3], self.board[4], self.board[5])
10
+ row3 = '| {} | {} | {} |'.format(self.board[6], self.board[7], self.board[8])
11
+
12
+ print()
13
+ print(row1)
14
+ print(row2)
15
+ print(row3)
16
+ print()
17
+
18
+ def available_moves(self):
19
+ return [i for i, spot in enumerate(self.board) if spot == ' ']
20
+
21
+ def empty_cells(self):
22
+ return ' ' in self.board
23
+
24
+ def num_empty_cells(self):
25
+ return self.board.count(' ')
26
+
27
+ def make_move(self, letter, move):
28
+ self.board[move] = letter
29
+
30
+ def winner(self):
31
+ winning_combos = [(0, 1, 2), (3, 4, 5), (6, 7, 8), (0, 3, 6), (1, 4, 7), (2, 5, 8), (0, 4, 8), (2, 4, 6)]
32
+ for combo in winning_combos:
33
+ if self.board[combo[0]] == self.board[combo[1]] == self.board[combo[2]] != ' ':
34
+ return self.board[combo[0]]
35
+ if ' ' not in self.board:
36
+ return 'Tie'
37
+ return False
38
+
39
+ def minimax(board, depth, is_maximizing):
40
+ result = board.winner()
41
+ if result:
42
+ if result == 'X':
43
+ return -10 + depth
44
+ elif result == 'O':
45
+ return 10 - depth
46
+ elif result == 'Tie':
47
+ return 0
48
+
49
+ if is_maximizing:
50
+ best_score = float('-inf')
51
+ for move in board.available_moves():
52
+ board.make_move('O', move)
53
+ score = minimax(board, depth + 1, False)
54
+ board.board[move] = ' '
55
+ best_score = max(score, best_score)
56
+ return best_score
57
+ else:
58
+ best_score = float('inf')
59
+ for move in board.available_moves():
60
+ board.make_move('X', move)
61
+ score = minimax(board, depth + 1, True)
62
+ board.board[move] = ' '
63
+ best_score = min(score, best_score)
64
+ return best_score
65
+
66
+ def ai_move(board):
67
+ best_score = float('-inf')
68
+ best_move = 0
69
+ for move in board.available_moves():
70
+ board.make_move('O', move)
71
+ score = minimax(board, 0, False)
72
+ board.board[move] = ' '
73
+ if score > best_score:
74
+ best_score = score
75
+ best_move = move
76
+ return best_move
77
+
78
+ def main():
79
+ board = TicTacToe()
80
+ while True:
81
+ board.print_board()
82
+ move = input("Enter your move (1-9): ")
83
+ if board.board[int(move) - 1] != ' ':
84
+ print("Invalid move, try again.")
85
+ continue
86
+ board.make_move('X', int(move) - 1)
87
+ result = board.winner()
88
+ if result:
89
+ board.print_board()
90
+ if result == 'X':
91
+ print("You win!")
92
+ elif result == 'O':
93
+ print("AI wins!")
94
+ else:
95
+ print("It's a tie!")
96
+ break
97
+ move = ai_move(board)
98
+ board.make_move('O', move)
99
+ result = board.winner()
100
+ if result:
101
+ board.print_board()
102
+ if result == 'X':
103
+ print("You win!")
104
+ elif result == 'O':
105
+ print("AI wins!")
106
+ else:
107
+ print("It's a tie!")
108
+ break
109
+
110
+ if __name__ == '__main__':
111
+ main()