|
|
Строка 1: |
Строка 1: |
- | [[Категория:Учебники]] | + | [[Категория:На удаление]] |
- | {{Цикл/Mono}}
| + | |
- | : '''Mono-Мания''' Программирование на современной платформе для новичков
| + | |
- | | + | |
- | ==Mono: Объекты и обобщенные типы==
| + | |
- | | + | |
- | : Объектно-ориентированное программирование кое-кого пугает больше, чем школьников прививки, но '''Пол Хадсон''' собирается обойтись без боли.
| + | |
- | | + | |
- | В теории, теория и практика совпадают, но на практике так бывает редко. Вот почему в наших уроках обучение строится на процессе выполнения: если вы читали этот учебник с самого начала,
| + | |
- | то уже сделали пять полноценных приложений, решающих реальные
| + | |
- | задачи. На данном уроке я хочу отойти от принятой формулы и обсудить довольно сложную теорию программирования: объектно-ориентированное программирование (ООП) и обобщенные типы.
| + | |
- | | + | |
- | Это не штуки, из которых можно делать свои приложения, а просто методы, которые пригодятся вам при программировании на Mono.
| + | |
- | Коль скоро вы это поняли, я покажу вам, как реализовать карточную игру с помощью названных двух методов.
| + | |
- | | + | |
- | ===Классификация объектов===
| + | |
- | | + | |
- | ООП позволяет определять предметы в вашем программном коде и
| + | |
- | даже придавать им желаемое поведение. Вы уже использовали ООП,
| + | |
- | только не догадывались об этом. Создайте новый проект с именем
| + | |
- | '''Geno''' (еще один редкий персонаж Nintendo – уж простите!), и вы уви-
| + | |
- | дите, что ''MonoDevelop'' напишет код по умолчанию:
| + | |
- | | + | |
- | <code>
| + | |
- | class MainClass
| + | |
- | {
| + | |
- | public static void Main(string[] args)
| + | |
- | {
| + | |
- | Console.WriteLine(“Hello World!”);
| + | |
- | }
| + | |
- | }
| + | |
- | </code>
| + | |
- | | + | |
- | Здесь применяется ООП, и программа отлично работает, даже
| + | |
- | если вы не понимаете, что это значит (да и знать не хотите). Но теперь
| + | |
- | знайте: класс – это определение предмета, а объект – это экземпляр
| + | |
- | предмета. Ясно как ночь? Так вот: на вопросы «Какого цвета машина?»,
| + | |
- | «Какая длина у машины?» или «Сколько у машины передач?» ответ
| + | |
- | будет «это зависит от»: что такое машина, представляют все, но каждая
| + | |
- | машина индивидуальна.
| + | |
- | | + | |
- | В терминах ООП, «машина» является классом. Но «машину вообще» увидеть нельзя: это абстрактное понятие. На самом деле мы
| + | |
- | видим «Форды», «Хонды» и так далее, то есть физические реализации
| + | |
- | класса «машина». Итак: машина, находящаяся на шоссе, это объект
| + | |
- | класса «машина». У нее есть цвет, длина, и вы знаете, сколько у нее
| + | |
- | передач, но это просто переменные свойства класса «машина». Другие
| + | |
- | машины, даже той марки, что и ваша, могут сильно отличаться; но все
| + | |
- | они машины.
| + | |
- | | + | |
- | Если для вас это все еще пустой звук, подождите: вы все поймете из кода! А пока ''MonoDevelop'' определил для нас класс '''MainClass''', содержащий метод '''public static void Main()''', который мы все время
| + | |
- | используем. Два странных слова – '''public''' и '''static''' – относятся к ООП.
| + | |
- | | + | |
- | ===Объект Geno===
| + | |
- | | + | |
- | Измените '''MainClass''' на '''Geno''', имя нашего проекта. Теперь замените
| + | |
- | строку '''Console.WriteLine()''' на следующую:
| + | |
- | | + | |
- | <code>
| + | |
- | Geno game = new Geno();
| + | |
- | </code>
| + | |
- | | + | |
- | Этот код создает объект класса '''Geno''' и присваивает его переменной '''game'''. Что представляет собой класс '''Geno'''? В данный момент он
| + | |
- | содержит только '''Main()''' и больше ничего – это просто пустая переменная. Но она рождает интересный вопрос: строка находится внутри
| + | |
- | метода '''Main()''', который находится внутри класса '''Geno'''. Как может '''Geno'''
| + | |
- | создать сам себя? Или – основной вопрос философии: что появилось
| + | |
- | раньше, класс '''Geno''' или метод '''Main()'''?
| + | |
- | | + | |
- | Тут на помощь приходит слово '''‘static’''' – статический. Метод '''Main()''',
| + | |
- | если вы помните, помечен как '''public static void''', и на то есть причина: статические методы могут вызываться без экземпляра класса. Фактически они привязывают метод к классу, просто в организационных целях. Например, если в нашем классе был метод '''СменитьПередачу()''', его применение не имело бы смысла без конкретного экземпляра машины, так как в противном случае, какую передачу надо менять? А как насчет вычисления тормозного пути машины, мчащейся со скоростью 100 км/ч? К машинам это имеет отношение, ноясно, что для этого не требуется реальный объект машины.
| + | |
- | | + | |
- | Таким образом, статический метод '''Main()''' может вызываться без
| + | |
- | существования класса '''Geno''', и мы используем его для создания объекта '''Geno''' так, чтобы можно было играть в карточную игру. Объект '''Geno''' будет контролировать все аспекты игры, поэтому других объектов нам
| + | |
- | не понадобится. Но, ради интереса, мы добавим еще два класса: один
| + | |
- | будет отвечать за игроков, другой за карты. Логика отдается на откуп
| + | |
- | объекту '''Geno''', так что классы игрока и карт предназначены просто для
| + | |
- | хранения данных.
| + | |
- | | + | |
- | Есть еще кое-что, что вам надо знать, прежде чем писать код.
| + | |
- | Иногда нужно, чтобы переменная принимала значение только из определенного набора. Например, набор данных для дней недели – воскресенье, понедельник, вторник и т.д. Для карточной игры требуется, чтобы каждая карта была определенной масти: червы, бубны, трефы или пики. C# позволяет определить масти карт как перечисление:
| + | |
- | | + | |
- | <code>
| + | |
- | public enum Suits { Hearts, Diamonds, Clubs, Spades };
| + | |
- | </code>
| + | |
- | | + | |
- | ===Описываем игру===
| + | |
- | | + | |
- | Запрограммируем детскую игру: она, возможно, знакома вам как
| + | |
- | «Дама червей». Из карточной колоды извлекается одна дама (бубен),
| + | |
- | остается 51 карта. Карты сдаются всем игрокам, втемную. Игроки смотрят на свои карты и сбрасывают пары карт одинакового достоинства:
| + | |
- | например, если у игрока есть две десятки, то он кладет эти две десятки
| + | |
- | на стол. Дама червей не может быть использована как парная карта;
| + | |
- | игрок, которому она досталась, должен ее сохранить.
| + | |
- | | + | |
- | После того, как все игроки выкинули свои пары, первый игрок
| + | |
- | поворачивается к игроку справа и втемную забирает у него произвольную карту. Если в результате у игрока образовались парные
| + | |
- | карты, то он может их сбросить. Игра продолжается, и второй игрок
| + | |
- | поворачивается к игроку справа и вынимает карту – и так далее. В
| + | |
- | конечном счете, каждая карта должна найти себе пару, за исключением дамы червей, а игрок, у которого она на руках, проигрывает. ['''порусски эта игра называется «Акулина» или «Акулька», но непарная дама – пиковая, – прим.ред.''']
| + | |
- | | + | |
- | Нам надо предусмотреть следующие функции:
| + | |
- | * '''Play()''' Эта функция начинает игру, после проведения необходимых настроек.
| + | |
- | * '''ShuffleCards()''' Перетасовать колоду (рандомизировать порядок карт).
| + | |
- | * '''RemovePlayerPairs()''' Поиск и удаление подходящей пары карты у игрока.
| + | |
- | * '''PrintResult()''' Печать результатов игры (у кого осталась червонная дама).
| + | |
- | | + | |
- | {{Врезка
| + | |
- | |Заголовок=Важное замечание
| + | |
- | |Содержание=При работе с MonoDevelop проверьте, что он использует среду .NET 2.0, в противном случае столкнетесь с проблемами. Вам надо изменить настройки для каждого проекта – выберите Project > Options, затем выберите категорию Runtime Options и установите среду выполнения на 2.0, а не 1.1..
| + | |
- | |Ширина=200px}}
| + | |
- | | + | |
- | Также потребуется определить классы '''Player''' и '''Card''', которые будут
| + | |
- | содержать информацию. Вот скелет будущего кода [для большей ясности: '''Suit''' – масть, '''Card''' – карта, '''Hearts''' – черви, '''Diamonds''' – бубны,
| + | |
- | '''Clubs''' – трефы, они же крести, '''Spades''' – пики, '''Player''' – игрок, англ.]:
| + | |
- | | + | |
- | <code>
| + | |
- | using System;
| + | |
- | namespace Geno {
| + | |
- | enum Suits { Hearts, Diamonds, Clubs, Spades };
| + | |
- | class Card {
| + | |
- | public int Val;
| + | |
- | public Suits Suit;
| + | |
- | }
| + | |
- | class Player {
| + | |
- | public int Score;
| + | |
- | }
| + | |
- | class Geno {
| + | |
- | static void Main(string[] args) {
| + | |
- | Geno game = new Geno();
| + | |
- | game.Play();
| + | |
- | }
| + | |
- | void Play() { }
| + | |
- | void ShuffleCards() { }
| + | |
- | void RemovePlayerPairs(int player) { }
| + | |
- | void PrintResults() { }
| + | |
- | }
| + | |
- | }
| + | |
- | </code>
| + | |
- | | + | |
- | Ну да, знаю, здесь куча пустых методов, но они проясняют структуру программы. Заметили, что мне пришлось объявить все переменные
| + | |
- | внутри классов '''Player''' и '''Card''' как '''public'''? Это потому, что переменные
| + | |
- | внутри объекта доступны только внутри самого объекта, чтобы внешние части кода их не затрагивали. Но так как у нас довольно простая
| + | |
- | программа, класс Geno будет делать большую часть работы и использовать классы '''Player и '''Card''' просто для хранения значений. То, что
| + | |
- | эти переменные стали '''public''', значит, что класс '''Geno''' может их читать
| + | |
- | и записывать ['''в серьезных программах так делать не рекомендуется – вместо этого следует определить методы, обеспечивающие доступ извне к закрытым переменным, – прим.ред.'''].
| + | |
- | | + | |
- | ===Введение в обобщенные типы===
| + | |
- | | + | |
- | {{Врезка
| + | |
- | |Заголовок=Скорая помощь
| + | |
- | |Содержание=Если вы не хотите, чтобы переменная была недоступна вне класса, удалите слово '''public'''. Или, если хотите быть точным, напишите вместо него слово '''private'''.
| + | |
- | |Ширина=200px}}
| + | |
- | | + | |
- | У нас есть класс '''Card''', то есть мы можем определить значение карты
| + | |
- | (от 1 до 13) и ее масть. Но мы еще не создали сам объект «карты» и
| + | |
- | не знаем, где их хранить. Здесь приходят на помощь массивы: они
| + | |
- | позволяют хранить множество объектов в одной переменной. У ''Mono''
| + | |
- | есть огромное количество типов массивов, но долгое время наиболее
| + | |
- | используемым был '''ArrayList'''. Он позволяет хранить в массиве любой
| + | |
- | тип объекта и обращаться к нему просто по индексу. Но здесь есть
| + | |
- | проблема: у каждой переменной в ''C#'' есть тип, будь то '''integer, string, Suit''' или любой другой. Так как '''ArrayList''' может содержать переменную
| + | |
- | любого типа, то вам всегда придется сообщать ''Mono'', какой тип используется. Например:
| + | |
- | | + | |
- | <code>
| + | |
- | int i = 1;
| + | |
- | ArrayList numbers = new ArrayList();
| + | |
- | numbers.Add(i);
| + | |
- | int j = numbers[0];
| + | |
- | </code>
| + | |
- | | + | |
- | Здесь будет ошибка – ''Mono'' не сможет преобразовать '''numbers[0]'''
| + | |
- | в '''integer''', даже если мы знаем, что оно уже типа '''integer'''. Вместо этого
| + | |
- | надо написать:
| + | |
- | | + | |
- | <code>
| + | |
- | int j = (int)numbers[0];
| + | |
- | </code>
| + | |
- | | + | |
- | Префикс '''(int)''' значит «обращаться с '''numbers[0]''' как с целым числом», и наш код будет компилироваться правильно. Новые версии ''C#'', включая поставляемую с Fedora Core 6, поддерживает новый способ
| + | |
- | программирования, известный как обобщенные типы '''(generics)'''. И
| + | |
- | если вы когда-либо раньше использовали ''С++'', то знакомы с термином
| + | |
- | «шаблон», а это почти тоже самое – только на вид гораздо легче!
| + | |
- | | + | |
- | Обобщенные типы – это произвольные массивы, которые принимают только один тип переменных. Вам уже не надо приписывать '''(int)''',
| + | |
- | чтобы вытаскивать целые числа из обобщенного списка – туда так и так
| + | |
- | можно помещать только целые числа. В Geno обобщенные типы будут
| + | |
- | использоваться для двух вещей: хранения карт и хранения игроков. У
| + | |
- | каждого игрока будет свой собственный список карт, так как карты из
| + | |
- | колоды раздаются именно игрокам.
| + | |
- | | + | |
- | Добавьте такие две строчки сразу под строкой '''class Geno''':
| + | |
- | | + | |
- | <code>
| + | |
- | List<Card> Cards = new List<Card>();
| + | |
- | List<Player> Players = new List<Player>();
| + | |
- | </code>
| + | |
- | | + | |
- | Этот синтаксис может затуманить мозги, но по-простому он гласит
| + | |
- | «Хочу, чтобы один список содержал переменные типа '''Card''', а второй
| + | |
- | список содержал переменные типа '''Players'''». Вы также должны добавить строку до переменной '''Score''' в классе '''Player''':
| + | |
- | | + | |
- | <code>
| + | |
- | public List<Card> Cards = new List<Card>();
| + | |
- | </code>
| + | |
- | | + | |
- | ===Устанавливаем игру===
| + | |
- | | + | |
- | Оба наших списка '''Players''' и '''Cards''' пусты, поэтому первым заданием
| + | |
- | будет поместить 51 карту в колоду (помните, что мы убрали бубновую
| + | |
- | даму) и разместить несколько игроков. Самым простым способом вста-
| + | |
- | вить карты будет перебрать в цикле все масти ('''Suits''), и для каждой масти посчитать от одного до 13 так, чтобы получить все от тузов до королей [туз считается единицей]. Добавив все карты и всех игроков, завершаем установку, вызывая метод '''ShuffleCards()''' для перетасовки карт.
| + | |
- | | + | |
- | <code>
| + | |
- | foreach (Suits suit in Enum.GetValues(typeof(Suits))) {
| + | |
- | for (int i = 1; i < 14; ++i) {
| + | |
- | Card card = new Card();
| + | |
- | card.Val = i;
| + | |
- | card.Suit = suit;
| + | |
- | if (card.Val == 12 && card.Suit == Suits.Diamonds) continue;
| + | |
- | Cards.Add(card);
| + | |
- | }
| + | |
- | }
| + | |
- | for (int i = 0; i < 4; ++i) {
| + | |
- | Player player = new Player();
| + | |
- | Players.Add(player);
| + | |
- | }
| + | |
- | ShuffleCards();
| + | |
- | </code>
| + | |
- | | + | |
- | Вы видите, что надо вызвать просто '''Cards.Add(card)''', чтобы вставить карту в список из '''Cards'''; все очень просто. Метод '''ShuffleCards()''' пока ничего не делает, потому что он пустой. Нам необходимо пройтись по всему массиву '''Cards''', вытащить отдельные карты и поместить их обратно в произвольную позицию. Тут не обойтись без генератора
| + | |
- | случайных чисел – вставьте эту строку перед '''static void Main''':
| + | |
- | | + | |
- | <code>
| + | |
- | Random Rand = new Random();
| + | |
- | </code>
| + | |
- | | + | |
- | Теперь полная реализация '''ShuffleCards()''':
| + | |
- | | + | |
- | <code>
| + | |
- | void ShuffleCards() {
| + | |
- | for (int i = 0; i < Cards.Count; ++i) {
| + | |
- | Card tmp = Cards[i];
| + | |
- | Cards.RemoveAt(i);
| + | |
- | Cards.Insert(Rand.Next(0, Cards.Count), tmp);
| + | |
- | }
| + | |
- | }
| + | |
- | </code>
| + | |
- | | + | |
- | Здесь показано несколько новых возможностей списков: у них есть
| + | |
- | свойство '''Count''', которое возвращает число содержащихся в них элементов; они содержат метод '''RemoveAt()''', который удаляет элемент в указанной позиции; и у них есть метод '''Insert()''', который вставляет элемент в указанную позицию. Номер позиции определяется переменной '''Rand''', которая может генерировать число между 0 и '''Crads.Count''' (количество карт в колоде).
| + | |
- | | + | |
- | ===Раздаем карты===
| + | |
- | | + | |
- | Наша колода заполнена и стасована. Осталось сдать ее игрокам. Чтобы
| + | |
- | это сделать, будем давать им карты, пока колода не кончится. Это зна
| + | |
- | чит, что нам надо начать с игрока 0 (в ''C#'' списки начинаются с 0), сдать
| + | |
- | карту, перейти к игроку 1, сдать карту и так далее, пока не закончатся
| + | |
- | игроки; потом перейти снова к игроку 0. В коде этот алгоритм будет
| + | |
- | выглядеть вот так:
| + | |
- | | + | |
- | <code>
| + | |
- | int playernum = 0;
| + | |
- | while (Cards.Count > 0) {
| + | |
- | Players[playernum].Cards.Add(Cards[0]);
| + | |
- | Cards.RemoveAt(0);
| + | |
- | ++playernum;
| + | |
- | if (playernum == Players.Count) playernum = 0;
| + | |
- | }
| + | |
- | // удалять начальные пары
| + | |
- | for (int i = 0; i < Players.Count; ++i) {
| + | |
- | RemovePlayerPairs(i);
| + | |
- | }
| + | |
- | </code>
| + | |
- | | + | |
- | В последней части (от комментариев и ниже) уже начинается логи
| + | |
- | ка игры: каждый игрок удаляет пары карт, которые оказались у него в
| + | |
- | начале игры.
| + | |
- | | + | |
- | ===Пишем логику===
| + | |
- | | + | |
- | Метод '''RemovePlayerPairs()''' принимает на вход номер игрока и удаляет
| + | |
- | у него парные карты. Чтобы облегчить понимание, я разделил функцию на две, вот так:
| + | |
- | | + | |
- | <code>
| + | |
- | void RemovePlayerPairs(int player) {
| + | |
- | while (TryPairRemove(player)) {
| + | |
- | ++Players[player].Score;
| + | |
- | }
| + | |
- | }
| + | |
- | </code>
| + | |
- | | + | |
- | Итак, номер игрока поступает в функцию и передается методу
| + | |
- | '''TryPairRemove()'''. Если этот метод вернул '''true''', значит, была найдена
| + | |
- | пара; затем он вызывается снова. В конце концов будут найдены все
| + | |
- | пары, и цикл завершит свою работу.
| + | |
- | | + | |
- | Метод '''TryPairRemove()''' немного сложноват, так как ему надо у каждого игрока взять карту, перебрать остальные его карты на предмет
| + | |
- | совпадения, и если в паре ни одна из карт не является дамой червей
| + | |
- | удалить пару и вернуть '''true'''.
| + | |
- | | + | |
- | <code>
| + | |
- | bool TryPairRemove(int player) {
| + | |
- | Card card1;
| + | |
- | Card card2;
| + | |
- | Player thisplayer = Players[player];
| + | |
- | for (int i = 0; i < thisplayer.Cards.Count; ++i) {
| + | |
- | card1 = thisplayer.Cards[i];
| + | |
- | if (card1.Suit == Suits.Hearts && card1.Val == 12) continue;
| + | |
- | for (int j = i + 1; j < thisplayer.Cards.Count; ++j) {
| + | |
- | card2 = thisplayer.Cards[j];
| + | |
- | if (card2.Suit == Suits.Hearts && card2.Val == 12) continue;
| + | |
- | if (card1.Val == card2.Val) {
| + | |
- | thisplayer.Cards.RemoveAt(j);
| + | |
- | thisplayer.Cards.RemoveAt(i);
| + | |
- | Console.WriteLine(“Player “ + (player + 1) + “ plays “ + CardName(card1) + “ and “ + CardName(card2));
| + | |
- | return true;
| + | |
- | }
| + | |
- | }
| + | |
- | }
| + | |
- | return false;
| + | |
- | }
| + | |
- | </code>
| + | |
- | | + | |
- | Некоторые комментарии по коду:
| + | |
- | * 1 '''j''' начинается с '''i+1''': среди проверенных карт совпадений нет.
| + | |
- | * 2 Проверка совпадения с дамой червей делается и для '''card1''', и для '''card2'''.
| + | |
- | * 3 Если найдено совпадение, сначала удаляется '''j''', затем '''i'''. Удаление элемента из списка заставляет сдвигаться все элементы массива, чтобы закрыть пробел, поэтому первым надо удалять элемент с более высоким индексом.
| + | |
- | * 4 Метод '''CardName()''' будет описан далее.
| + | |
- | | + | |
- | Да, это солидный кусок кода, ведь мы должны дважды перебрать
| + | |
- | карты игрока. Метод '''CardName()''' очень прост и выдает названия
| + | |
- | карт, я не буду приводить его здесь – можете обратиться к исходному коду на диске.
| + | |
- | | + | |
- | {{Врезка
| + | |
- | |Заголовок=Попробуйте другие типы
| + | |
- | |Содержание=Списки – это несортированные массивы, обращение к которым
| + | |
- | осуществляется по номеру позиции, но можно также использовать
| + | |
- | словари, с обращением через определенный вами тип. Например,
| + | |
- | '''Dictionary<string,string>''' хранит строки как ключи массива и как его
| + | |
- | значения. Если ключ '''foo''' имеет значение '''bar''', вы можете найти его так:
| + | |
- | '''MyDictionary[“foo”]'''. Можете использовать любой тип данных – даже
| + | |
- | объекты или другие обобщенные типы, если хотите.
| + | |
- | |Ширина=200px}}
| + | |
- | | + | |
- | ===Последний рывок===
| + | |
- | | + | |
- | Оставшийся код обрабатывает основной игровой цикл: игроки выбирают карты и пытаются найти пары. Самой сложной частью является
| + | |
- | выбор игрока, у которого надо вытащить карту: код должен начать
| + | |
- | искать игрока «справа» от нас (то есть его номер должен быть больше
| + | |
- | нашего), но если он никого не находит, то начинает смотреть с начала
| + | |
- | списка. Если он вернулся обратно, значит, игра закончилась. Если найден подходящий игрок, у него выбирается карта, возможно, сбрасываются новые пары, и игра продолжается.
| + | |
- | | + | |
- | Этот код должен следовать до конца метода '''Play()'''. Он длинный, но
| + | |
- | не такой сложный, если разбить его на части вот так:
| + | |
- | | + | |
- | {{Врезка
| + | |
- | |Заголовок=Скорая помощь
| + | |
- | |Содержание=Есть соблазн использовать целые числа, а не перечисления для списков. Например, воскресенье может быть 0, понедельник – 1, и так далее. Хотя для небольших списков это работает, в случае больших списков легко запутаться. Перечисления работают ничуть не медленнеe целых, так как автоматически преобразуются в целые числа.
| + | |
- | |Ширина=200px}}
| + | |
- | | + | |
- | <code>
| + | |
- | bool finished = false;
| + | |
- | while (!finished) {
| + | |
- | int playersleft = Players.Count;
| + | |
- | for (int i = 0; i < Players.Count; ++i) {
| + | |
- | Player player = Players[i];
| + | |
- | if (player.Cards.Count == 0) {
| + | |
- | --playersleft;
| + | |
- | continue;
| + | |
- | }
| + | |
- | </code>
| + | |
- | | + | |
- | Здесь отслеживается число игроков, оставшихся в игре. Если это
| + | |
- | значение равно 1, то надо выходить; займемся этим позже. Сейчас
| + | |
- | цикл просто обрабатывает каждого игрока.
| + | |
- | | + | |
- | <code>
| + | |
- | int playertouse = -1;
| + | |
- | for (int j = i + 1; j < Players.Count; ++j) {
| + | |
- | if (Players[j].Cards.Count > 0) {
| + | |
- | playertouse = j;
| + | |
- | break;
| + | |
- | }
| + | |
- | }
| + | |
- | if (playertouse == -1) {
| + | |
- | for (int j = 0; j < Players.Count; ++j) {
| + | |
- | if (Players[j].Cards.Count > 0) {
| + | |
- | playertouse = j;
| + | |
- | break;
| + | |
- | }
| + | |
- | }
| + | |
- | }
| + | |
- | </code>
| + | |
- | | + | |
- | Этот блок ищет игрока, у которого нужно вытянуть карту: сперва
| + | |
- | над текущей позицией, потом с начала. Игроки, у которых нет карт,
| + | |
- | автоматически пропускаются.
| + | |
- | | + | |
- | </code>
| + | |
- | if (playertouse == i) {
| + | |
- | finished = true;
| + | |
- | break;
| + | |
- | } else {
| + | |
- | int cardtochoose = Rand.Next(0, Players[playertouse].Cards.
| + | |
- | Count);
| + | |
- | Players[i].Cards.Add(Players[playertouse].
| + | |
- | Cards[cardtochoose]);
| + | |
- | Players[playertouse].Cards.RemoveAt(cardtochoose);
| + | |
- | RemovePlayerPairs(i);
| + | |
- | }
| + | |
- | }
| + | |
- | </code>
| + | |
- | | + | |
- | Теперь начинается самое интересное: если мы добрались сами
| + | |
- | до себя, значит, нет игроков, у которых можно взять карту. В
| + | |
- | противном случае, берем произвольную карту и вызываем метод
| + | |
- | '''RemovePlayerPairs()''', для сброса подходящей пары.
| + | |
- | | + | |
- | <code>
| + | |
- | if (playersleft == 1) finished = true;
| + | |
- | }
| + | |
- | PrintResults();
| + | |
- | </code>
| + | |
- | | + | |
- | Наконец, если игроков не осталось, завершаем цикл. Игра закончена, и '''PrintResult()''' выдает сообщение:
| + | |
- | | + | |
- | <code>
| + | |
- | void PrintResults() {
| + | |
- | Console.WriteLine(“”);
| + | |
- | for (int i = 0; i < Players.Count; ++i) {
| + | |
- | if (Players[i].Cards.Count > 0) {
| + | |
- | Console.WriteLine(“У игрока “ + (i + 1) + “ осталась дама червей!”);
| + | |
- | break;
| + | |
- | }
| + | |
- | }
| + | |
- | }
| + | |
- | </code>
| + | |
- | | + | |
- | Если вы запустите программу, то увидите четырех компьютерных
| + | |
- | игроков, играющих около секунды. Теперь у вас есть понимание о
| + | |
- | классах и объектах, и вы можете извлечь преимущества обобщенных
| + | |
- | типов для хранения ваших объектов. Вы также узнали, как сделать
| + | |
- | несложную карточную игру – полпути к разработке покера или любой
| + | |
- | другой игры. '''LXF'''
| + | |