Skip to content

Commit

Permalink
[Release] 1.0.2
Browse files Browse the repository at this point in the history
  • Loading branch information
leomotors committed Feb 19, 2022
1 parent 16cb539 commit ea60cee
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 116 deletions.
3 changes: 3 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DISCORD_TOKEN=
GUILD_IDS=
EXTRA_TIME=
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ All notable changes to Kawaii Cocoa Grader will be documented here

Changelog before 0.2.2 will not be noted here

## [1.0.2] - 2022-02-20

- Upgrade to Cocoa Discord Utils 1.0.0-rc.3 and use its new feature

- problems/manifest.json is no longer needed, the grader wil list directory
and attempt import

- Now supports Subtasks

- Now warns you if you forget to install pshved/timeout

- Bot now hints supported languages when user submit unsupported one

## [1.0.1] - 2022-02-07

- Upgrade to Cocoa Discord Utils 1.0.0-rc.2 and use its new feature
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 Nutthapat Pongtanyavichai
Copyright (c) 2022 Leomotors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,17 @@ A Discord Bot Grader, more specifically, '*Waifu Discord Bot*'

## 🔪 Cutting Edge

Branch `future-lib` contains `cocoa-discord-utils` in the version that is not
published on npm yet. Error will surely occur.
Branch `future-lib` (if is ahead of main) contains `cocoa-discord-utils`
in the version that is not published on npm yet. Error will surely occur if you run it.

## 🔧 Adding a problem

Create a folder with problem ID as the name of it

Inside that folder, you should have manifest.json

For schema of manifest.json please look [here](./src/grader/problems.ts) or
at src/problems of [stupid-problems](https://github.com/Leomotors/stupid-problems)

## 🖼️ Gallery of Cocoa Grader

Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cocoa-grader",
"version": "1.0.1",
"version": "1.0.2",
"description": "Discord Bot Grader",
"main": "dist/bot/client.js",
"repository": "https://github.com/Leomotors/cocoa-grader",
Expand All @@ -19,19 +19,19 @@
"dependencies": {
"@discordjs/builders": "^0.12.0",
"chalk": "^5.0.0",
"cocoa-discord-utils": "^1.0.0-rc.2",
"cocoa-discord-utils": "^1.0.0-rc.3",
"discord.js": "^13.6.0",
"dotenv": "^16.0.0",
"node-fetch": "^3.2.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^3.2.0",
"@types/node": "^17.0.15",
"@types/node": "^17.0.18",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "^5.10.2",
"eslint": "^8.8.0",
"@typescript-eslint/eslint-plugin": "^5.12.0",
"@typescript-eslint/parser": "^5.12.0",
"eslint": "^8.9.0",
"prettier": "^2.5.1",
"rimraf": "^3.0.2",
"typescript": "^4.5.5"
Expand Down
10 changes: 5 additions & 5 deletions src/bot/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ const client = new Client(CocoaOptions);
const msgcenter = new MessageCenter(client, { mention: true });
msgcenter.addCog(CocoaMsg);
msgcenter.validateCommands();
msgcenter.on("error", async (err, msg) => {
await msg.reply(`Ara, Error Occured: ${err}`);
msgcenter.on("error", async (name, err, msg) => {
await msg.reply(`あら?, Error Occured: ${err}`);
});

const slashcenter = new SlashCenter(
Expand All @@ -35,16 +35,16 @@ const slashcenter = new SlashCenter(
);
slashcenter.addCog(Cocoa);
slashcenter.validateCommands();
slashcenter.on("error", async (err, ctx) => {
await ctx.reply(`Error Occured: ${err}`);
slashcenter.on("error", async (name, err, ctx) => {
await ctx.reply(`あら?, Error Occured: ${err}`);
});

const groupLoader = new ActivityGroupLoader("data/activities.json");

client.on("ready", (cli) => {
console.log(
chalk.cyan(
`ココアお姉ちゃん 「${cli.user.tag}${process
`ココアお姉ちゃん 「${cli.user.tag}${process
.uptime()
.toFixed(2)}秒で 準備完了です!`
)
Expand Down
6 changes: 4 additions & 2 deletions src/bot/commands/message/submit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Message } from "discord.js";
import chalk from "chalk";
import fetch from "node-fetch";

import { getLang } from "../../../grader/compile";
import { getLang, supportedLang } from "../../../grader/compile";
import Grade, { Verdict } from "../../../grader/grader";
import { problemExists } from "../../../grader/problems";
import { Cocoa, style } from "../../shared";
Expand Down Expand Up @@ -126,7 +126,9 @@ export const submit: CocoaMessage = {

const lang = getLang(userLang);
if (lang == "Unsupported") {
await msg.reply("Unsupported Language");
await msg.reply(
`Unsupported Language! The supported languages are ${supportedLang}`
);
return;
}

Expand Down
15 changes: 15 additions & 0 deletions src/grader/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ function convertToTokens(s: string): string[] {
return s.split(/\s+/).filter((s) => s.length > 0);
}

/**
* **W**: Token Compare (Exact, number and string)
*
* **F4**: Float Compare (Relative Error <= 10^-4)
*
* **F6**: Float Compare (Relative Error <= 10^-6)
*
* **FA4**: Float Compare (Absolute Error <= 10^-4)
*
* **FA6**: Float Compare (Absolute Error <= 10^-6)
*
* *a little credit* to GitHub Copilot who help me write the last 3 lines (F6 to FA6)
* by infering from what I have written in the second line (F4)
* *AI these days are so smart and useful* ✨🤩
*/
export type CompareType = "W" | "F4" | "F6" | "FA4" | "FA6";

function compare(user: string, file: string, type: CompareType): boolean {
Expand Down
9 changes: 8 additions & 1 deletion src/grader/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import { writeFile } from "fs/promises";

import { exec } from "./grader";

export type SupportedLang = "C" | "C++" | "Python" | "JavaScript" | "Haskell";
export const supportedLang = [
"C",
"C++",
"Python",
"JavaScript",
"Haskell",
] as const;
export type SupportedLang = typeof supportedLang[number];

export function getLang(str: string): SupportedLang | "Unsupported" {
if (str == "cpp" || str == "c++" || str == "cc") return "C++";
Expand Down
55 changes: 45 additions & 10 deletions src/grader/grader.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import chalk from "chalk";
import { exec as execCb } from "child_process";
import { promisify } from "util";
import { v4 as uuid } from "uuid";

import { check } from "./check";
import { Compile, getECmd, SupportedLang } from "./compile";
import { getProblems, Problem } from "./problems";
import { shortenVerdicts } from "./utils";
import { numberToAlphabet, shortenVerdicts } from "./utils";

export const exec = promisify(execCb);

Expand Down Expand Up @@ -60,19 +61,46 @@ export default async function Grade(
}

let totalScore = 0;
const subtaskVerdicts: CaseVerdict[] = [];
const ecmd = getECmd(lang, submissionId);
const subtaskVerdicts: string[] = [];
for (const [subtaskName, subtaskScore] of Object.entries(
problem.subtasks
)) {
const subtaskVerdict = await GradeCase(
getECmd(lang, submissionId),
`./problems/${problemID}/testcase/${subtaskName}`,
problem,
limits
);
if (typeof subtaskScore == "number") {
const subtaskVerdict = await GradeCase(
ecmd,
`./problems/${problemID}/testcase/${subtaskName}`,
problem,
limits
);

totalScore += subtaskVerdict == "Correct Answer" ? subtaskScore : 0;
subtaskVerdicts.push(VerdictDict[subtaskVerdict]);
continue;
}

totalScore += subtaskVerdict == "Correct Answer" ? subtaskScore : 0;
subtaskVerdicts.push(subtaskVerdict);
let subtasksVerdict = "";
let subtasksScore = 0;
let maxsubtaskScore = 0;
for (const [id, subtask] of Object.entries(subtaskScore.scores)) {
const subtaskVerdict = await GradeCase(
ecmd,
`./problems/${problemID}/testcase/${subtaskName}${numberToAlphabet(
+id
)}`,
problem,
limits
);

subtasksScore += subtaskVerdict == "Correct Answer" ? subtask : 0;
maxsubtaskScore += subtask;
subtasksVerdict += VerdictDict[subtaskVerdict];
}

subtaskVerdicts.push(`[${subtasksVerdict}]`);
if (!subtaskScore.grouped || subtasksScore == maxsubtaskScore) {
totalScore += subtasksScore;
}
}

const subtaskStr = shortenVerdicts(subtaskVerdicts);
Expand Down Expand Up @@ -120,6 +148,13 @@ async function GradeCase(
return "Wrong Answer";
}
} catch (error) {
if (`${error}`.includes("timeout --help")) {
console.log(
chalk.yellow(
"You probably did not installed pshved/timeout, do you?"
)
);
}
return "Runtime Error";
}
}
32 changes: 25 additions & 7 deletions src/grader/problems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,35 @@ import chalk from "chalk";
import fs from "fs/promises";

import { CompareType } from "./check";
import { exec } from "./grader";

export interface Subtask {
grouped?: boolean;
scores: number[];
}

export interface Problem {
title: string;
description: string;
// * Time Limit in Seconds
timelimit: number;
// * Memory Limit in MB
memorylimit: number;
subtasks: { [name: string]: number };
subtasks: { [name: string]: number | Subtask };
// * Default = 100
// TODO Auto Infer maxScore from subtasks
maxScore?: number;
statement?: string;
// * Default = "W"
compare?: CompareType;
}

let problemsList: { [id: string]: Problem } = {};

export async function loadProblems() {
const buffer = await fs.readFile("problems/manifest.json");
const problems: string[] = JSON.parse(buffer.toString()).problemLists;
const problems = (await exec("ls problems")).stdout
.split("\n")
.filter((l) => !l.includes(".") && l.length > 0);

if (!problems?.length) {
console.log(
Expand All @@ -28,9 +40,15 @@ export async function loadProblems() {

const problemsLoaded: { [id: string]: Problem } = {};
for (const problemID of problems) {
const buffer = await fs.readFile(`problems/${problemID}/manifest.json`);
const problem: Problem = JSON.parse(buffer.toString());
problemsLoaded[problemID] = problem;
try {
const buffer = await fs.readFile(
`problems/${problemID}/manifest.json`
);
const problem: Problem = JSON.parse(buffer.toString());
problemsLoaded[problemID] = problem;
} catch (err) {
console.log(chalk.red(`Cannot load ${problemID}: ${err}`));
}
}

problemsList = problemsLoaded;
Expand All @@ -47,5 +65,5 @@ export function getProblems(id: string) {
}

export function problemExists(id: string): boolean {
return problemsList[id] !== undefined;
return id in problemsList;
}
18 changes: 14 additions & 4 deletions src/grader/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { CaseVerdict, VerdictDict } from "./grader";
export function shortenVerdicts(verdicts: string[]): string {
return verdicts.join("");
}

const alp = "abcdefghijklmnopqrstuvwxyz";

/**
* **Note**: Only work in range of [0, 701]
*
* You probably don't have more than 702 subtasks
*/
export function numberToAlphabet(num: number) {
if (num < 26) return alp[num];

export function shortenVerdicts(verdicts: CaseVerdict[]): string {
const verdictsShort = verdicts.map((verdict) => VerdictDict[verdict]);
return verdictsShort.join("");
return alp[Math.floor(num / 26) - 1] + alp[num % 26];
}
Loading

0 comments on commit ea60cee

Please sign in to comment.