Tic Tac Toe

Ветвление, циклы, массивы (1D + 2D), параметры функций

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

На данном занятии вы займётесь программированием еще одной игры - Крестики-нолики (Tic Tac Toe).

Objectives

  • Работа с массивами.
  • Работать с ветвлением и циклами в программе.
  • Освоить работу с параметрами функций.
  • Ознакомиться с двурозмерным полем (массивом).

Postup

Step 1: Крестики-нолики

На данном этапе напиши простую игру Крестики-нолики (1D). Игровой план в данном случае - одноразмерный массив длиной N. В игре принимают участие 2 игрока, оба записывают крестики (знак Х). Первый, у которого выйдет заполнить поле 3 своими знаками в ряд (диагональ) - выигрывает.

Для отладки программы и устранения возможных ошибок, используйте утилиту cgdb с прошлого занятия.

Task 1.1

Напишите функцию void draw(const int size, char field[]), которая выведет на экран актуальную ситуацию на поле.

Если field будет содержать в себе следующее:

char field[] = { ' ', 'X', ' ', 'X', ' '};

в таком случае, функция выведет на экран поле в следующем формате:

+-+-+-+-+-+
| |X| |X| |
+-+-+-+-+-+
 1 2 3 4 5

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

Task 1.2

Измените главную функцию программы так, что игрок сначала введёт размер игрового поля, которое затем должно вывестись на экран.

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

$ ./tictactoe
Enter the size of field: 3
Enter the size of field: 22
Enter the size of field: 7

+-+-+-+-+-+-+-+
| | | | | | | |
+-+-+-+-+-+-+-+
 1 2 3 4 5 6 7

Task 1.3

Напишите функцию int add_cross(const int size, char field[], const int position), которая поместит знак на позицию, указанную в параметре position знак игрока.

В случае если на месте, куда ставится знак, уже находится знак, функция возвращает 0, в успешном случае - 1.

Task 1.4

Измените функцию int add_cross() так, что она должна вернуть -1, если пользователь вводит позицию для помещения знак, находящуюся вне интервала игрового поля.

Task 1.5

Напишите функцию int is_solved(const int size, char field[]), с помощью котрой можно будет проверить, закончена ли игра.

Функция возвращает 1, если в поле находятся минимально 3 знака друг около друга. В противном случае - значение, равное 0.

Task 1.6

Используя только что написанные функции, реализуйте игру.

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

$ ./tictactoe
Enter the size of field: 9

+-+-+-+-+-+-+-+-+-+
| | | | | | | | | |
+-+-+-+-+-+-+-+-+-+
 1 2 3 4 5 6 7 8 9

Player A: 2

+-+-+-+-+-+-+-+-+-+
| |X| | | | | | | |
+-+-+-+-+-+-+-+-+-+
 1 2 3 4 5 6 7 8 9

Player B: 5

+-+-+-+-+-+-+-+-+-+
| |X| | |X| | | | |
+-+-+-+-+-+-+-+-+-+
 1 2 3 4 5 6 7 8 9

Player A: 9

+-+-+-+-+-+-+-+-+-+
| |X| | |X| | | |X|
+-+-+-+-+-+-+-+-+-+
 1 2 3 4 5 6 7 8 9

Player B: 10
Wrong position!
Player A: 2
X is already there!
Player B: 8

+-+-+-+-+-+-+-+-+-+
| |X| | |X| | |X|X|
+-+-+-+-+-+-+-+-+-+
 1 2 3 4 5 6 7 8 9

Player A: 7

+-+-+-+-+-+-+-+-+-+
| |X| | |X| |X|X|X|
+-+-+-+-+-+-+-+-+-+
 1 2 3 4 5 6 7 8 9

Player A wins!

Step 2: 2D

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

  • Таким образом, программу нужно изменить так, чтобы поле field[] было двухразмерное. Поле будет квадратного вида, т.е. высота и ширина будут равны.
  • Игрок будет вводить 2 координаты - х, у.
  • Ось х - горизонтальная ось, нумерованная слева направо, начиная со значения 1. Должна выводиться под игровым полем. Ось у - вертикальная ось, нумерованная снизу вверх, также начиная со значения 1. Выводится на экран слева от поля.
  • Первому игроку соответствуют крестики X, а второму игроку - нолики O.
  • Диалог двух игроков может выглядеть следующим образом:
$ ./tictactoe
Enter the size of field: 5

  +-+-+-+-+-+
5 | | | | | |
  +-+-+-+-+-+
4 | | | | | |
  +-+-+-+-+-+
3 | | | | | |
  +-+-+-+-+-+
2 | | | | | |
  +-+-+-+-+-+
1 | | | | | |
  +-+-+-+-+-+
   1 2 3 4 5

Player A: 0 3
Wrong position!
Player B: 3 3

  +-+-+-+-+-+
5 | | | | | |
  +-+-+-+-+-+
4 | | | | | |
  +-+-+-+-+-+
3 | | |O| | |
  +-+-+-+-+-+
2 | | | | | |
  +-+-+-+-+-+
1 | | | | | |
  +-+-+-+-+-+
   1 2 3 4 5

Player A: 2 2

  +-+-+-+-+-+
5 | | | | | |
  +-+-+-+-+-+
4 | | | | | |
  +-+-+-+-+-+
3 | | |O| | |
  +-+-+-+-+-+
2 | |X| | | |
  +-+-+-+-+-+
1 | | | | | |
  +-+-+-+-+-+
   1 2 3 4 5

Player B: 4 4

  +-+-+-+-+-+
5 | | | | | |
  +-+-+-+-+-+
4 | | | |O| |
  +-+-+-+-+-+
3 | | |O| | |
  +-+-+-+-+-+
2 | |X| | | |
  +-+-+-+-+-+
1 | | | | | |
  +-+-+-+-+-+
   1 2 3 4 5

Player A: 6 5
Wrong position!
Player B: 5 5

  +-+-+-+-+-+
5 | | | | |O|
  +-+-+-+-+-+
4 | | | |O| |
  +-+-+-+-+-+
3 | | |O| | |
  +-+-+-+-+-+
2 | |X| | | |
  +-+-+-+-+-+
1 | | | | | |
  +-+-+-+-+-+
   1 2 3 4 5

Player B wins!

Task 2.1

Измените декларацию поля field в функции main() так, чтобы оно было двухразмерным.

Высота и ширина поля равны. Они определяются переменной, в которой сохранён размер игрового поля. Данный параметр игры задаёт игрок.

Все элементы поля сначала содержат в себе знаки ' ' (пробел).

Task 2.2

Измените декларацию и дефиницию функции draw() так, чтобы она использовала двухразмерное поле.

Декларация изменится следующим образом: void draw(const int size, char field[][size]), в которой size это размер поля (вертикальный и горизонтальный), field - само двухразмерное поле. Второй размер (число во вторых квадратных скобках) при работе с двухразмерными полями обязателен. Поэтому, параметр size указан как первый (в момент своего использования уже должен быть известен).

Функция должна вывести на экран поле так, чтобы ось х была пронумерована слева направо, начиная с 1 под игровым полем. Ось у нумеруется снизу вверх, начиная с 1, изображается слева от игрового поля.

Task 2.3

Измените декларацию функции add_cross() так, чтобы она использовала двухразмерный массив.

Декларация изменится следующим способом: int add_cross(const int size, char field[][size], const int x, const int y, const char player), где size это размеры поля (горизонтальный + вертикальный), а field представляет собой поле. Параметры x и y представляют координаты на соответствующих осях, по которым игрок хочет поставить свой знак. Параметр player определяет, чьего игрока очередь.

Функция должна в поле field добавить крестик (знак X), если в данный момент очередь игрока A, кружок (знак O) - если игрок B. То, чья очередь, определяет параметр player.

Не забудьте изменить и место вызова функции (добавьте новые параметры).

Task 2.4

Измените декларацию функции is_solved() так, чтобы она использовала двухразмерный массив.

Декларация изменится следующим образом: int is_solved(const int size, char field[][size]), где size это размеры поля (горизонтальный + вертикальный), а field сам двухразмерный массив.

Функция должна определить, является ли игра выигранной (законченной), т.е. находятся ли друг около друга 3 в ряд крестика (нолика). Под проверку подлегают 3 крестика по горизонтали, вертикали, или диагонали.

Task 2.5

Проверьте правильность вашей реализации.

Additional Tasks

Task A.1

Напишите функцию void make_turn(const int size, char field[][size]). Данная функция будет представлять искусственный интеллект, играющий против игрока, которым будет сам компьютер.

Task A.2

Создайте функции int find_min(const int size, char field[][size]) и int find_max(const int size, char field[][size]), которые будут возвращать позиции наименьшего элемента и наибольшего элемента. В случае, если таких элементов, больше одного, функции должны вернуть позицию первого.

Task A.3

Напишите функцию int compare(const int size1, char field1[][size1], const int size2, char field2[][size2]), возвращающую 1, если два массива одинаковы. в противном случае - 0.

Additional Resources

  1. Break vs continue
  2. Functions vs parameters
  3. Мультиразмерные массивы в языке Си

Video

Циклы for и while с Даниелом