npm-noxyai 1.0.9 → 1.0.10
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 +153 -131
- package/index.html +20 -6
- package/package.json +1 -1
- package/script.js +28 -82
- package/tic_tac_toe_local.py +46 -0
package/index-noxyai.js
CHANGED
|
@@ -9,9 +9,25 @@ const readline = require('readline');
|
|
|
9
9
|
const BASE_URL = 'https://www.noxyai.com';
|
|
10
10
|
const CONFIG_FILE = path.join(os.homedir(), '.noxyai.json');
|
|
11
11
|
|
|
12
|
+
const GOOGLE_KEY = "AIzaSyAXoZgwIaEnXfO3JuKIvR8GzhydqRKPh20";
|
|
13
|
+
const GOOGLE_CX = "33d4204810fae4852";
|
|
14
|
+
|
|
12
15
|
const args = process.argv.slice(2);
|
|
13
16
|
const command = args[0];
|
|
14
17
|
|
|
18
|
+
// --- CONFIG MANAGEMENT ---
|
|
19
|
+
function loadConfig() {
|
|
20
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
21
|
+
try { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch(e){}
|
|
22
|
+
}
|
|
23
|
+
return { token: null, model: 'auto' };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function saveConfig(config) {
|
|
27
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// --- UI HELPERS ---
|
|
15
31
|
function printLogo() {
|
|
16
32
|
console.log('\x1b[36m' + `
|
|
17
33
|
███╗ ██╗ ██████╗ ██╗ ██╗██╗ ██╗ █████╗ ██╗
|
|
@@ -23,40 +39,38 @@ function printLogo() {
|
|
|
23
39
|
` + '\x1b[0m');
|
|
24
40
|
}
|
|
25
41
|
|
|
26
|
-
|
|
27
|
-
|
|
42
|
+
let spinnerInterval;
|
|
43
|
+
function startSpinner(text) {
|
|
44
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
45
|
+
let i = 0;
|
|
46
|
+
process.stdout.write('\x1b[?25l'); // Hide cursor
|
|
47
|
+
spinnerInterval = setInterval(() => {
|
|
48
|
+
process.stdout.write(`\r\x1b[36m${frames[i]} ${text}\x1b[0m`);
|
|
49
|
+
i = (i + 1) % frames.length;
|
|
50
|
+
}, 80);
|
|
28
51
|
}
|
|
29
52
|
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return config.token;
|
|
34
|
-
}
|
|
35
|
-
return null;
|
|
53
|
+
function stopSpinner() {
|
|
54
|
+
clearInterval(spinnerInterval);
|
|
55
|
+
process.stdout.write('\r\x1b[K\x1b[?25h'); // Clear line, show cursor
|
|
36
56
|
}
|
|
37
57
|
|
|
38
58
|
function logout() {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
console.log('You are already logged out.');
|
|
44
|
-
}
|
|
59
|
+
const config = loadConfig();
|
|
60
|
+
config.token = null;
|
|
61
|
+
saveConfig(config);
|
|
62
|
+
console.log('✅ Successfully logged out.');
|
|
45
63
|
}
|
|
46
64
|
|
|
47
65
|
function openBrowser(url) {
|
|
48
66
|
const platform = os.platform();
|
|
49
|
-
if (platform === 'android') {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
} else if (platform === 'win32') {
|
|
54
|
-
spawn('cmd.exe', ['/c', 'start', '""', url], { stdio: 'ignore' });
|
|
55
|
-
} else {
|
|
56
|
-
spawn('xdg-open', [url], { stdio: 'ignore' });
|
|
57
|
-
}
|
|
67
|
+
if (platform === 'android') spawn('termux-open-url', [url], { stdio: 'ignore' });
|
|
68
|
+
else if (platform === 'darwin') spawn('open', [url], { stdio: 'ignore' });
|
|
69
|
+
else if (platform === 'win32') spawn('cmd.exe', ['/c', 'start', '""', url], { stdio: 'ignore' });
|
|
70
|
+
else spawn('xdg-open', [url], { stdio: 'ignore' });
|
|
58
71
|
}
|
|
59
72
|
|
|
73
|
+
// --- CORE FUNCTIONS ---
|
|
60
74
|
async function login() {
|
|
61
75
|
printLogo();
|
|
62
76
|
console.log('Initializing secure login...\n');
|
|
@@ -72,7 +86,7 @@ async function login() {
|
|
|
72
86
|
rl.question('Press ENTER to automatically open the browser...', () => {
|
|
73
87
|
openBrowser(verificationUrl);
|
|
74
88
|
rl.close();
|
|
75
|
-
|
|
89
|
+
startSpinner('Waiting for authentication...');
|
|
76
90
|
});
|
|
77
91
|
|
|
78
92
|
const pollInterval = setInterval(async () => {
|
|
@@ -87,7 +101,11 @@ async function login() {
|
|
|
87
101
|
|
|
88
102
|
if (data.status === 'success') {
|
|
89
103
|
clearInterval(pollInterval);
|
|
90
|
-
|
|
104
|
+
stopSpinner();
|
|
105
|
+
const config = loadConfig();
|
|
106
|
+
config.token = data.token;
|
|
107
|
+
saveConfig(config);
|
|
108
|
+
|
|
91
109
|
console.clear();
|
|
92
110
|
printLogo();
|
|
93
111
|
console.log('✅ Login successful! Terminal connected.');
|
|
@@ -95,6 +113,7 @@ async function login() {
|
|
|
95
113
|
process.exit(0);
|
|
96
114
|
} else if (data.error) {
|
|
97
115
|
clearInterval(pollInterval);
|
|
116
|
+
stopSpinner();
|
|
98
117
|
console.error('\n❌ Login failed: ' + data.error);
|
|
99
118
|
process.exit(1);
|
|
100
119
|
}
|
|
@@ -109,16 +128,11 @@ async function login() {
|
|
|
109
128
|
function runTerminalCommand(cmd) {
|
|
110
129
|
return new Promise((resolve, reject) => {
|
|
111
130
|
console.log(`\n\x1b[33m⚡ Running:\x1b[0m ${cmd}\n`);
|
|
112
|
-
|
|
113
131
|
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
|
-
});
|
|
132
|
+
const child = spawn(cmd, { shell: true, stdio: 'inherit' });
|
|
119
133
|
|
|
120
134
|
if (isServer) {
|
|
121
|
-
console.log(`\x1b[36m[i] Server detected.
|
|
135
|
+
console.log(`\x1b[36m[i] Server detected. Running in foreground. Press Ctrl+C to stop.\x1b[0m`);
|
|
122
136
|
setTimeout(() => resolve(), 2500);
|
|
123
137
|
}
|
|
124
138
|
|
|
@@ -128,53 +142,38 @@ function runTerminalCommand(cmd) {
|
|
|
128
142
|
else resolve();
|
|
129
143
|
}
|
|
130
144
|
});
|
|
131
|
-
|
|
132
145
|
child.on('error', (error) => reject(error.message));
|
|
133
146
|
});
|
|
134
147
|
}
|
|
135
148
|
|
|
136
149
|
function getLocalContext() {
|
|
137
150
|
try {
|
|
138
|
-
const files = fs.readdirSync(process.cwd()).filter(f => !f.startsWith('node_modules') && !f.startsWith('.git')).slice(0,
|
|
139
|
-
return `\n[SYSTEM
|
|
140
|
-
} catch (e) {
|
|
141
|
-
return "";
|
|
142
|
-
}
|
|
151
|
+
const files = fs.readdirSync(process.cwd()).filter(f => !f.startsWith('node_modules') && !f.startsWith('.git')).slice(0, 30);
|
|
152
|
+
return `\n[SYSTEM: You are in ${process.cwd()}. Files here: ${files.join(', ')}]\n`;
|
|
153
|
+
} catch (e) { return ""; }
|
|
143
154
|
}
|
|
144
155
|
|
|
145
156
|
async function chat(prompt, depth = 0) {
|
|
146
|
-
const
|
|
147
|
-
if (!token) {
|
|
148
|
-
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (depth > 3) {
|
|
153
|
-
console.error('\n❌ Agent reached maximum retry depth. Please fix the error manually.');
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
157
|
+
const config = loadConfig();
|
|
158
|
+
if (!config.token) { console.error('❌ Unauthorized: Run "noxyai login"'); return; }
|
|
159
|
+
if (depth > 6) { console.error('\n❌ Reached max reasoning depth. Stopping.'); return; }
|
|
156
160
|
|
|
157
161
|
const enhancedPrompt = depth === 0 ? getLocalContext() + prompt : prompt;
|
|
158
|
-
|
|
162
|
+
|
|
163
|
+
startSpinner(depth === 0 ? 'NoxyAI is thinking...' : 'NoxyAI is analyzing results...');
|
|
159
164
|
|
|
160
165
|
try {
|
|
161
166
|
const res = await fetch(BASE_URL + '/api/io', {
|
|
162
167
|
method: 'POST',
|
|
163
|
-
headers: {
|
|
164
|
-
|
|
165
|
-
'Authorization': 'Bearer ' + token
|
|
166
|
-
},
|
|
167
|
-
body: JSON.stringify({ prompt: enhancedPrompt, model })
|
|
168
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + config.token },
|
|
169
|
+
body: JSON.stringify({ prompt: enhancedPrompt, model: config.model })
|
|
168
170
|
});
|
|
169
171
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
172
|
+
stopSpinner();
|
|
173
|
+
|
|
174
|
+
if (!res.ok) return console.error('\n❌ API Error: ' + await res.text());
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
else console.log('\n🤖 \x1b[36mNoxyAI\x1b[0m is analyzing the error and rewriting...\n');
|
|
176
|
+
console.log(`\n🤖 \x1b[36mNoxyAI (${config.model}):\x1b[0m\n`);
|
|
178
177
|
|
|
179
178
|
const reader = res.body.getReader();
|
|
180
179
|
const decoder = new TextDecoder('utf-8');
|
|
@@ -183,10 +182,7 @@ async function chat(prompt, depth = 0) {
|
|
|
183
182
|
while (true) {
|
|
184
183
|
const { done, value } = await reader.read();
|
|
185
184
|
if (done) break;
|
|
186
|
-
|
|
187
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
188
|
-
const lines = chunk.split('\n');
|
|
189
|
-
|
|
185
|
+
const lines = decoder.decode(value, { stream: true }).split('\n');
|
|
190
186
|
for (const line of lines) {
|
|
191
187
|
if (line.startsWith('data: ')) {
|
|
192
188
|
const data = line.slice(6).trim();
|
|
@@ -203,110 +199,136 @@ async function chat(prompt, depth = 0) {
|
|
|
203
199
|
}
|
|
204
200
|
|
|
205
201
|
console.log('\n\n\x1b[32m[Agent] Processing actions...\x1b[0m');
|
|
202
|
+
let agentFeedback = "";
|
|
206
203
|
|
|
207
|
-
|
|
208
|
-
|
|
204
|
+
// Tool: Read Files
|
|
205
|
+
const readRegex = /<read>([\s\S]*?)<\/read>/g;
|
|
206
|
+
let match;
|
|
207
|
+
while ((match = readRegex.exec(fullResponse)) !== null) {
|
|
208
|
+
const filePath = match[1].trim();
|
|
209
|
+
try {
|
|
210
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
211
|
+
console.log(`\x1b[34m📖 Read file:\x1b[0m ${filePath}`);
|
|
212
|
+
agentFeedback += `\n[FILE: ${filePath}]\n\`\`\`\n${content}\n\`\`\`\n`;
|
|
213
|
+
} catch (err) {
|
|
214
|
+
console.log(`\x1b[31m❌ Failed to read:\x1b[0m ${filePath}`);
|
|
215
|
+
agentFeedback += `\n[ERROR reading ${filePath}: ${err.message}]\n`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
209
218
|
|
|
219
|
+
// Tool: Search Web
|
|
220
|
+
const searchRegex = /<search>([\s\S]*?)<\/search>/g;
|
|
221
|
+
while ((match = searchRegex.exec(fullResponse)) !== null) {
|
|
222
|
+
const query = match[1].trim();
|
|
223
|
+
console.log(`\x1b[35m🔍 Searching Web:\x1b[0m ${query}`);
|
|
224
|
+
try {
|
|
225
|
+
const searchRes = await fetch(`https://www.googleapis.com/customsearch/v1?key=${GOOGLE_KEY}&cx=${GOOGLE_CX}&q=${encodeURIComponent(query)}`);
|
|
226
|
+
const searchData = await searchRes.json();
|
|
227
|
+
const snippets = searchData.items ? searchData.items.map(i => `- ${i.title}: ${i.snippet}`).join('\n') : "No results.";
|
|
228
|
+
agentFeedback += `\n[SEARCH RESULTS FOR "${query}"]\n${snippets}\n`;
|
|
229
|
+
} catch (err) {
|
|
230
|
+
agentFeedback += `\n[SEARCH FAILED: ${err.message}]\n`;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Tool: Write Files
|
|
210
235
|
const fileRegex = /<file path="([^"]+)">([\s\S]*?)<\/file>/g;
|
|
211
|
-
let match;
|
|
212
236
|
while ((match = fileRegex.exec(fullResponse)) !== null) {
|
|
213
|
-
const filePath = match[1];
|
|
214
|
-
const content = match[2];
|
|
215
|
-
|
|
237
|
+
const filePath = match[1], content = match[2];
|
|
216
238
|
const dir = path.dirname(filePath);
|
|
217
239
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
218
240
|
fs.writeFileSync(filePath, content.trim());
|
|
219
|
-
console.log(`\x1b[32m✔
|
|
220
|
-
filesCreated++;
|
|
241
|
+
console.log(`\x1b[32m✔ Wrote file:\x1b[0m ${filePath}`);
|
|
221
242
|
}
|
|
222
243
|
|
|
244
|
+
// Tool: Execute Commands
|
|
223
245
|
const execRegex = /<execute>([\s\S]*?)<\/execute>/g;
|
|
224
246
|
const commands = [];
|
|
225
|
-
while ((match = execRegex.exec(fullResponse)) !== null)
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
|
|
247
|
+
while ((match = execRegex.exec(fullResponse)) !== null) commands.push(match[1].trim());
|
|
248
|
+
|
|
229
249
|
for (const cmd of commands) {
|
|
230
|
-
commandsRun++;
|
|
231
250
|
try {
|
|
232
251
|
await runTerminalCommand(cmd);
|
|
233
252
|
} catch (err) {
|
|
234
|
-
console.
|
|
235
|
-
|
|
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.`;
|
|
237
|
-
await chat(errorPrompt, depth + 1);
|
|
238
|
-
return;
|
|
253
|
+
console.log(`\x1b[31m❌ Command Failed:\x1b[0m ${err}`);
|
|
254
|
+
agentFeedback += `\n[COMMAND ERROR running "${cmd}": ${err}]\n`;
|
|
239
255
|
}
|
|
240
256
|
}
|
|
241
257
|
|
|
258
|
+
if (agentFeedback) {
|
|
259
|
+
console.log(`\x1b[33m🔄 Sending data back to Agent...\x1b[0m`);
|
|
260
|
+
await chat(`Here are the results of your actions:\n${agentFeedback}\nWhat is the next step? Output <file> or <execute> if ready, or <read>/<search> if you need more info.`, depth + 1);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
242
264
|
console.log('\n\x1b[32m════════════════════════════════════════\x1b[0m');
|
|
243
|
-
console.log(`\x1b[32m✨ Task Completed
|
|
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`);
|
|
265
|
+
console.log(`\x1b[32m✨ Task Completed!\x1b[0m`);
|
|
247
266
|
console.log('\x1b[32m════════════════════════════════════════\x1b[0m\n');
|
|
248
267
|
|
|
249
|
-
} catch (error) {
|
|
250
|
-
|
|
268
|
+
} catch (error) {
|
|
269
|
+
stopSpinner();
|
|
270
|
+
console.error('\n❌ Connection error: ' + error.message);
|
|
251
271
|
}
|
|
252
272
|
}
|
|
253
273
|
|
|
254
|
-
function showHelp() {
|
|
255
|
-
printLogo();
|
|
256
|
-
console.log(`
|
|
257
|
-
\x1b[33mNoxyAI Agent CLI - Available Commands\x1b[0m
|
|
258
|
-
|
|
259
|
-
\x1b[32mnoxyai\x1b[0m Start Interactive Mode (Recommended)
|
|
260
|
-
\x1b[32mnoxyai login\x1b[0m Authenticate your terminal
|
|
261
|
-
\x1b[32mnoxyai logout\x1b[0m Remove authentication from this device
|
|
262
|
-
\x1b[32mnoxyai chat "<prompt>"\x1b[0m Run a single prompt and exit
|
|
263
|
-
\x1b[32mnoxyai help\x1b[0m Show this menu
|
|
264
|
-
`);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
274
|
function startInteractiveMode() {
|
|
275
|
+
const config = loadConfig();
|
|
268
276
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
269
277
|
printLogo();
|
|
270
278
|
console.log(`\x1b[33mWelcome to NoxyAI Interactive Mode!\x1b[0m`);
|
|
271
|
-
console.log(`
|
|
279
|
+
console.log(`Current Model: \x1b[32m${config.model}\x1b[0m`);
|
|
280
|
+
console.log(`Commands: \x1b[36m/model\x1b[0m (Change Model), \x1b[36m/clear\x1b[0m, \x1b[36m/exit\x1b[0m\n`);
|
|
272
281
|
|
|
273
282
|
function ask() {
|
|
274
283
|
rl.question('\x1b[36mNoxyAI > \x1b[0m', async (input) => {
|
|
275
284
|
const trimmed = input.trim();
|
|
276
|
-
|
|
277
|
-
|
|
285
|
+
const lower = trimmed.toLowerCase();
|
|
286
|
+
|
|
287
|
+
if (lower === 'exit' || lower === '/exit') {
|
|
278
288
|
process.exit(0);
|
|
279
|
-
}
|
|
280
|
-
if (
|
|
289
|
+
}
|
|
290
|
+
else if (lower === '/clear') {
|
|
291
|
+
console.clear();
|
|
292
|
+
printLogo();
|
|
293
|
+
ask();
|
|
294
|
+
}
|
|
295
|
+
else if (lower === '/model') {
|
|
296
|
+
console.log('\n\x1b[33mSelect an AI Model:\x1b[0m');
|
|
297
|
+
console.log(' \x1b[36m1)\x1b[0m Auto (Llama 3.3 70B - Fast/Coding)');
|
|
298
|
+
console.log(' \x1b[36m2)\x1b[0m Qwen3 Next 80B Thinking (Deep Reasoning)');
|
|
299
|
+
console.log(' \x1b[36m3)\x1b[0m GLM 4.7');
|
|
300
|
+
|
|
301
|
+
rl.question('\nEnter number (1-3): ', (choice) => {
|
|
302
|
+
let selected = 'auto';
|
|
303
|
+
if (choice === '2') selected = 'Qwen3';
|
|
304
|
+
if (choice === '3') selected = 'GLM';
|
|
305
|
+
|
|
306
|
+
const currentConfig = loadConfig();
|
|
307
|
+
currentConfig.model = selected;
|
|
308
|
+
saveConfig(currentConfig);
|
|
309
|
+
|
|
310
|
+
console.log(`\n\x1b[32m✔ Model successfully changed to: ${selected}\x1b[0m\n`);
|
|
311
|
+
ask();
|
|
312
|
+
});
|
|
313
|
+
return; // Pause the main loop while waiting for model choice
|
|
314
|
+
}
|
|
315
|
+
else if (trimmed) {
|
|
281
316
|
await chat(trimmed);
|
|
317
|
+
ask();
|
|
318
|
+
} else {
|
|
319
|
+
ask();
|
|
282
320
|
}
|
|
283
|
-
ask();
|
|
284
321
|
});
|
|
285
322
|
}
|
|
286
323
|
ask();
|
|
287
324
|
}
|
|
288
325
|
|
|
289
|
-
|
|
290
|
-
if (command === '
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
process.exit(0);
|
|
295
|
-
} else if (command === 'help' || command === '--help') { // 🚨 BUG FIXED HERE!
|
|
296
|
-
showHelp();
|
|
297
|
-
process.exit(0);
|
|
298
|
-
} else if (command === 'chat') {
|
|
299
|
-
const prompt = args.slice(1).join(' ');
|
|
300
|
-
if (!prompt) {
|
|
301
|
-
console.error('❌ Please provide a prompt. Example: noxyai chat "Create a python script"');
|
|
302
|
-
process.exit(1);
|
|
303
|
-
}
|
|
304
|
-
chat(prompt).then(() => process.exit(0));
|
|
305
|
-
} else if (!command) {
|
|
306
|
-
// Now this actually triggers!
|
|
307
|
-
startInteractiveMode();
|
|
308
|
-
} else {
|
|
309
|
-
console.error(`❌ Unknown command: ${command}`);
|
|
310
|
-
console.log('Run "noxyai help" for a list of commands.');
|
|
311
|
-
process.exit(1);
|
|
326
|
+
if (command === 'login') login();
|
|
327
|
+
else if (command === 'logout') logout();
|
|
328
|
+
else if (command === 'help' || command === '--help') {
|
|
329
|
+
printLogo();
|
|
330
|
+
console.log(`\n \x1b[32mnoxyai\x1b[0m Start Interactive Mode\n \x1b[32mnoxyai chat "<prompt>"\x1b[0m Run single prompt\n`);
|
|
312
331
|
}
|
|
332
|
+
else if (command === 'chat') { chat(args.slice(1).join(' ')).then(() => process.exit(0)); }
|
|
333
|
+
else if (!command) startInteractiveMode();
|
|
334
|
+
else { console.error(`❌ Unknown command. Run "noxyai help"`); process.exit(1); }
|
package/index.html
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
<html lang="en">
|
|
1
|
+
<html>
|
|
3
2
|
<head>
|
|
4
|
-
<
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Snake Game</title>
|
|
3
|
+
<title>Tic Tac Toe</title>
|
|
7
4
|
<link rel="stylesheet" href="style.css">
|
|
8
5
|
</head>
|
|
9
6
|
<body>
|
|
10
|
-
<
|
|
7
|
+
<h1>Tic Tac Toe</h1>
|
|
8
|
+
<div class="game-board">
|
|
9
|
+
<div class="row">
|
|
10
|
+
<div class="cell" id="cell-0"></div>
|
|
11
|
+
<div class="cell" id="cell-1"></div>
|
|
12
|
+
<div class="cell" id="cell-2"></div>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="row">
|
|
15
|
+
<div class="cell" id="cell-3"></div>
|
|
16
|
+
<div class="cell" id="cell-4"></div>
|
|
17
|
+
<div class="cell" id="cell-5"></div>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="row">
|
|
20
|
+
<div class="cell" id="cell-6"></div>
|
|
21
|
+
<div class="cell" id="cell-7"></div>
|
|
22
|
+
<div class="cell" id="cell-8"></div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
11
25
|
<script src="script.js"></script>
|
|
12
26
|
</body>
|
|
13
27
|
</html>
|
package/package.json
CHANGED
package/script.js
CHANGED
|
@@ -1,92 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
let currentPlayer = "X";
|
|
2
|
+
let gameBoard = ["", "", "", "", "", "", "", "", ""];
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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);
|
|
4
|
+
function clickCell(cellId) {
|
|
5
|
+
if (gameBoard[cellId] === "") {
|
|
6
|
+
gameBoard[cellId] = currentPlayer;
|
|
7
|
+
document.getElementById(`cell-${cellId}`).innerText = currentPlayer;
|
|
8
|
+
checkWin();
|
|
9
|
+
currentPlayer = currentPlayer === "X" ? "O" : "X";
|
|
21
10
|
}
|
|
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
11
|
}
|
|
30
12
|
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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';
|
|
13
|
+
function checkWin() {
|
|
14
|
+
const winConditions = [
|
|
15
|
+
[0, 1, 2],
|
|
16
|
+
[3, 4, 5],
|
|
17
|
+
[6, 7, 8],
|
|
18
|
+
[0, 3, 6],
|
|
19
|
+
[1, 4, 7],
|
|
20
|
+
[2, 5, 8],
|
|
21
|
+
[0, 4, 8],
|
|
22
|
+
[2, 4, 6]
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < winConditions.length; i++) {
|
|
26
|
+
const [a, b, c] = winConditions[i];
|
|
27
|
+
if (gameBoard[a] !== "" && gameBoard[a] === gameBoard[b] && gameBoard[b] === gameBoard[c]) {
|
|
28
|
+
alert(`Player ${gameBoard[a]} wins!`);
|
|
29
|
+
return;
|
|
61
30
|
}
|
|
62
31
|
}
|
|
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
32
|
}
|
|
76
33
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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';
|
|
34
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
35
|
+
for (let i = 0; i < 9; i++) {
|
|
36
|
+
document.getElementById(`cell-${i}`).addEventListener("click", () => clickCell(i));
|
|
91
37
|
}
|
|
92
38
|
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter import messagebox
|
|
3
|
+
|
|
4
|
+
class TicTacToe:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.window = tk.Tk()
|
|
7
|
+
self.window.title("Tic Tac Toe")
|
|
8
|
+
self.window.geometry("300x300")
|
|
9
|
+
self.player_turn = "X"
|
|
10
|
+
|
|
11
|
+
self.buttons = []
|
|
12
|
+
for i in range(3):
|
|
13
|
+
row = []
|
|
14
|
+
for j in range(3):
|
|
15
|
+
button = tk.Button(self.window, command=lambda row=i, column=j: self.click(row, column), height=3, width=6)
|
|
16
|
+
button.grid(row=i, column=j)
|
|
17
|
+
row.append(button)
|
|
18
|
+
self.buttons.append(row)
|
|
19
|
+
|
|
20
|
+
def click(self, row, column):
|
|
21
|
+
if self.buttons[row][column]['text'] == "":
|
|
22
|
+
self.buttons[row][column]['text'] = self.player_turn
|
|
23
|
+
if self.check_win():
|
|
24
|
+
messagebox.showinfo("Game Over", f"Player {self.player_turn} wins!")
|
|
25
|
+
self.window.quit()
|
|
26
|
+
self.player_turn = "O" if self.player_turn == "X" else "X"
|
|
27
|
+
|
|
28
|
+
def check_win(self):
|
|
29
|
+
for row in self.buttons:
|
|
30
|
+
if row[0]['text'] == row[1]['text'] == row[2]['text'] != "":
|
|
31
|
+
return True
|
|
32
|
+
for column in range(3):
|
|
33
|
+
if self.buttons[0][column]['text'] == self.buttons[1][column]['text'] == self.buttons[2][column]['text'] != "":
|
|
34
|
+
return True
|
|
35
|
+
if self.buttons[0][0]['text'] == self.buttons[1][1]['text'] == self.buttons[2][2]['text'] != "":
|
|
36
|
+
return True
|
|
37
|
+
if self.buttons[0][2]['text'] == self.buttons[1][1]['text'] == self.buttons[2][0]['text'] != "":
|
|
38
|
+
return True
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
def run(self):
|
|
42
|
+
self.window.mainloop()
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
game = TicTacToe()
|
|
46
|
+
game.run()
|