Skip to main content

Урок 9. Повторный взгляд

1 vote

Доброго времени суток, дорогие читатели нашего курса по основам С++, начинаем очередной урок, к сожалению, последний в этом цикле. Сегодня мы повторим пройденный материал и посмотрим, как его можно применить к итоговому проекту. Немного отойдем от традиционной подачи материала, сегодня домашнее задание будет выдано в начале урока. Итак, задание – создание небольшой игры. Эта игра может быть любой: крестики-нолики с компьютером или другим человеком, морской бой, пакмэн, сапер, теннис – все, что вам угодно, не обязательно из перечисленного. Сегодня мы рассмотрим некоторые аспекты, которые помогут в решении данной задачи.
Итак, первое, что нужно сказать. Поскольку в рамках этого курса ничего кроме консоли мы не рассматривали, я не имею права требовать ничего более этого. Соответственно, игра в консоли, хорошо. Информацию в консоли можно выводить в 25 строк и 80 столбцов.
Зачем я это сказал. Во всех перечисленных мной играх есть игровое поле, с ним как раз можно работать через двумерный массив размерностью не больше чем 25*80. Заполнение массива в простом случае можно организовать, используя обычный генератор случайных чисел. Подтвердим это примером: создадим поле 20*30, каждая ячейка которого будет содержать случайную цифру от 0 до 4 (включительно). Тогда код будет выглядеть следующим образом:

  1. #include "stdafx.h"
  2. #include <iostream>
  3. #include <time.h>
  4.  
  5. using namespace std;
  6.  
  7. int _tmain(int argc, _TCHAR* argv[])
  8. {
  9. srand(time(NULL));
  10. int mas[25][80];
  11. for (int i = 0; i < 20; i++)
  12. {
  13. for (int j = 0; j < 30; j++)
  14. {
  15. mas[i][j] = rand() % 5;
  16. cout << mas[i][j];
  17. }
  18. cout << endl;
  19. }
  20. getchar();
  21. return 0;
  22. }

На выходе получаем примерно такую вот картину:

1.PNG

Давайте представим, что в нашей матрице цифрой 1 обозначены стены, цифрой 0 обозначена свободная клетка, цифрой 2 пельмешки, цифрой 3 плюшки-печенюшки, а цифрой 4 – игрок. Подождите, скажете вы, судя по матрице, игроков много, а это не правильно. Хорошее замечание. Изменим код так, чтобы игрок был 1 и находился в заданной позиции. Может дополнительные условия? А что, если нам надо, чтобы наше поле было обрамлено стенами? Просто добавим условие. Ну и с учетом некоторых правок, получим следующий код:

  1. #include "stdafx.h"
  2. #include <iostream>
  3. #include <time.h>
  4.  
  5. using namespace std;
  6.  
  7. int _tmain(int argc, _TCHAR* argv[])
  8. {
  9. srand(time(NULL));
  10. const int maxX = 20, maxY = 30; // set field size
  11. int field[maxX][maxY];
  12. for (int i = 0; i < maxX; i++)
  13. {
  14. for (int j = 0; j < maxY; j++)
  15. {
  16. if (i == 0 || i == (maxX - 1) || j == 0 || j == (maxY - 1)) // set frame walls
  17. field[i][j] = 1;
  18. else
  19. field[i][j] = rand() % 4; // set elements
  20. }
  21. }
  22. field[10][10] = 4; // set player
  23. for (int i = 0; i < maxX; i++)
  24. {
  25. for (int j = 0; j < maxY; j++)
  26. cout << field[i][j];
  27. cout << endl;
  28. }
  29. }

Что мы тут видим: во-первых, размеры поля заданы константами, об этом я как-то упоминал в одном из уроков, во-вторых, теперь по периметру поля стоят стены (1), в-третьих, игрок теперь один и находится в позиции [10][10]. Вывод массива вынесен в отдельные циклы как намек на то, что все отдельные операции должны логически разделяться – в идеальном случае их нужно выносить в отдельные функции/методы. Примером отдельной операции может служить инициализация поля или его прорисовка, или перемещение игрока и так далее. Теперь поле выглядит следующим образом:

2.PNG

Есть поле, игрок, прочие элементы, так почему же не двигаться по нему? Давайте реализуем возможность передвижения по этому полю. Какие условия должны выполняться? Во-первых, для движения используются клавиши WSAD (как во многих играх), во-вторых, если игрок натыкается на стену, движения происходить не должно, в-третьих, если игрок перемещается на другую клетку, предыдущая клетка должна освобождаться (вспомним, что 0 – пустая клетка).
Теперь нам нужно где-то держать текущую позицию игрока (не перебирать же весь массив, пока мы на него не наткнемся – это будет чудовищно). Заведем переменные posX и posY, которые будут содержать номер ячейки массива, где находится игрок. Введем так же переменную action, которая будет отвечать за перемещение. Схема ввода, которую мы знаем, действует следующим образом: 1. Ввести значение, 2. Нажать Enter. Она подходит для ввода строк, но нам нужен только отдельный символ, да и нажатие Enter’a не требуется, соответственно, нам нужно что-то другое. Поможет функция getche (_getche в Visual Studio). До каких пор осуществлять движение? Можно добавить условие набора определенного количества очков, но мы, поскольку задача ознакомительного плана, будем гулять по карте пока не надоест. Отсюда цикл с условием выхода по нажатию кнопки. И последнее, что надо сделать – изобразить поле с учетом всех изменений. Если мы выведем поле после ввода, то с первым выводом будет ненужная задержка, следует вывод, что выводить поле надо до совершения хода. Стирание старого поля произведем системной функцией очистки экрана system(“cls”); Посмотрим на получившийся код.

  1. #include "stdafx.h"
  2. #include <iostream>
  3. #include <time.h>
  4.  
  5. using namespace std;
  6.  
  7. int _tmain(int argc, _TCHAR* argv[])
  8. {
  9. srand(time(NULL));
  10. const int maxX = 20, maxY = 30; // set field size
  11. int posX = 10, posY = 10;
  12. int field[maxX][maxY];
  13. for (int i = 0; i < maxX; i++)
  14. {
  15. for (int j = 0; j < maxY; j++)
  16. {
  17. if (i == 0 || i == (maxX - 1) || j == 0 || j == (maxY - 1)) // set frame walls
  18. field[i][j] = 1;
  19. else
  20. field[i][j] = rand() % 4; // set elements
  21. }
  22. }
  23. field[posX][posY] = 4; // set player
  24. char action;
  25. do // move it, move it
  26. {
  27. system("cls");
  28. for (int i = 0; i < maxX; i++)
  29. {
  30. for (int j = 0; j < maxY; j++)
  31. cout << field[i][j];
  32. cout << endl;
  33. }
  34. action = _getche();
  35. if (action == 'w')
  36. {
  37. posX -= 1;
  38. if (field[posX][posY] == 1)
  39. posX += 1;
  40. else
  41. {
  42. field[posX][posY] = 4;
  43. field[posX + 1][posY] = 0;
  44. }
  45. }
  46. if (action == 's')
  47. {
  48. posX += 1;
  49. if (field[posX][posY] == 1)
  50. posX -= 1;
  51. else
  52. {
  53. field[posX][posY] = 4;
  54. field[posX - 1][posY] = 0;
  55. }
  56. }
  57. if (action == 'a')
  58. {
  59. posY -= 1;
  60. if (field[posX][posY] == 1)
  61. posY += 1;
  62. else
  63. {
  64. field[posX][posY] = 4;
  65. field[posX][posY + 1] = 0;
  66. }
  67. }
  68. if (action == 'd')
  69. {
  70. posY += 1;
  71. if (field[posX][posY] == 1)
  72. posY -= 1;
  73. else
  74. {
  75. field[posX][posY] = 4;
  76. field[posX][posY - 1] = 0;
  77. }
  78. }
  79. } while (action != 'q');
  80. }

Все вышеописанные условия и замечания реализованы в коде. Подробно рассмотрим движение в каком-либо одном направлении.

  1. if (action == 'd')
  2. {
  3. posY += 1;
  4. if (field[posX][posY] == 1)
  5. posY -= 1;
  6. else
  7. {
  8. field[posX][posY] = 4;
  9. field[posX][posY - 1] = 0;
  10. }
  11. }

Сначала мы изменяем позицию в соответствии с направлением движения, после проверяем наличие стены в новой позиции, если она там есть – отменяем изменения, если нет – перемещаем игрока, а его прошлую позицию освобождаем. Конечно, этот код не претендует на звание идеального, но, на мой взгляд, для понимания он прост, читаем и в целом работает, что еще нужно от примера?
Что еще можно рассмотреть. Работа с файлами. Мы умеем их считывать и записывать, но пока что не умеем дозаписывать, то есть добавлять информацию к уже существующему файлу.
Вспомним последний пример из прошлого урока.

  1. ifstream file("D:\hello.txt");
  2. string str;
  3. if (file)
  4. {
  5. if (file.good())
  6. {
  7. while (getline(file, str))
  8. {
  9. cout << str << endl;
  10. }
  11.  
  12. }
  13. else
  14. {
  15. cout << "can't open file" << endl;
  16. }
  17. }
  18. else
  19. {
  20. cout << "file not found" << endl;
  21. }
  22. getchar();
  23. return 0;

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

4.PNG

Судя по таблице, нам нужно указать ios_base::app, что и сделаем.

  1. ifstream file("D:\hello.txt");
  2. string str;
  3. if (file)
  4. {
  5. if (file.good())
  6. {
  7. while (getline(file, str))
  8. {
  9. cout << str << endl;
  10. }
  11. }
  12. else
  13. {
  14. cout << "can't open file" << endl;
  15. }
  16. }
  17. else
  18. {
  19. cout << "file not found" << endl;
  20. }
  21. ofstream outFile("D:\hello.txt", ios_base::app);
  22. outFile << " some random words";
  23. getchar();
  24. return 0;

Результат представлен на рисунке ниже: в консольном окне считанная строка из файла, а в файле видна обновленная версия строки (после дозаписи).

3.PNG

На этом, думаю, вводные уроки можно завершать, базис дан, дальше должно быть проще. Последнее задание все-таки рекомендую выполнить и сдать его в течение двух недель с момента выхода статьи. Хоть времени достаточно, все же не стоит затягивать – это со многими сыграло злую шутку. Ну, друзья, удачи.

Аватар пользователя GoodWin

Наконец-то он вышел Smile