5. неделя

Guess the Number

Стандартный ввод/вывод, переменные, арифметические выражения, отладка программ, игра "Отгадай число, которое я загадал"

Тема лабораторной

На данной практической работы вы познакомитесь с базовыми инструментами, которые вы будете использовать. Вы будете работать с компилятором gcc и утилитой make. Помимо этого вы создадите свою первую супер игру с названием: Отгадай число, которое я загадал.

Цели

  1. Научиться работать с компилятором gcc и понимать некоторые из его параметров.
  2. Научиться работать с утилитой make и настраивать её с помощью переменных среды.
  3. Ознакомиться с базовыми типами данных языка C.
  4. Научиться работать с переменными.
  5. Создавать простые арифметические выражения.
  6. Освоить функции для работы со стандартным вводом/выводом.
  7. Освоить работу с собственными функциями и функциями, которые возвращают значения.

Инструкции

Шаг 1: Warm up

На данном этапе вы создадите свою первую программу на языке С, скомпилируете её и настроите среду для программирования.

Задача 1.1

Создайте каталог ~/labs/lab05, в который вы будете сохранять файлы с кодом на языке C.

В вашем распоряжении следующие команды:

  • ls выписать содержимое каталога
  • mkdir название создать новый каталог
  • cd название изменить текущую директорию (под-дерикторию)
  • cd .. изменить каталог на "надкаталог"
  • rmdir название удалить каталог (пустой)
  • rm -r название удалить каталог (непустой)

Задача 1.2

В своём любимом редакторе создайте новый файл с названием guess.c.

Задача 1.3

Создайте программу, благодаря которому выпишете на экран всем известное: Hello world!

Решение может выглядеть так:

#include <stdio.h>

int main(){
    printf("Hello world!\n");

    return 0;
}

Задача 1.4

Измените код программ так, чтобы в нём содержался вступительный диалог игры Угадай число, которое я загадал между пользователем и компьютером. В данном диалоге программа попросит пользователя, чтобы он попробовал отгадать число, которое должно затем считаться в отдельной функции get_guess(). Предположенное число и будет возвращаемым значением функции get_guess().

Данный диалог может выглядеть следующим образом, причём ввод пользователя обозначен жирным шрифтом, а строка, начинающаяся символом $, представляет командную строку:

$ ./guess
Pick a number between 1 and 100.
Your guess: 50
You have entered: 50

Решение может выглядеть следующим образом:

#include <stdio.h>

int get_guess();  // function declaration

int main(){
    printf("Pick a number between 1 and 100.\n");

    int guess = get_guess();  // stores returned value in variable

    printf("You have entered: %d\n", guess);

    return 0;
}

// function definition
int get_guess(){
    printf("Your guess: ");

    int guess;
    scanf("%d", &guess);

    // returns value
    return guess;
}

Задача 1.5

Скомпилируйте программу и проверьте её на функциональность.

Для компиляции до этих пор вы использовали утилитой make, предварительно настроив файл Makefile. От данного момента вы будете использовать компилятор GNU C Compiler - gcc со следующими параметрами:

gcc -std=c11 -Wall -Werror guess.c -lm -o guess

Значение каждого из параметров:

  • -std=c11 использование версию языка С 2011 года
  • -Wall нас будут интересовать все предупреждения (warning), потому что нам не нужно, чтобы вы писали "просто" работающий код. Нам нужно, чтобы вы писали "хороший" код и компилятор не выдавал сообщения с предупреждениями (программа может быть скомпилирована несмотря на многочисленные предупреждения компилятора)
  • -Werror для того, чтобы у вас данные предупреждения игнорировать не получалось, мы будет трактовать любое предупрждение как ошибку, чем не допустим конечной компиляции в случае их выявления
  • guess.c имя файла, который подлежит компиляции
  • -lm в процессе компиляции будет подключена библиотека для работы с математикой
  • -o guess имя исполнительного файла, который мы хотим получить в результате компиляции.

Программу можно скомпилировать и с помощью make, как это было и до этого, но без Makefile. Общая схема использования:

make guess

Комментарий

Предложенный нами сервер и VM уже имеют настроенные переменные среды так, чтобы компиляция прошла с параметрами, описанными выше. Но в случае, если вы используете свой дистрибутив Linux, уже определённое перед вами поведение обеспечит компилятор:

cc guess.c -o guess

Для того, чтобы добиться такого же результата, как и при использовании компилятора gcc, нужно предварительно настроить переменные среды CFLAGS, LDLIBS и CC в файле ~/.bashrc:

export CFLAGS="-std=c11 -Wall -Werror"
export LDLIBS="-lm"
export CC=gcc

Для того, чтобы применить сделанные изменения, нужно перезайти в систему. Или попробуйте выполнить команду exec bash.

Если изменения не вступили в силу, переименуйте файл ~/.bashrc на ~/.profile и снова перезайдите в систему.

Скомпилированную программу затем запустите, находясь в нужной директории:

./guess

Шаг 2: The Game Continues

На данном этапе вам остаётся только лишь написать саму игру.

Правила игры предельно просты: Компьютер сгенерирует случайное число из предписанного интервала, которое будет игрок пытаться угадать. Игрок затем приступит к отгадыванию, а программа будет отвечать, больше ли загаданное число, меньше или равно.

Главной целью следующих заданий является тренировка работы с функциями, которые могут возвращать значения, работы со стандартным вводом/выводом и работы с простыми выражениями.

Задача 2.1

Измените код программы так, что программа генерировала случайное число в функции get_secret(), считала ввод от пользователя и затем выписала на экран результат.

Для самой генерации числа из интервала <1, 100> на языке С можно использовать следующий фрагмент кода:

srand(time(NULL));
int secret = (rand() % 100) + 1;

Поскольку генерировать число мы бы хотели в функции get_secret() (и вернуть его), код будет отличаться:

int main(){
    srand(time(NULL));

    int secret = get_secret();

    // ......
}

int get_secret(){
    return (rand() % 100) + 1;
}

Кроме того, в языке С нет функций srand(), rand() и даже time(). Для того, чтобы компилятор понимал, про какие функции идет речь, нужно их импортировать из библиотек stdlib.h и time.h.

Примером вывода программы может быть следующее, причём ввод пользователя выделен жирным шрифтом, а строка, начинающаяся символом $ представляет командную строку:

$ ./guess
Pick a number between 1 and 100.
Your guess: 50
Hmm... My number is bigger than yours.
$ ./guess
Pick a number between 1 and 100.
Your guess: 50
Hmm... My number is smaller than yours.
$ ./guess
Pick a number between 1 and 100.
Your guess: 50
Congratulations! You found it!

Задача 2.2

Измените код программы таким образом, чтобы пользователь мог продолжать угадывать число до тех пор, пока не отгадает нужное число.

Примером вывода программы может быть следующее, причём ввод пользователя выделен жирным шрифтом, а строка, начинающаяся символом $ представляет командную строку:

$ ./guess
Pick a number between 1 and 100.
Your guess: 50
Hmm... My number is bigger than yours.
Your guess: 75
Hmm... My number is bigger than yours.
Your guess: 87
Hmm... My number is smaller than yours.
Your guess: 81
Congratulations! You found it!

Задача 2.3

Ограничьте количество попыток для отгадывания до 5. Если они закончатся, игрок проиграет.

В данном случае существует два варианта развития событий - выигрышный и проигрышный. Вывод выиграшного случая выглядит так, как указано выше. Пример вывода программы в проиграшном случае может выглядеть так, причём ввод пользователя выделен жирным шрифтом, а строка, начинающаяся символом $ представляет командную строку

$ ./guess
Pick a number between 1 and 100. You have max. 5 attempts.
Your guess: 50
Hmm... My number is bigger than yours.
Your guess: 75
Hmm... My number is bigger than yours.
Your guess: 87
Hmm... My number is smaller than yours.
Your guess: 81
Hmm... My number is bigger than yours.
Your guess: 84
Hmm... My number is smaller than yours.
Game over. My number was 82.

Задача 2.4

Дополните своё решение так, чтобы можно было посмотреть информацию о текущем числе попыток. Диалоговое окно для ввода числа для отгадывания находится в отдельной функции, поэтому число попыток необходимо передать функции get_guess() в качестве входного параметра.

Пример вывода программы является следующим, причём ввод пользователя выделен жирным шрифтом, а строка, начинающаяся с символа $, является командной строкой:

$ ./guess
Pick a number between 1 and 100. You have max. 5 attempts.
Your 1. guess: 50
Hmm... My number is bigger than yours.
Your 2. guess: 75
Hmm... My number is bigger than yours.
Your 3. guess: 87
Hmm... My number is smaller than yours.
Your 4. guess: 81
Hmm... My number is bigger than yours.
Your 5. guess: 84
Hmm... My number is smaller than yours.
Game over. My number was 82.

Задача 2.5

Дополните своё решение так, чтобы после окончания отгадывания игра спросила, хочет ли игрок сыграть снова. Если да, он снова начнет игру. Если нет, программа завершит свою работу.

Пример вывода программы является следующим, причём ввода пользователя выделен жирным шрифтом, а строка, начинающаяся с символа $, является командной строкой:

$ ./guess
Pick a number between 1 and 100. You have max. 5 attempts.
Your 1. guess: 50
Hmm... My number is bigger than yours.
Your 2. guess: 75
Congratulations! You found it!
Play again? (y/n) n
See you later!

Предупреждение

При чтении символа обязательно очищайте входной буфер до и после считывания ввода от пользователя. Перед извлечением удалите символ новой строки, а после считывания удалите символы, которые являются лишними вместе с символом новой строкой (вместо одного символа пользователь может также ввести последовательность yes). Например, вы можете очистить буфер. следующим образом:

while( getchar() != '\n' )
    ;

Вы можете заключить данный код в отдельную функцию clear_buffer().

Предупреждение

Не используйте бесконечные циклы!

Задача 2.6

Добавьте функции get_secret() два параметра и измените генерирование случайного числа так, чтобы оно находилось в интервале, заданном этими параметрами: int get_secret(const int start, const int end).

Комментарий

Ключевое слово const означает, что параметр является константой, так как его значение не изменяется во время выполнения функции.

Шаг 3: Macros

На этом этапе вы измените код программы так, чтоб она использовала макросы.

Задача 3.1

Добавьте в свою программу 3 макроса: START, END и ATTEMPTS.

  • START Представляет собой начало интервала, в котором должны генерироваться случайные значения
  • END Представляет собой конец интервала, в пределах которого должны генерироваться случайные значения
  • ATTEMPTS Представляет количество попыток для угадывания в одной игре

Задача 3.2

Измените код программы так, чтобы она использовала созданные вами макросы.

Дополнительные задачи

Задача A.1

Создайте программу, с помощью которой вы сможете считать последовательность чисел, при этом последовательность закончите введением числа 0. На экране выведите наибольшее введенное число, наименьшее введенное число, сумму всех введенных чисел и их среднее арифметическое.

Задача A.2

Создайте программу, которая считает значение N со стандартного ввода (целое положительное число) и на его основе выпишет на экран Треугольник Паскаля.

Задача A.3

Измените код программы guess так, чтобы пользователь мог отгадывать числа только из нужного интервала. В случае неверного ввода, программа предложит ввести число для отгадывания снова.

Задача A.4

Создайте новую программу guess_my_number так, чтобы компьютер отгадывал число, которое игрок придумает. Игрок будет помогать компьютеру ответами (моё число больше/меньше/одинаковое). Используйте метод бисекции для реализации программы. (Bisection search, метод разделения интервалов).

Пример вывода программы является следующим, причём ввода пользователя выделен жирным шрифтом:

$ ./guess_my_number
Please think of a number between 0 and 100!
Is your secret number 50?
Enter 'h' to indicate the guess is too high. Enter 'l' to indicate the guess is too low. Enter 'c' to indicate I guessed correctly. l
Is your secret number 75?
Enter 'h' to indicate the guess is too high. Enter 'l' to indicate the guess is too low. Enter 'c' to indicate I guessed correctly. l
Is your secret number 87?
Enter 'h' to indicate the guess is too high. Enter 'l' to indicate the guess is too low. Enter 'c' to indicate I guessed correctly. h
Is your secret number 81?
Enter 'h' to indicate the guess is too high. Enter 'l' to indicate the guess is too low. Enter 'c' to indicate I guessed correctly. l
Is your secret number 84?
Enter 'h' to indicate the guess is too high. Enter 'l' to indicate the guess is too low. Enter 'c' to indicate I guessed correctly. h
Is your secret number 82?
Enter 'h' to indicate the guess is too high. Enter 'l' to indicate the guess is too low. Enter 'c' to indicate I guessed correctly. l
Is your secret number 83?
Enter 'h' to indicate the guess is too high. Enter 'l' to indicate the guess is too low. Enter 'c' to indicate I guessed correctly. c
Game over. Your secret number was: 83

Задача A.5

Измените программу guess_my_number следующим образом: Пусть А загадает число, а В отгадывает число. Пользователь пусть задаст интервал, в котором будет находиться заданное число. Выводом пусть будет диалог между А и В. Компьютер таким образом будет общаться сам с собой.

Задача A.6

Повторите работу с генератором случайных чисел (Задача 2.1) и напрограммируйте счетчик «Товар сейчас просматривают» по образцу опытных программистов из интернета (см. Рисунок). Творчеству нет предела.

Опыт с интернета: Продукт сейчас просматривают X клиентов
Рис. 1: Опыт с интернета: Продукт сейчас просматривают X клиентов

Дополнительные источники

  1. Pavel Herout: Учебника языка C (1. издание) - глава 5.1, 5.4 и 5.5, 9.2
  2. Функция printf(): c-reference, cplusplus.com - Loads the data from the given locations, converts them to character string equivalents and writes the results to stdout.
  3. Функция scanf(): c-reference, cplusplus.com - Reads data from stdin, interprets it according to format and stores the results into given locations.
  4. Функция getchar(): c-reference, cplusplus.com - Reads the next character from stdin.
  5. Функция srand(): c-reference, cplusplus.com - Seeds the pseudo-random number generator used by rand() with the value seed.
  6. Функция rand(): c-reference, cplusplus.com - Returns a pseudo-random integral value between ​0​ and RAND_MAX (0 and RAND_MAX included).
  7. Функция time(): c-reference, cplusplus.com - Returns the current calendar time encoded as a time_t object.

Видео