Files
learn.c/challenges/imortal.c
2026-04-25 14:08:36 -03:00

329 lines
10 KiB
C

// Adiciona macros para adicionar tipos e valores booleanos no código, é
// equivalente a manualmente definir macros como:
// #define true 1
// #define false 0
#include <stdbool.h>
// Adiciona funções para interagir com entrada e saída.
#include <stdio.h>
// Adiciona funções e macros comuns como o EXIT_FAILURE e EXIT_SUCCESS
// para deixar o código mais legível e seguindo padrões da linguagem.
// É equivalente a definir manualmente com:
// #define EXIT_FAILURE 1
// #define EXIT_SUCCESS 0
#include <stdlib.h>
// Mínimo e Máximo número de leituras que podem ser feitas pelo usuário,
// definidas aqui como macro para deixar o código mais légivel e esses
// valores mais facilmente explicados e mudáveis.
#define MIN_READS 3
#define MAX_READS 100
#define MAX_READ_VALUE 2000
// Coisas que forão levadas em consideração a esse código:
// - Checagem de erro do usuário e de funções.
// - Fazer o código ser legível e facilmente modificável.
// - Seguir as convenções (que conheço e/ou estou aprendendo) da linguagem C.
// - Uso de nome de variáveis e funções em inglês, para seguir convenções.
// get_avarage retorna a média dos valores dentro do array `arr`.
// `size` é obrigatório e precisa ser o tamanho `arr`.
double get_avarage(double arr[], int size) {
double sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return sum / size;
}
// get_min retorna o menor valor dentro do array `arr`.
// `size` é obrigatório e precisa ser o tamanho `arr`.
double get_min(double arr[], int size) {
// Caso o array não tenha nenhum elemento, não teremos nenhum
// valor para poder iniciar a variável `min` e teremos um erro
// por tentar acessar o array. Então retornamos 0 como "valor" padrão
// para a função.
//
// Tecnicamente, poderiamos retornar o menor valor possível que pode
// ser armazenado numa double na casa dos negativos, usando `-DBL_MAX`,
// mas sinto que isso seria confuso para o usuário. Como o usuário nesse
// sentido colocou valor nenhum, 0 parece um valor esperável para essa função.
if (size == 0) {
return 0;
}
double min = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] < 0) {
min = arr[i];
}
}
return min;
}
// get_max retorna o maior valor dentro do array `arr`.
// `size` é obrigatório e precisa ser o tamanho `arr`.
double get_max(double arr[], int size) {
double max = 0;
for (int i = 0; i < size; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
// show_differences imprime as diferenças de uma leitura para a outra,
// o "desvio", passadas pelo array `reads`. `size` é obrigatório e precisa
// ser o tamanho de `reads`.
void show_differences(double reads[], int size) {
double av = get_avarage(reads, size);
for (int i = 0; i < size; i++) {
printf("Leitura %d: %.2f Desvio: %.2f\n", i + 1, reads[i], reads[i] - av);
}
}
// verify_reads recebe o mínimo e máximo aceitável do usuário e imprime quais
// são as leituras dentro do array `reads` que estão dentro ou não dos limites.
// `size` é obrigatório e precisa ser o tamanho `reads`.
//
// retorna EXIT_FAILURE (1) em caso de erro durante a leitura de entradas do
// usuário.
int verify_reads(double reads[], int size) {
printf("Digite o mínimo aceitável: ");
double min = 0.0;
// Checagem de erro do scanf. Scanf retorna o número de valores
// que foram corretamente pegos do STDIN, ou seja, se esse valor
// não for igual a um, algum erro ou ação inesperada aconteceu.
//
// Essa checagem se repete pelo código todo quando o scanf é utilizado.
// Assim, o código é mais seguro e resiliênte.
if (scanf("%lf", &min) != 1) {
printf("ERROR: Falha ao ler a entrada do usuário.\n");
return EXIT_FAILURE;
}
printf("Digite o máximo aceitável: ");
double max = 0.0;
if (scanf("%lf", &max) != 1) {
printf("ERROR: Falha ao ler a entrada do usuário.\n");
return EXIT_FAILURE;
}
if (max < min) {
printf("ERROR: Máximo é menor que o mínimo.\n");
return EXIT_FAILURE;
}
for (int i = 0; i < size; i++) {
if (min > reads[i]) {
printf("Leitura %d: ABAIXO DO LIMITE\n", i + 1);
} else if (max < reads[i]) {
printf("Leitura %d: ACIMA DO LIMITE\n", i + 1);
} else {
printf("Leitura %d: OK\n", i + 1);
}
}
return EXIT_SUCCESS;
}
// print_bar imprime uma barra de asterísticos para mostrar o valor provido
// via `value` de forma visual.
void print_bar(int value) {
printf("[");
for (int i = 0; i < value; i++) {
printf("*");
}
printf("]\n");
}
// show_intensity imprimime a intensidade das leituras em forma de uma barra
// de asterítsticos.
void show_intensity(double arr[], int size) {
double av = get_avarage(arr, size);
printf("Intensidade: %.2f\n", av);
print_bar(av / MAX_READ_VALUE * 20);
}
int show_full_results(double reads[], int size) {
printf("Média das leituras: %.2f\n", get_avarage(reads, size));
printf("Valor mínimo encontrado nas leituras: %.2f\n", get_min(reads, size));
printf("Valor máximo encontrado nas leituras: %.2f\n", get_max(reads, size));
if (verify_reads(reads, size) != EXIT_SUCCESS) {
printf("ERROR: Falha ao verificar faixas das leituras.\n");
return EXIT_FAILURE;
}
show_intensity(reads, size);
return EXIT_SUCCESS;
}
// imortal_process é a lógica principal do programa e que simula o IMORTAL-1.
//
// retorna EXIT_FAILURE (1) em caso de erro durante sua execução.
int imortal_process() {
printf("Quantas leituras serão realizadas? (min %d, max %d) ", MIN_READS,
MAX_READS);
int readCount = 0;
if (scanf("%d", &readCount) != 1) {
printf("ERROR: Falha ao ler a entrada do usuário.\n");
return EXIT_FAILURE;
}
if (readCount < MIN_READS || readCount > MAX_READS) {
printf("ERROR: Número de leituras precisa estar entre %d e %d\n", MIN_READS,
MAX_READS);
return EXIT_FAILURE;
}
// Dinamicamente alocar memória para as leituras
double *reads = malloc(readCount * sizeof(double));
for (int i = 0; i < readCount; i++) {
// rand é uma função de <stdlib> que retorna um número inteiro aleatório
// entre 0 e RAND_MAX, nessa linha, ele está sendo dividio por RAND_MAX para
// poder virar um double entre 0 e 1, e depois multíplicado por 100 para
// virar um número entre 0 e 100.
reads[i] = ((double)rand() / (double)RAND_MAX) * MAX_READ_VALUE;
printf("Leitura %d: %.2f\n", i + 1, reads[i]);
}
printf("\n--- OPERAÇÕES ---\n");
printf("1 - Média\n");
printf("2 - Máx/Mín\n");
printf("3 - Desvios\n");
printf("4 - Verificão de faixa\n");
printf("5 - Barra gráfica\n");
printf("6 - Relatório\n");
printf("0 - Sair\n");
printf("\n");
// loop while sendo usado para poder haver mais de uma operação por simulação,
// ele irá rodar continuamente até uma declaração `return` ser chamada.
while (true) {
int opt = 0;
printf("Escolha uma opção: ");
if (scanf("%d", &opt) != 1) {
printf("ERROR: Falha ao ler a entrada do usuário.\n");
free(reads); // Qualquer return tem que antes liberar a memória das leituras
return EXIT_FAILURE;
}
// switch case sendo usado para deixar o código mais legível, ele é o mesmo
// que utilizar declarações `if` e `else if` em sequência. Como estamos
// apenas checando um valor `opt`, acredito que faz mais sentido o uso de
// `switch` ao invés de `else if`.
switch (opt) {
case 1: {
printf("Média das leituras: %.2f\n", get_avarage(reads, readCount));
break; // Esse `break` statement, e ademais dentro de blocos `case`, são
// para o `switch` e não o `while` loop.
}
case 2: {
printf("Valor mínimo encontrado nas leituras: %.2f\n",
get_min(reads, readCount));
printf("Valor máximo encontrado nas leituras: %.2f\n",
get_max(reads, readCount));
break;
}
case 3: {
show_differences(reads, readCount);
break;
}
case 4: {
// Verificação de falha para a função `verify_reads`
if (verify_reads(reads, readCount) != EXIT_SUCCESS) {
printf("ERROR: Falha ao verificar faixas das leituras.\n");
free(reads);
return EXIT_FAILURE;
}
break;
}
case 5: {
show_intensity(reads, readCount);
break;
}
case 6: {
printf("\n--- Relatório Completo ---\n\n");
show_full_results(reads, readCount);
printf("\n--- Relatório Completo ---\n");
break;
}
case 0: {
printf("Saindo do programa.\n");
free(reads);
return EXIT_SUCCESS;
}
// Checagem caso o usuário coloque uma operação que não existe, ou
// em outras palavras, um valor que não esteja coberto por alguma
// das declarações `case`.
default: {
printf("INFO: %d não é uma operação válida.", opt);
break;
}
}
printf("\nDeseja realizar outra operação? (s/n): ");
char res = 'n';
if (scanf(" %c", &res) != 1) {
printf("ERROR: Falha ao ler a entrada do usuário.\n");
free(reads);
return EXIT_FAILURE;
}
if (res == 'n') {
free(reads);
return EXIT_SUCCESS;
}
}
free(reads);
return EXIT_SUCCESS;
}
// Processo inicial do programa, que inicia a simulação
// do satélite IMORTAL-1.
//
// Acredito que provavelmente em um programa real essa função seria a que
// contém o código da função `imortal_process`, e a reiniciação da simulação
// ficaria em mãos de quem iniciou o programa (algum script bash, serviço
// systemd, outro programa, etc).
int main() {
while (true) {
printf("\n=== IMORTAL-1 - SISTEMA DE BORDO ===\n");
// Iniciação da simulação do IMORTAL-1, com checagem de
// erro para caso o processo tenha falhado por alguma razão.
if (imortal_process() != EXIT_SUCCESS) {
printf("Erro ao executar IMORTAL-1");
return EXIT_FAILURE;
};
printf("Deseja iniciar nova simulação? (s/n): ");
char res = 'n';
if (scanf(" %c", &res) != 1) {
printf("ERROR: Falha ao ler entrada do usuário.\n");
return EXIT_FAILURE;
}
if (res == 'n') {
// Sai do loop e termina o programa.
break;
}
}
return EXIT_SUCCESS;
}