• Программинг

Нужны источники бесперебойного питания?

Источники бесперебойного питания от дизельстор

C#. Использование System.Console для создания игр в текстовом режиме. Часть 3

Главный цикл

Есть еще одна вещь, о которой вы должны знать, когда будете создавать игру, которая будет работать в окне консоли: главный цикл. Все игры обычно имеют основной цикл, который работает пока игра не завершится. Вот полезные шаблоны, которые можно использовать в главном цикле программы:

  • Console.CancelKeyPress делает курсор невидимым. Таким образом, вас не будет раздражать мерцание;
  • событие Console.CancelKeyPress возникает, когда пользователь нажимает Ctrl-C, но только если свойство Console.TreatControlCAInput не установлено в true;
  • смотрите комментарии к коду статьи для получения больше информации, как обрабатывать событие CancelKeyPress;
  • вызов System.Threading.Thread.Sleep() в главном цикле добавляет небольшую задержку, а также разгружает ваш процессор;
  • устанавливайте свойство Console.KeyAvailable в true, если хотите принимать сообщения с клавиатуры. Таким образом, вы сможете прочитать его с помощью метода Console.ReadKey();

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

1 /// <summary>
2 /// Установите это статическое поле в true для выхода из игры
3 /// </summary>
4 static bool quit = false;
5  
6 /// <summary>
7 /// Точка входа, инициализация игры и старт основного цикла
8 /// </summary>
9 static void Main(string[] args)
10 {
11      // Будьте уверены, что пользователь нажмет Ctrl-C для выхода из игры
12      // Установите Console.TreatControlCAsInput в true если хотите использовать Ctrl-C как  допустимое входное значение
13      Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);
14  
15      Console.CursorVisible = false;
16  
17      /*** Инициализация игры! ***/
18  
19      MainLoop();
20 }
21  
22 /// <summary>
23 /// Обработка нажатия Ctrl-C
24 /// </summary>
25 static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
26 {
27      // К сожалению, в связи с ошибкой в .NET Framework v4.0.30319 вы не можете олаживать это
28      // потому что Visual Studio 2010 выдает ошибку "No Source Available".
30      Console.WriteLine("{0} hit, quitting...", e.SpecialKey);
31      quit = true;
32      e.Cancel = true; // Установка в true позволяет на закрывать программу сразу
33 }
34  
35 /// <summary>
36 /// Главный цикл игры
37 /// </summary>
38 static void MainLoop()
39 {
40      int elapsedMilliseconds = 0;
41      int totalMilliseconds = TIME_LIMIT_SECONDS * 1000;
42      const int INTERVAL = 100;
43  
44      while (elapsedMilliseconds < totalMilliseconds && !quit)
45      {
46           // Задержка на определенный период
47           Thread.Sleep(INTERVAL);
48           elapsedMilliseconds += INTERVAL;
49  
50           /*** Обновление экрана! ***/
51       }
52  
53      Console.WriteLine("Игра окончена!");
54 }

Теперь у вас есть все части, которые нужны для создания игры. Это все, что нужно для создания WordFinder. WordFinder – игра-головоломка, в которой игрок имеет 60 секунд, чтобы найти как можно больше слов в таблице со случайными буквами.

Вы можете загрузить все эти файлы в проект Visual Studio 2010 под названием WordFinder. После этого вы должны создать в проекте текстовый файл с именем words.txt. Убедитесь в том, что он копируется в выходной каталог при компиляции.

Также, вы можете скомпилировать его из командной строки следующим образом:
1. Сохраните каждый из четырех файлов в одну папку.
2. Используя Блокнот, создайте текстовый файл и сохраните его под именем words.txt. Заполните его словами для игры (можно использовать вот этот список).
3. Откомпилируйте ваше приложение:

%SYSTEMROOT%\Microsoft.NET\Framework\v3.5\csc.exe /out:WordFinder.exe Program.cs Game.cs Puzzle.cs WordChecker.cs

4. Когда вы будете запускать игру, убедитесь, что файл words.txt находится в той же папке, что и WordFinder.exe.

Конечно, есть и более элегантное решение, к примеру, использование файла ресурса, но я хотел, чтобы моя игра была быстрая и хардкорная!

Обратите внимание, если вы пользуетесь Блокнотом для сохранения файлов, вы получите предупреждение, что Unicode-символы будут утеряны. Потому что Game.cs содержит символы, которые были вставлены из буфера обмена в Unicode-кодировки. На этот счет у вас есть два варианта. Вы можете сохранить все файлы, используя настройки по умолчанию, которые автоматически преобразуют символы линий в +, - и |. Но если вы хотите сохранить символы Unicode правильно, то в окне сохранения «Сохранить как…» выберите «Unicode» из выпадающего списка выбора кодировок.

Все описанное выше и комментарии к коду помогут вам разобраться, как работают игры. Удачи вам в написании игр в текстовом режиме! Если вам что-то не понравилось, дайте мне об этом знать.

Исходный код игры WordFinder

1 using System;
2 using System.IO;
3 using System.Threading;
4  
5 namespace WordFinder
6 {
7      /// <summary>
8      /// Класс Program содержит главный цикл, окончание игры, запуск таймера,
9      /// и обработку нажития пользователем Ctrl-C для выхода из игры. Весь геймплей и отрисовка
10      /// обрабатывается в классе Game.
11      /// </summary>
12      class Program
13      {
14           /// <summary>
15           /// Длина головоломки в буквах
16           /// </summary>
17           const int PUZZLE_LENGTH = 49;
18  
19           /// <summary>
20           /// Каждая n-я буква должна быть гласной
21           /// </summary>
22           const int VOWEL_EVERY = 5;
23  
24           /// <summary>
25           /// Отведенный лимит времени
26           /// </summary>
27           const int TIME_LIMIT_SECONDS = 60;
28  
29           /// <summary>
30           /// Список слов -- Я скачал отсюда http://unix-tree.huihoo.org/V7/usr/dict/words.html
31           /// и вставил в words.txt, не забудьте установить своейство копирования в целевую директорию
32           /// в "Copy Always" (таким образом он будет в той же папке, где и запускной файл). Конечно, мы можем использовать
33           /// файл ресурса, но использование текстового файла позволяет расширить игру, используя различные списки слов
34           /// </summary>
35           static string[] words = File.ReadAllLines("words.txt");
36  
37           /// <summary>
38           /// Объект для отслеживания игры
39           /// </summary>
40           static Game game;
41  
42           /// <summary>
43           /// Установите это свойство в true для выхода из игры
44           /// </summary>
45           static bool quit = false;
46  
47           /// <summary>
48           /// Текущий пользовательский ввод
49           /// </summary>
50           static string word = String.Empty;
51  
52           /// <summary>
53           /// Входная точка установки экрана, инициализации игры и запуск главного цикла
54           /// </summary>
55           static void Main(string[] args)
56           {
57                // Будьте уверены, что пользователь нажмет Ctrl-C для выхода из игры
58                // Установите Console.TreatControlCAsInput в true если хотите использовать Ctrl-C как  допустимое входное значение
59                Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);
60  
61                Console.CursorVisible = false;
62  
63                game = new Game(PUZZLE_LENGTH, VOWEL_EVERY, words);
64                game.DrawInititalScreen();
65                MainLoop();
66            }
67  
68           /// <summary>
69           /// Обработка нажатия Ctrl-C
70           /// </summary>
71           static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
72           {
73                // К сожалению, в связи с ошибкой в .NET Framework v4.0.30319 вы не можете олаживать это
74                // потому что Visual Studio 2010 выдает ошибку "No Source Available".
76                Console.SetCursorPosition(0, 19);
77                Console.WriteLine("Нажата {0}, выход...", e.SpecialKey);
78                quit = true;
79                e.Cancel = true; // Установка в true позволяет на закрывать программу сразу
80            }
81  
82           /// <summary>
83           /// Главный цикл игры
84           /// </summary>
85           static void MainLoop()
86           {
87                int elapsedMilliseconds = 0;
88                int totalMilliseconds = TIME_LIMIT_SECONDS * 1000;
89                const int INTERVAL = 100;
90  
91                while (elapsedMilliseconds < totalMilliseconds && !quit)
92                {
93                     // Задержка на определенный период
94                     Thread.Sleep(INTERVAL);
95                     elapsedMilliseconds += INTERVAL;
96  
97                     HandleInput();
98  
99                     PrintRemainingTime(elapsedMilliseconds, totalMilliseconds);
100                 }
101  
102                Console.SetCursorPosition(0, 20);
103                Console.WriteLine(Environment.NewLine + Environment.NewLine
104                    + "Игра окончена! Вы нашли {0} слов.", game.NumberFound);
105            }
106  
107           /// <summary>
108           /// Запись оставшегося времени в правый верхний угол экрана
109           /// </summary>
110           /// <param name="elapsedMilliseconds">Пройденное время с начала запуска игры</param>
111           /// <param name="totalMilliseconds">Общее количество милисекудн установленное для игры</param>
112           private static void PrintRemainingTime(int elapsedMilliseconds, int totalMilliseconds)
113           {
114                int milliSecondsLeft = totalMilliseconds - elapsedMilliseconds;
115                double secondsLeft = (double)milliSecondsLeft / 1000;
116                string timeString = String.Format("{0:00.0} секунд осталось", secondsLeft);
117  
118                // Сохранение позиции курсора
119                int left = Console.CursorLeft;
120                int top = Console.CursorTop;
121  
122                // Рисуем время в правом верхнем углу экрана
123                Console.SetCursorPosition(Console.WindowWidth - timeString.Length, 0);
124                Console.ForegroundColor = ConsoleColor.Magenta;
125                Console.Write(timeString);
126  
127                // Восстановление цвета текста консоли и установка курсора на последнюю позицию
128                Console.ResetColor();
129                Console.SetCursorPosition(left, top);
130            }
131  
132           /// <summary>
133           /// Обрабатывать любые нажатия клавиш пользователем
134           /// </summary>
135           static void HandleInput()
136           {
137                Thread.Sleep(50);
138                if (Console.KeyAvailable)
139                {
140                     ConsoleKeyInfo keyInfo = Console.ReadKey(true);
141                     if (keyInfo.Key == ConsoleKey.Backspace)
142                     {
143                          if (word.Length > 0)
144                              word = word.Substring(0, word.Length - 1);
145                      }
146                     else if (keyInfo.Key == ConsoleKey.Escape)
147                     {
148                          word = String.Empty;
149                      }
150                     else
151                     {
152                          string key = keyInfo.KeyChar.ToString().ToUpper();
153                          if (game.IsValidLetter(key))
154                          {
155                               word = word + key;
156                           }
157                      }
158                     game.CurrentInput = word;
159                     game.ProcessInput();
160                     game.UpdateScreen();
161                 }
162            }
163       }
164 }

Файл Game.cs:

1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4  
5 namespace WordFinder
6 {
7      /// <summary>
8      /// Класс Game отслеживает состояние игры и рисует на экране и обновляет его
9      /// Он использует экземпляр класса Puzzle следить за буквами в таблице
10      /// головоломки, а экземпляр WordChecker следить за
11      /// словами и занимается проверкой ответов игрока.
12      /// </summary>
13      class Game
14      {
15           /// <summary>
16           /// Объект WordChecker проверяет слова
17           /// </summary>
18           private WordChecker wordChecker;
19  
20           /// <summary>
21           /// Получить количество найденных слов
22           /// </summary>
23           public int NumberFound
24           {
25                // Объет WordChecker содержит эту информацию
26                get { return wordChecker.NumberFound; }
27            }
28  
29           /// <summary>
30           /// Объект Puzzle содержит буквы и проверяет слова
31           /// </summary>
32           private Puzzle puzzle;
33  
34           /// <summary>
35           /// Текущей пользовательский ввод
36           /// </summary>
37           public string CurrentInput { private get; set; }
38  
39           /// <summary>
40           /// Конструктор класса Game
41           /// </summary>
42           /// <param name="puzzleLength">Количество букв в головоломке</param>
43           /// <param name="vowelEvery">Добавлять гласную каждую n-ю букву</param>
44           /// <param name="validWords">Список проверочных слов</param>
45           public Game(int puzzleLength, int vowelEvery, IEnumerable<string> validWords)
46           {
47                this.wordChecker = new WordChecker(validWords);
48                this.puzzle = new Puzzle(puzzleLength, vowelEvery);
49                CurrentInput = String.Empty;
50            }
51  
52           /// <summary>
53           /// Рисуем на экране консоли стартовую заставку
54           /// </summary>
55           public void DrawInititalScreen()
56           {
57                Console.Clear();
58                Console.Title = "Word finder";
59                puzzle.Draw(25, 3);
60                Console.SetCursorPosition(7, 11);
61                Console.Write("--------------------------------------------------------¬");
62                Console.SetCursorPosition(7, 12);
63                Console.Write("¦");
64                Console.SetCursorPosition(63, 12);
65                Console.Write("¦");
66                Console.SetCursorPosition(7, 13);
67                Console.Write("L=======================================================-");
68                UpdateScreen();
69            }
70  
71           /// <summary>
72           /// Обновляем экран
73           /// </summary>
74           public void UpdateScreen()
75           {
76                // Используем String.PadRight() чтобы убедиться, что желтое поле ввода остается постоянного
77                // размера, вне зависимости от длины слова
78                Console.SetCursorPosition(8, 12);
79                Console.ForegroundColor = ConsoleColor.DarkGray;
80                Console.BackgroundColor = ConsoleColor.Yellow;
81                string message = String.Format("Введите слово #{0}: {1}",
82                        wordChecker.NumberFound, CurrentInput);
83                Console.Write(message.PadRight(54));
84                Console.ResetColor();
85  
86                Console.SetCursorPosition(0, 17);
87                Console.Write("Найдено слов: ");
88                foreach (string word in wordChecker.FoundWords)
89                    Console.Write("{0} ", word);
90  
91                Console.SetCursorPosition(7, 14);
92                Console.Write("Наберайте любые слова, которые вы нашли. <ESC> очищает поле.");
93            }
94  
95           /// <summary>
96           /// Обработка слова при каждом вводе
97           /// </summary>
98           public void ProcessInput()
99           {
100                wordChecker.CheckAnswer(CurrentInput, puzzle);
101            }
102  
103           /// <summary>
104           /// Возвращает true, если нажата правильная буква из набора.
105           /// </summary>
106           /// <param name="key">Буква, которая была нажата</param>
107           /// <returns>True если буква гласная или согласная из таблицы</returns>
108           public bool IsValidLetter(string key)
109           {
110                if (key.Length == 1)
111                {
112                     char c = key.ToCharArray()[0];
113                     return Puzzle.Consonants.Contains(c) || Puzzle.Vowels.Contains(c);
114                 }
115                return false;
116            }
117  
118       }
119 }

Файл Puzzle.cs:

1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4  
5 namespace WordFinder
6 {
7      /// <summary>
8      /// Класс Puzzle содержит таблицу головоломки. Игра использует его для отрисовки
9      /// таблицы на экране, а класс WordFinder используется для проверки игрока,
10      /// ввел ли он букву из таблицы
11      /// </summary>
12      class Puzzle
13      {
14           /// <summary>
15           /// Randomizer
16           /// </summary>
17           private Random random = new Random();
18  
19           /// <summary>
20           /// Согласные (содержит Y)
21           /// </summary>
22           public static readonly char[] Consonants = { 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K',
23                'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z' };
24  
25           /// <summary>
26           /// Гласные (содержит Y)
27           /// </summary>
28           public static readonly char[] Vowels = { 'A', 'E', 'I', 'O', 'U', 'Y' };
29  
30           /// <summary>
31           /// Резервное поле для букв
32           /// </summary>
33           char[] letters;
34  
35           /// <summary>
36           /// Получить буквы из головоломки
37           /// </summary>
38           public IEnumerable<char> Letters
39           {
40                get { return letters; }
41            }
42  
43           /// <summary>
44           /// Номер буквы в головоломке
45           /// </summary>
46           private int puzzleLength;
47  
48           /// <summary>
49           /// Конструктор класса Puzzle
50           /// </summary>
51           /// <param name="puzzleLength">Количество букв в головоломке</param>
52           /// <param name="vowelEvery">Каждая n-я буква гласная</param>
53           public Puzzle(int puzzleLength, int vowelEvery)
54           {
55                this.puzzleLength = puzzleLength;
56  
57                letters = new char[puzzleLength];
58  
59                for (int i = 0; i < puzzleLength; i++)
60                {
61                     if (i % vowelEvery == 0)
62                         letters[i] = Vowels[random.Next(Vowels.Length)];
63                     else
64                         letters[i] = Consonants[random.Next(Consonants.Length)];
65                 }
66            }
67  
68           /// <summary>
69           /// Нарисовать головоломку в выбранном месте экрана
70           /// </summary>
71           /// <param name="left">Координата X</param>
72           /// <param name="top">Координата Y</param>
73           public void Draw(int left, int top)
74           {
75                int oldTop = Console.CursorTop;
76                int oldLeft = Console.CursorLeft;
77  
78                Console.BackgroundColor = ConsoleColor.Gray;
79  
80                // Создать случайную головоломку и нарисовать ее
81                for (int i = 0; i < puzzleLength; i++)
82                {
83                     // Используя перемещение курсора рисуем строки в таблице головоломки
84                     if (i % Math.Floor(Math.Sqrt(puzzleLength)) == 0)
85                     {
86                          Console.CursorTop = top++;
87                          Console.CursorLeft = left;
88                      }
89  
90                     if (Vowels.Contains(letters[i]))
91                         Console.ForegroundColor = ConsoleColor.DarkRed;
92                     else
93                         Console.ForegroundColor = ConsoleColor.DarkBlue;
94  
95                     Console.Write(" {0} ", letters[i]);
96                 }
97  
98                Console.ResetColor();
99  
100                Console.CursorTop = oldTop;
101                Console.CursorLeft = oldLeft;
102            }
103       }
104 }

Файл WordChecker.cs:

1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4  
5 namespace WordFinder
6 {
7      /// <summary>
8      /// Класс WordChecker содержит список правильных слов и проверяет
9      /// буквы введенные пользователем с буквами из таблицы
10      /// </summary>
11      class WordChecker
12      {
13           /// <summary>
14           /// Список слов для проверки
15           /// </summary>
16           private List<string> words = new List<string>();
17  
18           /// <summary>
19           /// Найденные слова
20           /// </summary>
21           private List<string> foundWords = new List<string>();
22  
23           /// <summary>
24           /// Возвращает количество найденных слов
25           /// </summary>
26           public int NumberFound
27           {
28                get { return foundWords.Count; }
29            }
30  
31           /// <summary>
32           /// Возвращает найденные слова
33           /// </summary>
34           public IEnumerable<string> FoundWords {
35                get
36                {
37                     List<string> value = new List<string>();
38                     foreach (string word in foundWords)
39                     {
40                          value.Add(word.ToUpper());
41                      }
42                     return value;
43                 }
44            }
45  
46           /// <summary>
47           /// Конструктор WordChecker
48           /// </summary>
49           /// <param name="validWords">Список правильных слов</param>
50           public WordChecker(IEnumerable<string> validWords)
51           {
52                // Перевести слово в верхний регист и добавить в список
53                foreach(string word in validWords)
54                    this.words.Add(word.ToUpper());
55            }
56  
57           /// <summary>
58           /// Проверить введенное польвателем слова с головоломной
59           /// </summary>
60           /// <param name="word">Проверяемое слово</param>
61           /// <param name="puzzle">Ссылка на объект головоломки</param>
62           public void CheckAnswer(string word, Puzzle puzzle)
63           {
64                // Проверяем что слово не пустое, что такого слова еще нет и в нем содержится не менее 4-х букв
65                if (String.IsNullOrEmpty(word) || foundWords.Contains(word) || word.Length < 4)
66                    return;
67  
68                // Переводим слово в верхний регистр -- строка upperCaseWord должна быть уничтожена,
69                // и поэтому нам нужна копия. Мы удаляем каждую найденную букву, пока в слове не останется букв.
70                // Если буквы останутся, то слово считается не верным
71                string upperCaseWord = word.ToUpper();
72                if (words.Contains(upperCaseWord))
73                {
74                     // Проверяем, что слово состоит из букв головоломки
75                     foreach (char letter in puzzle.Letters)
76                     {
77                          // Удаляем букву, если она содержится в головоломке
78                          if (upperCaseWord.Contains(letter))
79                          {
80                               // Если слово начинается с буквы, иначе Substring(0, index - 1) выдаст исключение
81                               if (upperCaseWord.StartsWith(letter.ToString()))
82                                   upperCaseWord = upperCaseWord.Substring(1);
83                               else
84                               {
85                                    int index = upperCaseWord.IndexOf(letter);
86                                    upperCaseWord = upperCaseWord.Substring(0, index - 1) + upperCaseWord.Substring(index + 1);
87                                }
88                           }
89                      }
90                 }
91  
92                // Если из слова удалены все буквы, то считается что слово найдено.
93                // Издаем звуковой сигнал и добавляем слово в список найденных.
94                if (String.IsNullOrEmpty(upperCaseWord))
95                {
96                     Console.Beep();
97                     foundWords.Add(word);
98                 }
99            }
100       }
101 }