MOJ MOJ Melhor Online Judge BETA

Criando problemas no MOJ — um guia para professores que vivem no terminal

Tudo aqui também existe no editor web — mas se o shell é o seu habitat natural, a CLI moj transforma a autoria de problemas em arquivos no seu $EDITOR, scriptável em loops, com git por baixo. Este é o roteiro de professor para professor.

1. O ciclo de vida de um problema

rascunho privadovalidarcalibrarpublicar
  • Nasce privado dentro de uma ORG (o prefixo do id: apc#fibonacci). Ninguém de fora da org o vê — nem que ele existe. Prova em elaboração não vaza, por construção.
  • Validar é o portão de qualidade: seções obrigatórias no enunciado, testes pareados, soluções good aceitas. Não publica nada — é exatamente o modo p/ provas.
  • Calibrar: o juiz roda as SUAS soluções good em cada máquina e mede o time limit por máquina E por linguagem. Você nunca mais chuta TL.
  • Publicar o coloca no treino livre (a org precisa permitir público — trava que protege provas). Em contests de turma você usa problemas ainda privados.

O Painel acompanha cada fase — e avisa, com os commits exatos, quando um problema mudou e precisa recalibrar.

2. A CLI em 60 segundos

curl -fsSL https://moj.naquadah.com.br/moj -o ~/.local/bin/moj && chmod +x ~/.local/bin/moj
moj login     # o MESMO login desta página — sem git, sem chave SSH

Requisitos: bash, curl, jq. Um arquivo só, auto-contido (a lib comum vem embutida). Quem gere contests ou o parque de juízes também tem /moj-contest e /moj-judges no mesmo endereço.

Dois jeitos de trabalhar, misture à vontade: moj edit abre um menu interativo com todos os campos (enunciado, exemplos, soluções, conf, publicar) — e o jeito arquivos-locais: o pacote é um diretório, você edita com as suas ferramentas e moj push envia.

Quando o mojtools local é necessário: exatamente TRÊS comandos rodam maquinário de julgamento na SUA máquina — moj test --run (julgamento local; também exige Linux com bubblewrap real), moj checker e moj interactive (instalam os drivers de correção especial). Para esses:
git clone https://github.com/cd-moj/mojtools ~/mojtools
export MOJTOOLS_DIR=~/mojtools     # no seu .bashrc
TODO O RESTO da CLI só precisa de bash, curl e jq.

3. Passo a passo: do zero ao publicado

Passo 1 — o esqueleto

moj new minha-org fibonacci      # scaffold completo em ./fibonacci
cd fibonacci && ls
# docs/enunciado.md  tests/input/ tests/output/  sols/good/  conf  author  tags

O primeiro argumento é a ORG (o id vira minha-org#fibonacci). Sua org pessoal (seu login) sempre existe e é sempre privada — perfeita p/ rascunhos.

Rascunhe na org pessoal e mova depois com moj mv fibonacci apc2026 — muda o id, nada mais. Na web: “+ Novo problema” na gestão.

Passo 2 — o enunciado (docs/enunciado.md)

Markdown com matemática ($O(n \log n)$ vira MathML). Duas regras que o validador cobra e uma que ele não tem como cobrar:

  • As seções ## Entrada e ## Saída são obrigatórias (## Input/## Output também valem).
  • O título é um campo (definido no moj new/edit ou no formulário web) — % Título na primeira linha é legado e é removido.
  • Declare as restrições EXPLICITAMENTE (1 ≤ N ≤ 10⁵, cabe em 64 bits…). O aluno resolve as restrições, não a estória.
NUNCA cole exemplos no texto. O renderizador os injeta sozinho a partir dos arquivos de sample (próximo passo) — exemplo colado envelhece e diverge.
moj preview renderiza EXATAMENTE o HTML que o aluno verá (mesmo renderizador do site). Deixe aberto enquanto escreve.

Imagens no enunciado

Jogue a figura AO LADO do enunciado.md (o docs/ do pacote) e referencie por caminho relativo — o renderizador a embute em base64 (pandoc --embed-resources), então o HTML servido ao aluno é auto-contido:

cp ~/screenshots/grafo.png docs/
# no enunciado.md:
![O grafo do exemplo 1](grafo.png)

moj preview mostra a imagem embutida; moj push a carrega (round-trip completo). Qualquer formato que o navegador renderiza (png/jpg/svg) funciona.

Macro de editor p/ copiar a imagem p/ o lugar e inserir o markdown num golpe — vim (no .vimrc) e emacs (init.el):
" vim — :MojImg ~/screenshots/grafo.png  (copia p/ o dir do arquivo atual e insere a linha)
command! -nargs=1 -complete=file MojImg
  \ execute '!cp' shellescape(<q-args>) shellescape(expand('%:p:h')) |
  \ execute 'normal! o![](' . fnamemodify(<q-args>, ':t') . ')'
;; emacs — M-x moj-img  (idem: copia p/ o dir do buffer e insere ![](arquivo))
(defun moj-img (file)
  (interactive "fImagem: ")
  (let ((dest (expand-file-name (file-name-nondirectory file) default-directory)))
    (copy-file file dest t)
    (insert (format "![](%s)" (file-name-nondirectory file)))))

Passo 3 — exemplos visíveis (samples)

printf '5\n1 2 3 4 5\n' > tests/input/sample1
# a saída de CADA teste você vai gerar com a sua solução no passo 5

Cada tests/input/sampleX casa com tests/output/sampleX e aparece no enunciado, em ordem. Tudo que NÃO se chama sample* é teste oculto.

2–3 samples PEQUENOS e didáticos: um caso típico, uma borda que o aluno deva notar. Explicações por exemplo vivem em docs/sample-notes.json. Grande e exaustivo é papel dos ocultos.

Passo 4 — testes ocultos

É aqui que a nota se decide. Cubra: o menor caso válido, o MAIOR caso (limites das restrições — é ele que derruba o O(n²)), valores que estouram int, empates/duplicatas e formatos degenerados. Um gerador vale mais que arquivos à mão:

for i in $(seq 1 8); do python3 gen.py $i > tests/input/t$i; done
Gere TODO tests/output/* COM a sua solução good (passo 5) — nunca à mão. Uma saída digitada com espaço sobrando reprova a turma inteira.

Nota parcial estilo OBI: tests/score passo a passo

  1. Nomeie os testes ocultos POR GRUPO — é o glob que os amarra:
    tests/input/g1_pequeno1  g1_pequeno2   # casos N ≤ 100
    tests/input/g2_medio1    g2_medio2     # N ≤ 10^4
    tests/input/g3_grande1   g3_grande2    # N ≤ 10^6
  2. Uma linha por grupo no tests/score: globs, depois - , depois o peso. O separador ENTRE globs é vírgula+espaço (", ") — o parser exige, dos dois lados (API e juiz):
    sample* - 0 pontos
    g1_* - 20 pontos
    g2_* - 30 pontos
    g3_* - 50 pontos
  3. Regras: grupo é tudo-ou-nada (um teste falhou = grupo vale 0); o valor do problema é a SOMA dos pesos (não precisa dar 100); os samples entram com peso 0 p/ aparecerem no relatório sem valer nota.
  4. O que o aluno vê embaixo do veredicto: ✓ Grupo 2 (30/30) · ✗ Grupo 3 (0/50) — feedback explícito por grupo; em contest modo OBI o placar também vira pontos.

Passo 5 — soluções de referência (sols/)

A extensão define a linguagem (sol.c, sol.cpp, sol.py, Main.java…). Cada subdiretório tem um PAPEL na conferência:

  • good/obrigatório (≥1): as corretas. Geram as saídas esperadas, a validação exige que sejam aceitas, e o juiz as cronometra p/ calibrar o TL.
  • wrong/erradas de propósito: prova que seus testes PEGAM o erro clássico.
  • slow/lentas de propósito (o O(n²) que você quer reprovar): prova que o TL de fato as derruba.
  • pass/devem passar raspando: prova que o TL não ficou apertado demais.
  • upcoming/rascunhos; fora da conferência.
Regra de ouro: uma good POR LINGUAGEM que você quer permitir. O TL é medido por linguagem — linguagem sem good própria herda um limite que pode ser injusto (o tempo da sua solução C não serve p/ Python).
# gere as saídas com a good (exemplo em C):
gcc -O2 sols/good/sol.c -o /tmp/sol
for f in tests/input/*; do /tmp/sol < "$f" > "tests/output/$(basename "$f")"; done

Passo 6 — conf: limites e opções

  • MEMLIMITMBlimite de memória (a JVM/-Xmx se dimensiona por ele também).
  • calibrafactora folga do aluno sobre o tempo medido da sua good. Mais generoso = mais justo com abordagens alternativas.
  • languagesrestringe as linguagens de submissão (vazio = todas as padrão).
Entrada pesada em Python? Dê espaço: suba MEMLIMITMB e a folga — ou forneça uma good em Python e deixe a calibração por linguagem fazer a justiça por você.

Opcional — correção especial (checker próprio / interativo)

Quando o diff puro não basta: várias respostas válidas (qualquer ordem topológica, qualquer caminho máximo), tolerância numérica, ou problemas interativos. Dois caminhos:

A) testlib (recomendado)escreva um checker.cpp testlib padrão e instale o driver normalizado:

moj checker ./fibonacci checker.cpp      # instala scripts/ (stub canônico + seu checker)
moj interactive ./meu-jogo arbitro.cpp   # idem p/ problema INTERATIVO (árbitro via FIFOs)

B) na mãoum scripts/compare.sh com este CONTRATO:

#!/bin/bash
# $1 = saída do ALUNO   $2 = saída ESPERADA   $3 = ENTRADA do teste
# exit 4 = Accepted · 5 = Accepted (Presentation Error) · 6 = Wrong Answer
awk -v a="$1" -v b="$2" 'BEGIN{ ... }' && exit 4 || exit 6
chmod +x é OBRIGATÓRIO em todo scripts/*.sh (a jaula os executa direto — sem o bit, TUDO vira Compilation Error). E mexer em scripts/ muda o tl-checksum: o Painel vai pedir recalibração — está certo, aceite.

Guias: checker-testlib.md, correcao-especial.md e problema-interativo.md nas docs do mojtools (cobrem também submissão-de-função e banimento de função).

Passo 7 — teste local, depois envie

moj preview      # o HTML do aluno no navegador
moj test         # pré-voo: estrutura, pareamento, seções
moj test --run   # JULGA de verdade na sua máquina (Linux + bwrap)
moj push         # envia — cria/atualiza no servidor (round-trip completo)

Todo push/salvar é um commit git no servidor: a aba histórico do editor (e moj log/moj restore) permite inspecionar e voltar qualquer versão.

Passo 8 — validar, calibrar, publicar

moj validate minha-org#fibonacci   # portão + calibração no juiz — CONTINUA PRIVADO
moj check    minha-org#fibonacci   # QA: checks, TL por juiz, good sem TL
moj publish  minha-org#fibonacci   # público no treino (a ORG precisa permitir)

validate é o modo prova: conferência completa, exposição zero. check mostra o veredicto do juiz sobre as SUAS soluções e os TLs calibrados. publish exige a trava de público da org aberta (aba Orgs) — a org pessoal implícita nunca publica.

4. Receitas de professor

Uma lista/prova do zero, em lote

moj mkdir apc2026                       # org da disciplina (privada por padrão)
for p in soma-vetores busca-bin pilha-min; do
  moj new apc2026 $p                    # três esqueletos
done
# ... edite cada um (moj edit ./$p ou seus arquivos) ...
for p in soma-vetores busca-bin pilha-min; do
  (cd $p && moj push) && moj validate "apc2026#$p"
done
moj board                               # a saúde de tudo num tiro só

Os problemas ficam privados até você decidir o contrário; o contest da turma pode usá-los ainda privados. No dia da prova, nada nunca esteve exposto.

Reusar entre semestres

moj clone apc2026#fibonacci fib-2027    # o pacote inteiro, scripts incluídos
# ajuste os testes / a estória / os limites ...
(cd fib-2027 && moj push)               # o histórico git guarda cada versão
moj log apc2026#fibonacci -n 5          # e se algo der errado:
moj restore apc2026#fibonacci <sha>     # volta como um commit NOVO (nada se perde)

Delegar a monitores; organizar o acervo

moj share apc2026 monitor-joao          # membro da org: edita TUDO da org
moj collection create "APC 2026.1"      # coleção = TAG de agrupamento (m:n, cross-org)
moj collection add apc2026#fibonacci "APC 2026.1"

ORG = acesso (uma por problema, o prefixo do id). COLEÇÃO = rótulo de navegação/curadoria, várias por problema. O aluno filtra o treino por coleção.

De olho em tudo

moj board                    # seus problemas: validado? calibrado? precisa revisar?
moj check apc2026#fibonacci  # o porquê de cada estado, juiz por juiz
moj calibrate --all-stale    # recalibra TUDO que o painel marcou (lote seguro)

5. ⚡ Comandos rápidos

ComandoO que faz
moj login · whoamisessão (mostra se você pode criar problemas)
moj new <org> <prob>scaffold completo do pacote em ./prob
moj clone <id> [dir]baixa o pacote INTEIRO (testes, soluções, scripts)
moj edit <id|dir>menu interativo com todos os campos
moj preview [dir]o HTML exato do aluno, no navegador
moj test [dir] [--run]pré-voo local; --run julga de verdade (bwrap)
moj push [dir]envia (cria/atualiza; round-trip completo)
moj upload/download <id>pacote inteiro via tarball (download --sha = versão antiga)
moj validate <id>portão + calibração — CONTINUA privado (modo prova)
moj check <id>QA: checks, TLs por juiz, o porquê da recalibração
moj publish <id> · public <id> on|offpúblico no treino (org precisa permitir) / alternar
moj ls · board · info <id>listas · painel de saúde · tudo de um problema
moj log <id> · restore <id> <sha>histórico git · volta como commit NOVO
moj mv <id> <org>move rascunho p/ outra org (muda o id)
moj mkdir <org> · share <org> <login>cria org · adiciona membro (quem edita)
moj collection …cria/marca/navega coleções (tags de agrupamento)
moj calibrate <id> | --all-staleenfileira calibração / recalibra tudo que precisa
moj checker · interactiveinstala checker testlib / problema interativo (correção especial)

6. 💡 Dicas & armadilhas (aprendidas na prática)

  • Exemplos vivem em arquivos, nunca no texto do enunciado — o renderizador os injeta e mantém tudo consistente.
  • Saídas esperadas são SEMPRE geradas por uma good. Edição à mão é WA futuro p/ a turma inteira.
  • Mudou testes, soluções good, conf ou scripts de correção? O TL ficou velho — o Painel marca (com os commits causadores) e moj calibrate resolve. Mudança SÓ de enunciado não recalibra.
  • Justiça por linguagem vem de good por linguagem. Se você permite Python, entregue uma good em Python.
  • A privacidade é estrutural: problema privado não aparece em lugar nenhum (nem p/ admin global de fora da org). A trava de público é da ORG — fechá-la despublica tudo em cascata.
  • Precisa de checker próprio (várias respostas válidas) ou problema interativo? Os dois são cidadãos de primeira classe: moj checker / moj interactive instalam os drivers normalizados.
  • moj test --run exige Linux com bubblewrap real; sem ele, a validação simplesmente adia a execução p/ o juiz — não é bug.

7. Referências

Formato do pacote (fonte única) · Documentação completa · API · ← voltar à Gestão de Problemas