diff --git a/task-1/Time.js b/task-1/Time.js index 97ca7e2..cf6e0e6 100644 --- a/task-1/Time.js +++ b/task-1/Time.js @@ -1,3 +1,48 @@ export class Time { - // Your code here -} \ No newline at end of file + #secondsFromMidnight; + + constructor(hours, minutes, seconds) { + this.#secondsFromMidnight = hours * 3600 + minutes * 60 + seconds; + this.#normalize(); + } + + // Private helper to keep seconds in 0..86399 + #normalize() { + this.#secondsFromMidnight %= 86400; + if (this.#secondsFromMidnight < 0) { + this.#secondsFromMidnight += 86400; + } + } + + getHours() { + return Math.floor(this.#secondsFromMidnight / 3600); + } + + getMinutes() { + return Math.floor((this.#secondsFromMidnight % 3600) / 60); + } + + getSeconds() { + return this.#secondsFromMidnight % 60; + } + + addSeconds(seconds) { + this.#secondsFromMidnight += seconds; + this.#normalize(); + } + + addMinutes(minutes) { + this.addSeconds(minutes * 60); + } + + addHours(hours) { + this.addSeconds(hours * 3600); + } + + toString() { + const hh = String(this.getHours()).padStart(2, "0"); + const mm = String(this.getMinutes()).padStart(2, "0"); + const ss = String(this.getSeconds()).padStart(2, "0"); + return `${hh}:${mm}:${ss}`; + } +} diff --git a/task-2/.gitignore b/task-2/.gitignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/task-2/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/task-2/index.js b/task-2/index.js index 02acffa..bb4cd02 100644 --- a/task-2/index.js +++ b/task-2/index.js @@ -1 +1,245 @@ -// Write your code here. You may create as many files as you like. +// =============================================== +// AI QUIZ GAME — GitHub Models Edition +// Final Clean Version with English Comments +// =============================================== + +import OpenAI from "openai"; +import dotenv from "dotenv"; +import readline from "readline"; +import chalk from "chalk"; + +// Load environment variables +dotenv.config(); + +// Read GitHub token +const GITHUB_TOKEN = process.env.GITHUB_TOKEN; + +if (!GITHUB_TOKEN) { + console.error("āš ļø Please set GITHUB_TOKEN in your .env file"); + process.exit(1); +} + +// ---------------------------------------------------- +// Initialize GitHub Models client +// IMPORTANT: baseURL must stop at /inference +// The SDK automatically appends /chat/completions +// ---------------------------------------------------- +const client = new OpenAI({ + baseURL: "https://models.github.ai/inference", + apiKey: GITHUB_TOKEN, +}); + +// CLI interface +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +// Helper for asking questions in CLI +function ask(questionText) { + return new Promise((resolve) => rl.question(questionText, resolve)); +} + +let score = 0; + +// ---------------------------------------------------- +// Generate quiz questions using GitHub LLM +// Includes JSON cleanup and validation +// ---------------------------------------------------- +async function generateQuestions(topic, difficulty) { + console.log("\nšŸŽ² Generating questions via GitHub LLM..."); + + const prompt = ` +Generate exactly 10 ${difficulty} quiz questions about ${topic}. +Format each question EXACTLY like this: + +Q1: What is ...? +A) Option 1 +B) Option 2 +C) Option 3 +D) Option 4 +Correct: B + +Do NOT use JSON. +Do NOT add explanations. +Do NOT add extra text. +`; + + try { + const response = await client.chat.completions.create({ + model: "openai/gpt-4o-mini", + messages: [ + { role: "system", content: "Follow the format strictly." }, + { role: "user", content: prompt }, + ], + temperature: 0.8, + }); + + const text = response.choices[0].message.content; + + // --------------------------------------------- + // Parse the text into JSON + // --------------------------------------------- + const blocks = text.split(/Q\d+:/).slice(1); + + const questions = blocks.map((block, index) => { + const lines = block.trim().split("\n"); + + const question = lines[0].trim(); + + const answers = [ + lines[1].replace("A)", "").trim(), + lines[2].replace("B)", "").trim(), + lines[3].replace("C)", "").trim(), + lines[4].replace("D)", "").trim(), + ]; + + const correctLine = lines.find((l) => l.startsWith("Correct")); + const correctLetter = correctLine.split(":")[1].trim(); + + const map = { A: 1, B: 2, C: 3, D: 4 }; + + return { + question, + answers, + correctAnswer: map[correctLetter], + }; + }); + + if (questions.length !== 10) { + throw new Error("Model did not return 10 questions"); + } + + return questions; + } catch (err) { + console.error("āš ļø Could not generate questions from GitHub LLM."); + console.error(chalk.yellow("Using fallback questions.")); + console.error(chalk.red(err.message)); + + return [ + { + question: "Which country has the city of Kyoto?", + answers: ["China", "Japan", "South Korea", "Thailand"], + correctAnswer: 2, + }, + { + question: "What is the largest planet in our solar system?", + answers: ["Earth", "Jupiter", "Saturn", "Mars"], + correctAnswer: 2, + }, + ]; + } +} + +// ---------------------------------------------------- +// Ask a single question (supports hints) +// ---------------------------------------------------- +async function askQuestion(qObj, index) { + console.log(`\nQuestion ${index + 1}: ${qObj.question}`); + qObj.answers.forEach((ans, i) => console.log(`${i + 1}. ${ans}`)); + + while (true) { + const answer = await ask("Your answer (1-4, h for hint, q to quit): "); + + // Quit + if (answer.toLowerCase() === "q") { + console.log("\nExiting quiz..."); + rl.close(); + return null; + } + + // Hint + if (answer.toLowerCase() === "h") { + const correctIndex = qObj.correctAnswer - 1; + + // Randomly hide 2 wrong answers + const hide = [0, 1, 2, 3] + .filter((i) => i !== correctIndex) + .sort(() => 0.5 - Math.random()) + .slice(0, 2); + + console.log("Hint: possible answers:"); + qObj.answers.forEach((ans, i) => { + if (!hide.includes(i)) console.log(`${i + 1}. ${ans}`); + }); + + continue; + } + + // Validate answer + const num = parseInt(answer); + + if ([1, 2, 3, 4].includes(num)) { + if (num === qObj.correctAnswer) { + console.log(chalk.green("āœ… Correct!")); + score++; + } else { + console.log( + chalk.red( + `āŒ Wrong! Correct answer: ${qObj.correctAnswer}. ${qObj.answers[qObj.correctAnswer - 1]}`, + ), + ); + } + break; + } else { + console.log("āš ļø Please enter 1-4, h for hint, or q to quit."); + } + } +} + +// ---------------------------------------------------- +// Main quiz flow +// ---------------------------------------------------- +async function startQuiz() { + console.log("šŸŽ® AI Quiz Game"); + + const topics = ["General Knowledge", "Programming", "Geography"]; + topics.forEach((t, i) => console.log(`${i + 1}. ${t}`)); + + // Select topic + let topic = ""; + while (true) { + const tInput = await ask("Select topic (1-3 or q to quit): "); + if (tInput.toLowerCase() === "q") return rl.close(); + const tNum = parseInt(tInput); + if (tNum >= 1 && tNum <= topics.length) { + topic = topics[tNum - 1]; + break; + } + } + + // Select difficulty + const difficulties = ["easy", "medium", "hard"]; + difficulties.forEach((d, i) => + console.log(`${i + 1}. ${d[0].toUpperCase() + d.slice(1)}`), + ); + + let difficulty = ""; + while (true) { + const dInput = await ask("Select difficulty (1-3 or q to quit): "); + if (dInput.toLowerCase() === "q") return rl.close(); + const dNum = parseInt(dInput); + if (dNum >= 1 && dNum <= difficulties.length) { + difficulty = difficulties[dNum - 1]; + break; + } + } + + console.log(`\nStarting quiz: ${topic} | Difficulty: ${difficulty}`); + + const questions = await generateQuestions(topic, difficulty); + + for (let i = 0; i < questions.length; i++) { + const res = await askQuestion(questions[i], i); + if (res === null) break; + } + + console.log(`\nšŸ† Quiz finished! Your score: ${score} / ${questions.length}`); + rl.close(); +} + +// Exit handler +rl.on("close", () => process.exit(0)); + +// Start the game +startQuiz(); diff --git a/task-2/package-lock.json b/task-2/package-lock.json new file mode 100644 index 0000000..bfb6502 --- /dev/null +++ b/task-2/package-lock.json @@ -0,0 +1,155 @@ +{ + "name": "task-2", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "task-2", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "chalk": "^5.6.2", + "dotenv": "^17.3.1", + "node-fetch": "^3.3.2", + "openai": "^6.32.0" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/dotenv": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/openai": { + "version": "6.32.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.32.0.tgz", + "integrity": "sha512-j3k+BjydAf8yQlcOI7WUQMQTbbF5GEIMAE2iZYCOzwwB3S2pCheaWYp+XZRNAch4jWVc52PMDGRRjutao3lLCg==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + } + } +} diff --git a/task-2/package.json b/task-2/package.json index a992f45..2d3d3c0 100644 --- a/task-2/package.json +++ b/task-2/package.json @@ -9,5 +9,11 @@ "keywords": [], "author": "", "license": "ISC", - "type": "module" -} \ No newline at end of file + "type": "module", + "dependencies": { + "chalk": "^5.6.2", + "dotenv": "^17.3.1", + "node-fetch": "^3.3.2", + "openai": "^6.32.0" + } +}