Игра в кости
http://forum.codenet.ru/showthread.php?t=57128
Вроде бы не особо сложная, но и не очень мелкая. Можно было бы поиграть в создание "настоящей программы". Составить нормальное ТЗ (просто выкинуть из того что есть двузначности, добавить пояснения), придумать требования к взаимодействию с пользователем, выбрать тип лога, обосновать этот выбор, создать программу в хорошем ООП-шном стиле и т.д.
Предполагается, что кто-то начнёт, кто-то продолжит, а я буду критиковать и подсказывать (не потому что шибко умный, а потому что люблю критиковать). За интересные ходы возможно буду раздавать зелёные квадраты (вроде это правилам не противоречит).
Язык программирования конечно же C++, как наиболее популярный :)
Как идея? Найдутся желающие?
Вот только меня несколько смущает
Вроде как не очень сложная задачка и я с трудом могу представить в ней несколько классов, а уж тем более наследование и прочие вкусности ООП. Если бы я писал нечто подобное, то, думаю, ограничился бы несколькими функциями (сколько понадобится). Однако, именно поэтому мне это интересно.
то есть историю игры тоже будем сохранять? Ладно, подумаем.
И насчёт взаимодействия с пользователем: думаю, ограничимся консолью?
Тем интереснее попробовать написать с применением ООП. Для целесообразности можно предположить, что в ТЗ будут вносить изменения. Например, компьютер будет предоставлять несколько игроков, или будут несколько стратегий для компьютера, или изменятся правила (например, кость будут бросать не до выпадания единицы, а определенное число раз).
Вот что пишут в исходном ТЗ:
А раз есть запись, то возможно надо сделать и чтение. Хотя в ТЗ пока про это ничего не говорится.
Для начала - да. И тексты вначале будут английские, чтобы не морочиться. Интересно было бы потом перейти к GUI, чтобы ООП показал свои возможности, но думаю, тут будет много ненужных споров.
- определение проблемы;
- выработка требований;
- создание плана конструирования;
- разработка архитектуры ПО, или высокоуровневое проектирование;
- детальное проектирование;
- кодирование и отладка;
- блочное тестирование;
- интеграционное тестирование;
- интеграция;
- тестирование системы;
- корректирующее сопровождение.
- определение проблемы;
- выработка требований;
- детальное проектирование;
- кодирование и отладка;
Поэтому для начала надо сформулировать адекватное ТЗ.
Затем надо определиться с инструментами и технологиями. Например, будем ли для лога использовать xml или csv, или какой-то свой формат. Да и компилятор надо выбрать.
Тут я полностью согласен. Закодить такую программу без всяких предварительных планов можно довольно быстро. Однако на собственном опыте много раз убеждался, что едва проект по сложности превысит некоторый порог, так сразу начинаются проблемы без предварительного тщательного планирования. Когда пишу что-то для себя, для души, постоянно добавляю в уже почти готовую программу какие-нибудь фичи, и так до бесконечности... У меня что ни программа - то симфония. Неоконченная... :(
Мне без разницы. Но склоняюсь к csv. Свой формат городить вроде ни к чему, а xml несколько сложнее, чем csv, но его широкие возможности здесь не дадут выгоды.
Мне опять же без разницы. Вообще я пользуюсь Visual Studio, но если появятся ещё желающие (ау-у! есть кто?), то готов перейти на что-нибудь иное, по требованию коллектива.
- определение проблемы;
Думал-думал над этим пунктом... В итоге надумал: Kogrom, раз уж вызвался критиковать и управлять, то выставляй задание, выступай в роли заказчика :).
PS: Макконнелла читал, "Совершенный код" у меня в бумажном варианте имеется. Превосходная вещь.
Ок. Плюс к тому - его проще читать сторонними программами. То есть можно спокойно не добавлять функцию чтения лога.
Я бы предпочел что-то свободное и простое. Типа этого:
http://www.codeblocks.org/
Если смущает, то, что официальный дистрибутив обновлялся больше года назад, то можно собрать свой. Я когда-то развлекался так:
https://code.google.com/p/codeblocks-mix/
Ок. Управлять я не брался, но играть роль заказчика вероятно смогу :)
Вопрос по техзаданию: сколько будет кубиков в игре - 1, 2 или больше? Если больше одного, то как выводить выброшенные очки: одним числом-суммой, или несколькими числами (по количеству костей)?
Понимаю, что ещё не завершено проектирование, но меня так и тянет покодить. Набросок класса "Игральный кубик"
class Dice
{
public:
unsigned int count; // количество бросков за всю игру
Dice()
{
srand(time(0));
count = 0;
}
enum DiceValue // нужно ли?
{
one = 1,
two = 2,
three = 3,
four = 4,
five = 5,
six = 6
};
DiceValue Roll() // бросить кость
{
count++;
return (DiceValue)(rand() % 6);
}
};
Бегу вперёд паровоза, по ещё непроложенным путям :).
Разработать программу, реализующую игру в кости человека с компьютером. Правила игры следующие: каждый игрок (компьютер или пользователь) в свою очередь хода бросает кость столько раз, сколько хочет, до выпадения единицы.
Если игрок заканчивает ход до выпадения единицы (по своему желанию), то записывает себе сумму выпавших за эти бросания очков. Если он выбрасывает единицу, то он не записывает на свой счет ничего. То есть в данном случае весь ход будет потрачен впустую. После выпадения единицы ход заканчивается принудительно.
Выигравшим считается тот, кто первый наберет (превысит) 100 очков.
Предусмотреть возможность изменения стратегии игры со стороны компьютера. Например, компьютер может вести более жадную стратегию (больше бросков за ход) если проигрывает и более надежную (меньше бросков за ход), если выигрывает. Или наоборот.
Предусмотреть запись всех шагов игры в лог файл и определение случайным образом игрока делающего первый ход.
Предполагаемые изменения во второй версии программы: вводится несколько игроков (ботов), за которых играет компьютер, предусматривается режим игры без пользователя, предусматривается загрузка файлов с описанием стратегий ботов.
Так же могут быть изменены правила: например, будет введён бонус за дубль и т.п.
Пока кубик будет один. В будущих версиях может и два будет. Это позволит сделать правила более интересными.
Класс-кость нужен, это очевидно. Но не понял зачем там count. Кость будет считать сколько раз её бросили?
На счет enum DiceValue тоже не уверен. Но больше смущает rand() % 6. А вдруг кубик не шестисторонний. Был у меня когда-то восьмисторонний кубик :)
class Dice
{
private:
int facets; // количество граней кости
public:
unsigned int count; // количество бросков за всю игру
Dice(int facets = 6)
{
if (facets < 2 /* && facets > ? */)
{
throw exception("Argument out of range"); // вариант 1
//facets = 6; // вариант 2
}
this->facets = facets;
srand((unsigned int)time(0));
count = 0;
}
int Roll() // бросить кость
{
count++;
return rand() % facets + 1;
}
};
Мне всё же кажется, что сделать подсчёт общего количества бросков в самом классе кубика вполне разумно. Вне класса можно считать кол-во бросков отдельными игроками.
Каким значением ограничим максимальное количество граней кости?
Как будем сигнализировать о превышении этого значения? Выбрасыванием исключения или установкой значения по умолчанию?
Заменить ли магические числа на
#define MaxFacets ?
#define DefaultFacets 6
Идея неплохая, на мой взгляд. Мог бы поучаствовать (тем более я обозначен в теме как один из предполагаемых участников), но, в настоящее время, в силу нехватки времени, мое участие может иногда быть запоздалым... Тем более, что тут требуется выделить время на осмысление ТЗ и на проектирование, а не не накидать за 10-15 минут нерадивому студенту решение задачки или ответить на вопрос... А перед Новым Годом и несколько дней после него буду отсутствовать, по причине отъезда, и участвововать вообще не смогу. Так что полноценным участником в ближайшее время вряд ли буду, может буду время от времени появляться и что-то писать в теме, но на серъезное участие с моей стороны рассчитывать пока не приходится. А поскольку проект простой, к тому времени как активно смогу включиться - скорее всего будет закончен. :)
...
Предусмотреть возможность изменения стратегии игры со стороны компьютера. Например, компьютер может вести более жадную стратегию (больше бросков за ход) если проигрывает и более надежную (меньше бросков за ход), если выигрывает. Или наоборот.
Вот тут следует расписать поподробнее и поточнее, на мой взгляд. Наверно, под стратегией компьютера правильней понимать алгоритм принятия компьютером тактических решений в ходе игры (сколько раз пытаться бросить кость), в зависимости от текущей ситуации (ближе ли к выигрышу компьютер, чем человек, или дальше). И тогда фразу "Предусмотреть возможность изменения стратегии игры со стороны компьютера" можно перефразировать в "В стратегии компьютера предусмотреть возможность изменения тактики игры компьютера", и / или "Должен быть реализован набор нескольких стратегий компьютера, перед началом игры игрок-человек имеет возможность выбора, с какой стратегией будет с ним играть его компьютерный противник". Так что требуется, ИМХО, уточнение данного пункта.
Ну почему же, у меня вот сразу вырисовывается пример, как можно использовать несколько классов:
- Абстрактный класс "игрок", имеет переменную - количество накопленных за предыдущие ходы очков, метод бросания кости, и метод принятия решения (чисто виртуальная функция) - прекращать броски или нет.
- "Игрок-человек" - соответствует пользователю, играющему с программой, для этого класса метод принятия решения будет запрашиванием в консоли от пользователя, прекращать броски или нет.
- Несколько классов "Игрок-компьютер", каждый класс со своей стратегией игры, соответственно у каждого класса будет своя реализация метода принятия решений.
http://www.codeblocks.org/
Поддерживаю, т.к. для такого простого проекта навороты Visual Studio не нужны, а для учебных целей вполне сгодится Code::Blocks (который я сам для учебных целей использую). Но поскольку, как я уже сказал, я пока не могу быть полноценным участником - можете мое мнение спокойно проигнорить. :)
Мне всё же кажется, что сделать подсчёт общего количества бросков в самом классе кубика вполне разумно. Вне класса можно считать кол-во бросков отдельными игроками.
Среди паттернов GRASP есть так называемый Information Expert, который утверждает, что обязанности должны быть назначены объекту, который владеет максимумом необходимой информации для выполнения обязанности. Возможно, для подсчета бросков это будет кость. Может даже для подсчета очков за ход. Но пока мы не выяснили, какие объекты у нас есть, поэтому трудно рассуждать.
Как будем сигнализировать о превышении этого значения? Выбрасыванием исключения или установкой значения по умолчанию?
Я намекал на то, что используется магическая переменная. На самом деле, наверняка кость сама должна знать, сколько граней у неё. То есть это может быть константа класса, записываемая прямо в классе: static const int facets = 6;
Тоже неплохо.
На данном этапе я понимаю именно так. То есть не человек выбирает как будет играть бот, а бот меняет тактику.
- Абстрактный класс "игрок", имеет переменную - количество накопленных за предыдущие ходы очков, метод бросания кости, и метод принятия решения (чисто виртуальная функция) - прекращать броски или нет.
- "Игрок-человек" - соответствует пользователю, играющему с программой, для этого класса метод принятия решения будет запрашиванием в консоли от пользователя, прекращать броски или нет.
- Несколько классов "Игрок-компьютер", каждый класс со своей стратегией игры, соответственно у каждого класса будет своя реализация метода принятия решений.
До прочтения этого сообщения я пытался анализировать предметную область. В первом приближении у меня получилось так:
У нас есть 2 игрока. Игроки делятся на 2 подкласса: бот и пользователь. Также должен быть некий диспетчер хода, ведущий. Он определяет очередность ходов, момент передачи хода и момент завершения игры. Есть объект-кость, который используется во время хода. В общем, кость может создаваться на время хода, или быть статическим объектом (некая замена модулю).
Кроме того, должны быть объекты взаимодействия с пользователем и объект записи в лог.
В чем-то идеи сходятся. Хорошо бы ещё нарисовать пару эскизных диаграмм UML (классов и взаимодействия). Было бы нагляднее.
1. Создается объект - Ведущий.
2. Ведущий создает другие объекты: игроков, объект интерфейса.
3. Ведущий определяет игрока, который будет ходить первым. Пусть игроком вначале будет бот.
4. Ведущий сообщает боту, чтобы тот сделал ход.
5. Бот создаёт объект "кость".
6. Бот бросает кость некоторое количество раз (зависит от текущей стратегии бота).
7. Бот сообщает ведущему, что ход закончен.
8. Ведущий сообщает объекту-игроку, чтобы тот сделал ход.
9. Игрок создаёт объект "кость".
10. Игрок бросает кость.
11. Если не выпала единица, то игрок через объект интерфейса обращается к пользователю, чтобы тот ответил, нужно ли ещё бросать кость.
12. Если игрок подтверждает, то переходим на 2 пункта назад.
13. Повторяем пункты 4-12 пока у одного игрока не накопится 100 очков.
14.Если у кого-то накопилось не менее 100 очков, то игра завершается. Пользователю выводится соответствующее сообщение.
В точку :)
У меня никак руки не дойдут вплотную заняться освоением UML.
Понять схему не проблема, так что можешь делать UML, если есть такое желание. Вернее, это даже нужно - мне пойдёт на пользу.
Сценарий принят. Постараюсь набросать каркас приложения.
1. Создается объект - Ведущий.
2. Ведущий создает другие объекты: игроков, объект интерфейса.
3. Ведущий определяет игрока, который будет ходить первым. Пусть игроком вначале будет бот.
4. Ведущий сообщает боту, чтобы тот сделал ход.
5. Бот создаёт объект "кость".
6. Бот бросает кость некоторое количество раз (зависит от текущей стратегии бота).
7. Бот сообщает ведущему, что ход закончен.
8. Ведущий сообщает объекту-игроку, чтобы тот сделал ход.
9. Игрок создаёт объект "кость".
10. Игрок бросает кость.
11. Если не выпала единица, то игрок через объект интерфейса обращается к пользователю, чтобы тот ответил, нужно ли ещё бросать кость.
12. Если игрок подтверждает, то переходим на 2 пункта назад.
13. Повторяем пункты 4-12 пока у одного игрока не накопится 100 очков.
14.Если у кого-то накопилось не менее 100 очков, то игра завершается. Пользователю выводится соответствующее сообщение.
Итак, от этапа составления ТЗ уже произошел переход к проектированию решения...
По данному сценарию, думаю также требуется кое-какое пояснение. Каждый игрок, перед началом хода, имеет накопленное за предыдущие ходы количество очков. И тут есть два варианта в плане того, "знает" ли каждый игрок, помимо количества своих очков, текущее количество очков игрока-противника:
1. Знает - объекты игрок (человек) и бот раполагают информацией о том сколько очков у другого игрока (эта информация может, например, передаваться ведущим перед совершением хода), при этом пользователю это число высвечивается на экране, а для бота - хранится в виде переменной (переменной класса, либо локальной переменной - входным параметром функции - метода принятия решений по части текущей тактики игры). Игроки соответственно имеют возможность менять тактику игры, исходя из того насколько близок он и противник к победе.
2. Не знает (информация игроку о количестве очков противника не передается), и соответственно игрок знает напрямую только насколько он сам близок к победе, а насчет противника - может судить только косвенно (например, по количеству уже сделанных ходов - если ходов сделано уже много, то есть вероятность что противник скоро победит). Т.е. этакая "игра вслепую".
И тут нужно определиться: взять ли первый вариант (для обоих игроков), либо второй, либо опционально (по выбору пользователя в начале игры) - "в открытую" либо "игра вслепую". Можно даже реализовать возможность выбора, чтобы один из игроков знал о количестве очков другого игрока, а другой - нет.
И еще можно реализовать возможность переключения от одного варианта к другому прямо в ходе игры, по обоюдному согласию игроков. Правда, тогда придется для игрока-бота создавать алгоритм решения - стоит ли предложить противнику перейти к игре вслепую либо наоборот - к игре в открытую. А также - если от противника поступает предложение о смене стиля игры - согласиться или нет.
Сказал два, а рассказал о трёх, если учесть комбинированный :)
Я предполагаю, что и игроку будет интереснее знать, сколько очков набрал бот, и у бота будет больше данных для стратегии, если он будет знать, сколько очков у игрока.
Предлагаю остановиться на таком варианте.
Думаю, игроки будут узнавать данные о сопернике через ведущего (если такой объект будет). В случае, если участников в игре будет больше двух, то ведущий будет вероятно передавать максимум. Для бота вроде бы и не нужно большего.
В данном случае, структура связей будет в виде звёздочки. Если играющие будут обмениваться данными напрямую, то структура будет сложнее.
Предлагаю остановиться на таком варианте.
Для первой версии программы (простой вариант игры - два игрока, одна стратегия бота, в рамках которой он может менять тактику) - согласен. Но для второй версии считаю нужным реализовать возможность игры вслепую. Для нее кстати стратегия бота должна быть другой.
Ну если хотя бы исходить из теории вероятности, то все равно может быть полезно знать количество очков у каждого из противников. Допустим у бота два противника. Согласись, что в ситуации, когда у обоих противников бота по 70 очков, в общем случае шансов проиграть больше, чем когда у одного противника 70, а у другого - всего 10? И соответственно стратегия может быть разной.
И еще в случае, если игроков будет больше двух (второй этап разработки игры), то можно также придумать разные варианты завершения игры:
1. Как только какой-нибудь игрок наберет 100 очков - игра заканчивается, игрок объявляется победителем, остальные - проигравшими.
2. После того как какой-нибудь игрок наберет 100 очков, но при этом есть два или более игроков, еще не набравших 100 очков, то игра не заканчивается. Не набравшие 100 очков игроки продолжают делать ходы, пока кто-нибудь также 100 очков не наберет. Так до тех пор, пока не останется только один игрок, который не набрал 100 очков, который объявляется проигравшим. Остальные игроки занимают места: кто первым набрал 100 очков - первое, кто вторым - второе и т.д.
Возможно, что в зависимости от вариантов, стратегия также может быть разной, т.к. добиться больше шансов выиграть - это одно, а добиться более-менее гарантированно получить какое-либо место (пусть не первое) - это другое.
И еще у меня возникла идея, но это возможно для второго этапа игры, а возможно что и для третьего (возможно что она потребует более тщательной проработки). Реализовать самообучающегося бота, который, играя с полььзователем, пробует разные варианты стратегий, копит статистику - при какой стратегии чаще получается выиграть, и затем ее придерживается. Если начал проигрывать при выбранной стратегии - то опять пытается менять стратегию, и т.д. Например, можно было бы реализовать так. У бота есть предопределенный набор мини-стратегий (например, стремиться бросить 1 раз; 2 раза; 3 раза; 4 раза; стремиться бросать пока в течение хода не выброшено какое-то кол-во очков, и т.д.), и предопределенный набор событий, при которых, возможно, стоит поменять текущую мини стратегию на другую (например, такими событиями могут быть накопление ботом либо его противником 20, 40, 60, 80 очков; разница в накоплении очков между ботом и противником -80; -60; -40; -20; 0; 20; 40; 60; 80). Но это вкратце, тут для большей ясности мне следовало бы подробнее описать что я имею в виду... Если вариант предоставляется перспективным, то можно потом его проработать...
Не совсем понял - зачем? Странным представляется сценарий: бросал, бросал кубик и вдруг проиграл. Ну, или выиграл. Ни догоняешь, ни отрываешься. Нет азарта.
1. Как только какой-нибудь игрок наберет 100 очков - игра заканчивается, игрок объявляется победителем, остальные - проигравшими.
2. После того как какой-нибудь игрок наберет 100 очков, но при этом есть два или более игроков, еще не набравших 100 очков, то игра не заканчивается. Не набравшие 100 очков игроки продолжают делать ходы, пока кто-нибудь также 100 очков не наберет. Так до тех пор, пока не останется только один игрок, который не набрал 100 очков, который объявляется проигравшим. Остальные игроки занимают места: кто первым набрал 100 очков - первое, кто вторым - второе и т.д.
Тогда не максимум, а массив результатов.
Но всё это расчеты на будущее. Пока же надо сделать простейший вариант для двух игроков. А кода что-то не видать (если не считать тот класс кости). Наверное, готовится шедевр... Подожду ещё день и если код не появится, буду делать свои наброски. Чтобы другие не теряли интерес - кодить буду на Python :)
Не надо Python! :eek: В таком случае над проектом будет работать всего один человек, и это будет Kogrom.
Шедевр, к сожалению, не готовится. Я увлёкся освоением UML :). Открыв Крэга Лармана, понял, откуда ноги растут у игры в кости ;).
Я тут присмотрел софтинку StarUML для построения диаграмм UML. Как только расчищу место на винте (сцуко, кончилось :(), установлю её и попробую UML на практике.
Теперь насчёт стратегии игры в кости. Я за точность формулировок! При нынешних правилах - все выброшенные очки больше 1 начисляются, при выпадении единицы ход переходит к другому игроку - выигрышная стратегия всего одна: бросать кубик, пока не выпадет единица. При таких правилах возможна лишь стратегия поддаваться противнику - передавать ему ход, даже если не выпала единица.
Ок.
Я тут присмотрел софтинку StarUML для построения диаграмм UML. Как только расчищу место на винте (сцуко, кончилось :(), установлю её и попробую UML на практике.
Один продвинутый в проектировании человек рекомендовал Microsoft Visio. Я пытался использовать бесплатную альтернативу - Dia, но уж больно она неудобная. В результате остановился на MS Paint (стандартный редактор Windows). Для черновиков он хорошо подошёл, а большего мне и не надо.
Ты не понял правил. Если у игрока выпадает единица, то он теряет все очки, набранные за ход. Игроку не выгодно ждать, пока выпадет единица.
В уточняющем ТЗ я это пытался донести в доступной форме.
В уточняющем ТЗ я это пытался донести в доступной форме.
Каюсь, действительно прочитал невнимательно.
Ок, стратегия понята. Буду кодить.
Хех, а я-то думал что пока идет этап проектирования решения. А ты, оказывается, уже кода ждешь. :)
Тогда вот мой набросок в соответствии с тем, как я представлял структуру программы. Использовал сделанный ранее koodeer'ом класс Dice. Метод принятия решений ботом пока сделал примитивный, т.к. разработка алгоритма стратегии игры бота - не такая простая вещь как может показаться.
P.S. Проверял в Code::Blocks - вроде работает.
#include <conio.h>
using namespace std;
class Dice
{
private:
int facets; // количество граней кости
public:
unsigned int count; // количество бросков за всю игру
Dice(int facets = 6)
{
this->facets = facets;
srand((unsigned int)time(0));
count = 0;
}
int Roll() // бросить кость
{
count++;
return rand() % facets + 1;
}
};
class Player
{
protected:
unsigned int points;
unsigned int turnsPassed;
unsigned int throwDice();
const string name;
public:
Player(const string&);
virtual ~Player();
virtual unsigned int makeTurn(unsigned int) = 0;
string announce();
};
Player::Player(const string& str) : points(0), turnsPassed(0), name(str) {}
Player::~Player() {}
unsigned int Player::throwDice()
{
Dice * pDice = new Dice;
unsigned int result = pDice->Roll();
delete pDice;
return result;
}
string Player::announce()
{
return name;
}
class HumanPlayer : public Player
{
public:
HumanPlayer();
virtual unsigned int makeTurn(unsigned int);
};
HumanPlayer::HumanPlayer() : Player("user player") {}
unsigned int HumanPlayer::makeTurn(unsigned int otherPartyPoints)
{
cout << "\n----------------------\n";
cout << announce() << ", your turn # " << turnsPassed++ << ", you have " << points << " points\n";
cout << "your adversary has " << otherPartyPoints << " points\n";
cout << "start now rolling the dice\n";
unsigned int totalResult = 0, throws = 0, throwResult;
bool continueRolls = true;
while(continueRolls)
{
++throws;
cout << "Roll # " << throws << ": ";
throwResult = throwDice();
cout << "rolled " << throwResult << " points";
if(throwResult == 1)
{
cout << "\nyou rolled 1 and loose all points rolled in this turn\n";
totalResult = 0;
continueRolls = false;
}
else
{
totalResult += throwResult;
cout << "; total " << totalResult << " points\n";
cout << "press 'y' if you wish to continue rolls\n";
char c;
cin >> c;
if(c != 'y')
continueRolls = false;
}
}
cout << "ending turn, you got " << totalResult << " in " << throws << " rolls\n";
points += totalResult;
cout << "Press any key to continue\n";
_getch();
return totalResult;
}
class RobotPlayer : public Player
{
public:
RobotPlayer();
virtual unsigned int makeTurn(unsigned int);
};
RobotPlayer::RobotPlayer() : Player("computer player") {}
unsigned int RobotPlayer::makeTurn(unsigned int otherPartyPoints)
{
cout << "\n----------------------\n";
cout << announce() << ", your turn # " << turnsPassed++ << ", you have " << points << " points\n";
cout << "your adversary has " << otherPartyPoints << " points\n";
cout << "start now rolling the dice\n";
unsigned int totalResult = 0, throws = 0, throwResult;
bool continueRolls = true;
int diffPoints = (int)otherPartyPoints - (int)points;
unsigned int wishedTriesCount = 1;
if(diffPoints > 80)
wishedTriesCount = 6;
else if(diffPoints > 60)
wishedTriesCount = 5;
else if(diffPoints > 40)
wishedTriesCount = 4;
else if(diffPoints > 20)
wishedTriesCount = 3;
else if(diffPoints > 0)
wishedTriesCount = 2;
while(continueRolls)
{
++throws;
cout << "Roll # " << throws << ": ";
throwResult = throwDice();
cout << "rolled " << throwResult << " points";
if(throwResult == 1)
{
cout << "\nyou rolled 1 and loose all points rolled in this turn\n";
totalResult = 0;
continueRolls = false;
}
else
{
totalResult += throwResult;
cout << "; total " << totalResult << " points\n";
if(throws < wishedTriesCount)
cout << "you decided to continue rolls\n";
else
{
cout << "you decided to end this turn\n";
continueRolls = false;
}
}
}
cout << "ending turn, you got " << totalResult << " in " << throws << " rolls\n";
points += totalResult;
return totalResult;
}
class Game
{
static const unsigned int maxPoints = 100;
static const unsigned int playersQuantity = 2;
std::pair<Player*, unsigned int> players[playersQuantity];
unsigned int turnsPassed;
Game(const Game&);
Game operator=(const Game&);
public:
Game();
~Game();
void Play();
};
Game::Game() : turnsPassed(0)
{
for(unsigned int i = 0; i < playersQuantity; ++i)
{
if(i % 2)
players.first = new RobotPlayer;
else
players.first = new HumanPlayer;
players.second = 0;
}
}
Game::~Game()
{
for(unsigned int i = 0; i < playersQuantity; ++i)
delete players.first;
cout << "Game over\n";
}
void Game::Play()
{
unsigned int i1 = playersQuantity - 1, i2 = 0;
do
{
if(i1 == playersQuantity - 1)
{
i1 = 0;
i2 = playersQuantity - 1;
++turnsPassed;
}
else
{
++i1;
i2 = 0;
}
players[i1].second += players[i1].first->makeTurn(players[i2].second);
}
while(players[i1].second < maxPoints);
cout << "player " << players[i1].first->announce() << " wins, having got " << players[i1].second << " in " << turnsPassed + 1 << " turns\n";
}
int main()
{
Game* pGame = new Game;
pGame->Play();
delete pGame;
}
Я жду хоть того, хоть другого. Наверное, надо было настоять на диаграммах. Тогда было бы удобнее рассуждать.
Замечания (как и обещал):
1. Свойство count класса Dice не используется.
2. Функции makeTurn громоздкие (слишком много строк), части кода в них дублируются.
3. Игроки знают, как работать с интерфейсом вывода, то есть с Видом в терминологии MVC. С чего бы это? А если я перенесу код в GUI, то мне придется переделывать весь этот класс.
4. Метод Game::Play() я не смог понять с ходу - слишком наворочено, да и имена i1, i2 радуют.
Ну и предложение по броску кости:
#include <vector>
#include <conio.h>
using namespace std;
class Dice
{
private:
static const size_t FACETS = 6; // количество граней кости
static const size_t BREAK_FACET = 1;
public:
static void Roll(const size_t count, vector<int> &result) // бросить кость
{
result.clear();
srand((unsigned int)time(0));
for(size_t i = count; i > 0; --i)
{
size_t rollResult = rand() % FACETS + 1;
if (rollResult != BREAK_FACET)
result.push_back(rollResult);
else
break;
}
}
};
int main()
{
vector<int> res;
Dice::Roll(5, res);
for(size_t i = 0; i < res.size(); ++i)
cout << res << endl;
return 0;
}
Возможно, есть ошибки в этом коде. Он только идею иллюстрирует.
Совершенно верно, лучше мой пример не смотреть пока что, хотя бы потому что он быдлокодерский и очень далек от совершенства. Можно потом посмотреть (когда будет свое), для сравнения...
В моем коде оно не нужно, я просто скопипастил пример класса koodeer'а, не посмотрев хорошенько что из этого класса мне реально нужно, а что нужно удалить за (пока) ненадобностью. Но судя по комментарию, если оно и в самом деле нужно, то оно должно быть, как я понимаю, static unsigned int.
Да, верно. Я сначала хотел сделать 2 совсем разных функции, для человека - чтобы путем диалога с пользователем производились броски кости, а для компьютера - чтобы ничего не выводилось. Но потом подумал - нужно чтобы пользователь видел, как играет компьютер, сколько раз бросил кость и что у него выпадало. Потому функцию для бота создал на основе функции для человека, изменив только алгоритм принятия решения. Думаю что общую часть кода вынести в отдельную функцию базового класса (невиртуальную), а метод принятия решения - сделать виртуальной функцией и для нее уже создать разные версии в классах-наследниках.
Это пока не проработал - создал пока что для случая когда жестко задан интерфейс - консоль. Для доработки у меня пока такая идея - создать абстрактный класс Interface, в котором будут объявлены функции для ввода и вывода значений различных типов. От этого класса создаются наследники - реализации конкретных типов интерфейса. В классе Player добавить переменную - указатель на объект Interface. Что-то вроде такого (пока единственный интерфейс - консоль, если понадобится другой - то тогда нужно будет создать новый класс, унаследованный)
{
public:
Interface();
virtual ~Interface();
virtual void out(const char*) = 0;
virtual void out(const string&) = 0;
virtual void out(int) = 0;
virtual void in(char*) = 0;
};
class consoleIfase : public Interface
{
public:
virtual void out(const char*);
virtual void out(const string&);
virtual void out(int);
virtual void in(char*);
};
void consoleIfase::out(const char* str)
{
cout << str;
}
void consoleIfase::out(const string& str)
{
cout << str;
}
void consoleIfase::out(int n)
{
cout << n;
}
void consoleIfase::in(char* c)
{
cin >> *c;
}
class Player
{
//...
Interface * pIface;
public:
Player(const string&, Interface*);
};
Player(const string& str, Interface* iface) : name(str), pIface(iface) {}
И в тех функциях где производится ввод-вывод, заменить cout и cin на pIface->out и pIface->in (тогда, если интерфейс сменится, переписывать код класса не придется):
{
totalResult += throwResult;
cout << "; total " << totalResult << " points\n";
cout << "press 'y' if you wish to continue rolls\n";
char c;
cin >> c;
if(c != 'y')
continueRolls = false;
}
// сделать
{
totalResult += throwResult;
pIface->out("; total ");
pIface->out(totalResult);
pIface->out(" points\n");
pIface->out("press 'y' if you wish to continue rolls\n");
char c;
pIface->in(&c);
if(c != 'y')
continueRolls = false;
}
i1 - это номер текущего игрока (того чья очередь хода), i2 - номер его противника. Вообще я делал пример с ориентиром на то что будет второй этап - когда игроков может быть больше двух - для этого и сделал переменную playersQuantity. Но чтобы было сделано толково - пока не сделал. Надо будет эту функцию дорабатывать.
Конечно, пользователь должен видеть, как играет бот. Надо подумать, как это правильнее реализовать.
Думаю, что не стоит делать универсальную обёртку для потоков ввода-вывода. Надо заточить класс интерфейса под конкретную задачу.
Далее. Не вижу смысла знать об этом классе объектам-игрокам. Игрок-бот должен вычислять стратегию и в соответствии с ней управлять костью. Человеческому игроку нужно от внешнего мира только число бросков кубика за ход. На этом их функции заканчиваются. Не думаю, что надо их нагружать функциями общения с пользователем.
И ещё вопрос. Как лучше сделать: чтобы игрок сразу задавал число бросков за ход, и далее броски совершались бы автоматом, или чтобы он подтверждал каждый бросок?
Далее. Не вижу смысла знать об этом классе объектам-игрокам. Игрок-бот должен вычислять стратегию и в соответствии с ней управлять костью. Человеческому игроку нужно от внешнего мира только число бросков кубика за ход. На этом их функции заканчиваются. Не думаю, что надо их нагружать функциями общения с пользователем.
Не понял - что значит "не знать об этом классе объектам-игрокам"? Чтобы объекты игроки не имели в своем составе переменной класса Interface - чтобы она была в составе объекта "ведущий" (который у меня представлен классом Game), а объекты-игроки обменивались бы входящими/исходыщими сообщениями с объектом "ведущий", который бы перенаправлял эти сообщения в интерфейс?
Думаю правильнее сделать подтверждение после каждого броска. Например, такая ситуация. Ты играешь с ботом, четыре раза бросил кость. Если у тебя четыре раза выпала двойка, то ты, возможно, не захочешь останавливаться. А вот если четыре раза выпала шестерка, то остановиться вполне можешь захотеть, чтобы сохранить приличный результат. :) Думаю пример дает понять зачем может быть нужно подтверждение после каждого хода. Прекратить ли броски после того как за ход суммарно выпало некоторое количество очков - тоже может быть моментом стратегии игры.
Но для простоты можно внести ограничение в правила игры. Игрок перед совершением хода должен назвать число >=1 бросков, которые он будет пытаться совершить, и возможности остановиться после такого-то броска (по факту достаточности, по его мнению, выпавших за данный ход очков) у него быть не должно.
Согласен. Значит будем запрашивать подтверждение на каждый бросок.
Пояснение на рисунке.
Добавлено позже. Примерно так оно должно быть: игрок получает команду на ход, бросает кость, результат передает ведущему и запрашивает у него разрешение сделать ещё ход, если выпала не единица. При этом обмен идёт не строками, а числами и переменными логического типа.
Добавлено позже. Примерно так оно должно быть: игрок получает команду на ход, бросает кость, результат передает ведущему и запрашивает у него разрешение сделать ещё ход, если выпала не единица. При этом обмен идёт не строками, а числами и переменными логического типа.
Насчет выходной информации, которая для обоих типов игроков (человек и бот) выводится в интерфейс - да, можно сделать, чтобы она передавалась объекту "ведущий", и тот уже через интерфейс выводит информацию в консоль / GUI.
Но вот как быть насчет информации о том, собирается ли игрок продолжать броски или решил завершить свой ход? Для объектов игрок-человек и игрок-бот эта информация формируется по разному, в первом случае - поступает в объект извне, через интерфейс, от пользователя, во втором случае - формируется внутри объекта с помощью заложенного в боте алгоритма. И если сделать чтобы любой объект-игрок мог взаимодействовать с интерфейсом только через объект "ведущий игры", то тогда получится, что ведущий должен по разному обслуживать ход игрока-человека и игрока-бота. Т.е. ведущий должен знать, какого типа текущий игрок. И в таком случае уже не работает полиморфизм который я планировал использовать (чтобы для ведущего не было разницы между объектом игрок-человек и игрок-бот).
Поэтому думаю что интерфейс для ввода запроса к пользователю нужно оставить внутри класса игрок. Как вариант - можно не хранить объект интерфейс как переменную класса игрок, а передавать ссылку / указатель на интерфейс объектом "ведущий" в качестве аргумента функции принятия решений объекта-игрок, и в случае объекта игрок-человек - объект будет через этот интерфейс обращаться к пользователю. Вывод же результатов хода на экран - он однороден для обоих типов игроков, и его можно реализовать путем передачи информации через ведущего.
Еще вижу такое замечание по своему варианту. Кажется неправильным, что ведущий никак не контролирует, может ли игрок продолжать ход, контроль сейчас заложен внутри самого объекта-игрок. Думаю надо контроль вынести на уровень ведущего, а на стороне игрока оставить только хранение текущего баланса выпавших очков, и принятие решения (и сообщение о них ведущему) после каждого броска насчет того, продолжать ли ход или закончить, в случае когда очередной бросок возможен по правилам игры. Ведущий после каждого броска игрока должен проверять, выпала ли единица или нет, если нет - то сообщать игроку сколько очков выпало, и запрашивать от игрока хочет ли он бросать дальше. Если выпала единица - то сообщить игроку что у него все очки за ход сгорели и что ход передается противнику.
#include "Krupje.h"
int main()
{
// создаём ведущего
Krupje krup;
// игроки
Igrok igk1(1);
Igrok igk2(2);
Igrok igk3(3);
// подключаем игроков
krup.AddPlayer( &igk1);
krup.AddPlayer( &igk2);
krup.AddPlayer( &igk3);
// начинаем игру
krup.Start();
/* Другой вариант: создать отдельный поток
_beginthreadex ( .. Start() ...)
А в родительском потоке контролировать,
когда приостановить игру, когда завершить.
*/
krup.SaveLog(); // сохраняем результаты
cout << "Press enter ... " << endl;
cin.get();
return 0;
}
Функция Start() в классе Krupje.
{
while ( Continue() )
{
// игрок делает ход
currentIgrok->SdelatKhod();
// определяем следующего игрока
DetermineNextPlayer();
// отображаем процесс игры
ShowInformation();
}
return 0;
}
Наверное, так и хорошо. Можно вообще сделать так, чтобы игрок не знал, что из себя представляет интерфейс. Например, так:
{
public:
// Возвращает true, если требуется ещё ход
static bool GetConfirmation()
{
char answer;
cout << "Do you want to continue? y/n" << endl;
cin >> answer;
return answer == 'y';
}
};
struct HumanPlayer
{
int MakeTurn(int data, bool (*confirmFunc)())
{
while(confirmFunc());
return 0;
}
};
int main()
{
HumanPlayer hPlayer;
hPlayer.MakeTurn(0, Interface::GetConfirmation);
return 0;
}
Тут передаваемая функция является статической, но вроде бы у класса интерфейса все функции должны быть такими. Хотя, возможно, в случае с GUI будет желание сделать такую функцию нестатической...
Добавлено позже: возможно, указатель на такую функцию "человеческому" игроку лучше передать при его создании. Всё равно, методы создания разных игроков будут отличаться.
Замечания:
1. Думаю, потоки не нужны пока в этой игре.
2. Не показано то, что за одного игрока будет отвечать пользователь.
3. Не ясно, почему игроки не спрятаны в ведущем (или объекте Игра) - они же вне его нигде не используются.
4. Немного нейминг корявый - мешанина транслита с английским. Лучше всё на английский попытаться перевести.
Подход к функциям Ведущего (крупье) мне нравится.
Тут передаваемая функция является статической, но вроде бы у класса интерфейса все функции должны быть такими. Хотя, возможно, в случае с GUI будет желание сделать такую функцию нестатической...
Добавлено позже: возможно, указатель на такую функцию "человеческому" игроку лучше передать при его создании. Всё равно, методы создания разных игроков будут отличаться.
Вот, ИМХО отличный вариант. Как говорится, и волки сыты, и овцы целы: объект игрок теперь ничего не знает про интерфейс - имеет дело с объектом-ведущим и указателем на некую функцию подтверждения хода, о устройстве которой ничего не знает; и полиморфизм сохраняется. Я за.
И тогда для класса игрок-человек можно просто добавить переменную - указатель на эту функцию, который будет специфичной переменной для данного класса, и которой не будет в базовом абстрактном классе и классе игрок-бот. И в конструкторе класса "игрок-человек" будет входным аргументом указатель на эту функцию. Как-то так:
{
bool (*confirmFunc)();
public:
HumanPlayer(bool (*func)());
// ...
};
HumanPlayer::HumanPlayer(bool (*func)()) : Player("user player"), confirmFunc(func) {}
// ...
Game::Game() : turnsPassed(0)
{
players[0].first = new RobotPlayer;
players[0].second = 0;
players[1].first = new HumanPlayer(Interface::GetConfirmation);
players[1].second = 0;
}
Решил немного пересмотреть схему реализации кости.
Думаю, класс кость должен иметь переменную, которая будет хранить, сколько очков выпало после последнего броска (если бросков еще не было, то какое-то специальное значение). И должен быть, помимо функции - броска кости, также и метод получения предыдущего выпавшего значения.
{
static const unsigned int nullValue = 1000;
const unsigned int facets;
unsigned int lastPoints;
public:
Dice(unsigned int = 6);
unsigned int Roll();
unsigned int GetLastPoints() const;
};
Dice::Dice(unsigned int f) : facets(f), lastPoints(nullValue)
{
if(facets < 2 || facets > 20)
{
cerr << "Dice facets quantity out of range\n";
exit(1);
}
}
unsigned int Dice::Roll()
{
srand((unsigned int)time(0));
lastPoints = rand() % facets + 1;
return lastPoints;
}
unsigned int Dice::GetLastPoints() const
{
if(lastPoints == nullValue)
{
cerr << "Attempt to get value from dice without rolling\n";
exit(1);
}
return lastPoints;
}
Также решил пересмотреть реализацию игроков. Метод бросания кости (makeTurn) решил разделить на функции начала хода, продолжения хода, и завершения хода. Пример реализации:
{
const string name;
protected:
unsigned int totalPoints;
unsigned int currentPoints;
unsigned int turnsPassed;
public:
Player(const string&);
virtual ~Player();
virtual void beginTurn(Dice*, unsigned int);
virtual bool continueTurn(Dice*) = 0;
void endTurn(bool);
string announce();
};
Player::Player(const string& str) :
name(str), totalPoints(0), currentPoints(0), otherPartyPoints(0), turnsPassed(0) {}
Player::~Player() {}
void Player::beginTurn(Dice* pDice, unsigned int)
{
currentPoints = pDice->Roll();
}
void Player::endTurn(bool isSuccess)
{
if(isSuccess)
totalPoints += currentPoints;
++turnsPassed;
}
class HumanPlayer : public Player
{
bool (*confirmFunc)();
public:
HumanPlayer(bool (*func)());
virtual bool continueTurn(Dice*);
}
HumanPlayer::HumanPlayer(bool (*func)()) : Player("user player"), confirmFunc(func) {}
bool HumanPlayer::continueTurn(Dice* pDice)
{
bool result = confirmFunc();
if(result)
currentPoints += pDice->Roll();
return result;
}
class RobotPlayer : public Player
{
unsigned int otherPartyPoints;
unsigned int rollsCount;
unsigned int upRollsCount;
unsigned int upPoints;
public:
RobotPlayer();
virtual void beginTurn(Dice*, unsigned int);
virtual bool continueTurn(Dice*);
};
RobotPlayer::RobotPlayer() : Player("computer player") {}
void RobotPlayer::beginTurn(Dice* pDice, unsigned int oPPoints)
{
otherPartyPoints = oPPoints;
currentPoints = pDice->Roll();
rollsCount = 1;
int diffPoints = (int)otherPartyPoints - (int)totalPoints;
upPoints = diffPoints > 0 ? (unsigned int)diffPoints : 0;
if(diffPoints > 80)
upRollsCount = 12;
else if(diffPoints > 60)
upRollsCount = 10;
else if(diffPoints > 40)
upRollsCount = 8;
else if(diffPoints > 20)
upRollsCount = 6;
else if(diffPoints > 0)
upRollsCount = 4;
else
upRollsCount = 2;
}
bool RobotPlayer::continueTurn(Dice* pDice)
{
bool result = !(rollsCount >= upRollsCount || currentPoints >= upPoints);
if(result)
currentPoints += pDice->Roll();
return result;
}
Итого предлагаю следующую схема реализации объектов-игроков и их взаимодействия с объектом-ведущий.
Абстрактный класс Player, содержит:
- переменные:
количество очков, накопленное до начала хода;
суммарное количество очков, накопленных за текущий ход;
- функции (помимо конструктора и деструктора):
функция начала хода, принимает аргумент - указатель на объект-кость, выполняет один бросок;
функция принятия решения о том, следует ли продолжать броски дальше - чисто виртуальная функция (будет определена своя для каждой реализации игрока);
функция окончания хода, принимает булевскую переменную (от ведущего) - сгорели ли очки или нет, если не сгорели, то суммирует набранные за ход очки к накопленным.
Класс игрок-человек наследуется от Player, имеет в своем составе переменную - указатель на функцию для подтверждения извне о том что нужно совершить ход, значение указателя передается объектом-ведущим при инициализации объекта игрок-человек.
Класс игрок-бот также наследуется от Player, имеет переменные: количество очков противника, и специальные переменные, которые используются в функции - принятие решения, стоит ли сделать, по предложению ведущего, еще один бросок, или стоит остановиться. В приведенном мной выше примере - это переменные upRollsCount и upPoints.
Взаимодействие ведущего с игроками:
Перед игрой ведущий создает (с помощью new например) объект-кость.
Порядок совершения хода:
Ведущий выбирает объект - игрок который совершает ход.
Выводит в интерфейс информацию о том, что ходит такой-то игрок
Вызывает для этого объекта функцию начала хода. В функции происходит бросок кости.
Далее в цикле:
Ведущий проверяет, сколько выпало очков:
- Если выпала единица, то сообщает игроку что у него все очки сгорели и что ход передается другому игроку, выход из цикла.
- Если выпало > 1, то ведущий предлагает игроку еще раз бросить кость:
Если игрок бросает еще раз, то возврат на начало цикла;
Если игрок решил остановиться, то ведущий суммирует к очкам данного игрока, которые он (ведущий) хранит у себя, выпавшие за ход очки.
Выход из цикла.
После совершения хода проверяется, набрал ли игрок 100 очков. Если набрал, то игрок объявляется победителем. Если не набрал, то ход передается другому игроку.
Вопрос того, как должен быть реализован ведущий (какая должна быть структура, какие функции у него должны быть), пока не затрагиваю (за исключением описанного алгоритма взаимодействия ведущего с игроками). Также пока не касаюсь других поставленных в ТЗ задач (например, вывода в лог).
Спасибо, что восстановил тему. Я сам её думал восстановить после 10-го когда каникулы закончатся. Но раз интерес появился раньше, то и я приму участие.
Посмотрев на предложения, понял, что немного сбил с толку людей неясностью требований к коду. А они должны быть примерно такие:
1. Ясность, читаемость.
2. Отсутствие дублирования. Или хотя бы стремление к нему.
3. Обоснование решений с помощью шаблонов GRASP.
4. Дополнительное, но не обязательное условие - использование правил рефакторинга.
Так вот, я критиковал код за использование числа 6 - так как это было использованием магического числа. То есть, нарушало читаемость.
Но зачем же поименовав константу мы пытаемся сразу навесить на неё кучу функций (присвоение числа граней в конструкторе)? А нужны ли они пользователю? Навесить функции и сможем и позже. Сейчас надо собрать просто что-то рабочее.
Далее. Ведущий у нас получается очень жадным. Он вмешивается в игру, он знает о существовании кости и даже создаёт её. Более того, из-за его жадности приходится в кость добавить неочевидную функцию, которая отдаляет её от предметной области.
Если бы он не "жадничал", то его роль была бы примерно такая:
1. Заставляем игрока бросить кость, заодно считывая его желание бросить ещё раз.
2. Считываем данные игрока и выводим на экран.
3. Если было желание сделать ещё ход, то переходим к пункту 1, иначе выдаем соответствующее сообщение на экран и переходим к другому игроку.
Игроки же являются программными классами - можно сделать их такими, чтобы они не обманывали ведущего. Возможно, игрок может даже сообщить, что выиграл. Хотя тут я не уверен.
1. Ясность, читаемость.
2. Отсутствие дублирования. Или хотя бы стремление к нему.
3. Обоснование решений с помощью шаблонов GRASP.
4. Дополнительное, но не обязательное условие - использование правил рефакторинга.
Против требований не возражаю, но думаю что рассматривать их надо будет на этапе кодинга, до которого дело еще не дошло. Хочу заметить, что тот код что я приводил в предыдущих сообщениях - это лишь примеры, поясняющие предлагаемую мной схему взаимодействия объектов. Сейчас пока у нас пока этап проектирования, я так понимаю...
Но зачем же поименовав константу мы пытаемся сразу навесить на неё кучу функций (присвоение числа граней в конструкторе)? А нужны ли они пользователю? Навесить функции и сможем и позже. Сейчас надо собрать просто что-то рабочее.
На мой взгляд, это уже на этапе самого кодирования надо будет обсуждать. Сейчас пока достаточно описать в общих чертах объект "кость" (наряду с другими объетами), а будут ли в нем магические числа или нет - оставить на потом.
Если бы он не "жадничал", то его роль была бы примерно такая:
1. Заставляем игрока бросить кость, заодно считывая его желание бросить ещё раз.
2. Считываем данные игрока и выводим на экран.
3. Если было желание сделать ещё ход, то переходим к пункту 1, иначе выдаем соответствующее сообщение на экран и переходим к другому игроку.
Игроки же являются программными классами - можно сделать их такими, чтобы они не обманывали ведущего. Возможно, игрок может даже сообщить, что выиграл. Хотя тут я не уверен.
Итого, вырисовываются два разных варианта - в плане того, в каких объектах будет контролироваться выполнение игроками правил игры:
1. Вариант Kogrom'а: контроль выполнения правил игры возлагается на объекты, представляющие игроков.
2. Мой вариант: контроль выполнения правил игры возлагается на ведущего. Этот вариант описан в моем предыдущем сообщении, и вызвал замечания Kogrom'а по отдельным моментам предложенной схемы, которые являются следствием того, что контроль осуществляет ведущий - отсюда его "жадность" и т.д..
Честно говоря, не нашел серъезных аргументов в пользу своего варианта. Поэтому согласен на вариант Kogrom'а, соответственно то что я писал выше (предложенная мной схема объектов) - отменяется (в силу того что было завязано на контроль правил в ведущем), надо прорабатывать другую схему.
Очень рад. Этот проект затевался как групповая работа, как проект для общения, в котором главное процесс разработки. Поэтому я его не стал в одиночку продолжать.
Конечно возможно.
Ну, Grasp - это не GoF, в них всё примитивно и понятно каждому программисту, знакомому с ООП, но немного систематизировано.
Например, если у нас есть объекты: игральная доска, клетка (элемент игральной доски) и собака, то использование собаки для создания клеток - не лучшая идея, так как порождает ненужные связи. Лучше, чтобы клетки создавала сама доска, так как она будет их использовать. Вот приблизительный смысл шаблона Creator (Производитель).
Примерно такие же сложные и все остальные шаблоны. Однако, проще понять друг друга, если использовать какую-то известную терминологию.
Ну, сейчас принято не делить на такие этапы. Например, в RUP используется такая схема:
http://upload.wikimedia.org/wikipedia/ru/d/da/RUP_process.png
то есть, проектирование и кодинг идут параллельно.
Тем более ясность, перестройка и отсутствие дублирования и в проектировании используется.
К делу. Если берёмся делать что-то, то надо внести хоть какой-то ритм. Например, очерёдность сообщений от участников, лимит времени между сообщениями и т.д. То есть участники должны как минимум подавать признаки того, что они в проекте, делиться мыслями, черновиками, намерениями.
Ок. Ваш ход. Ожидаю какие-то предложения по очередности ходов, по лимиту времени и т.д.
Почитал про GRASP в Википедии. На первый взгляд - да, то что написано - вроде просто и понятно... Но непонятно (за исключением совсем уже простых примеров, аналогично приведенному тобой ниже), например, как эти шаблоны правильно использовать в реальных проектах. Нужно изучение способов применения этих шаблонов, а также разбор одного или нескольких примеров их использования, а это уже есть составляющие той самой теории проектирования, на изучение которой требуется время.
Конечно можно пытаться использовать эти шаблоны при обосновании своих решений и без полного понимания как их правильно использовать, но такие обоснования рискуют оказаться всего лишь какой-то софистикой...
Пример надуманный, в реальном проекте все не так просто. Как я понял, лежащий в основе шаблона Creator тезис таков: "Создателем каждого конкретного объекта должен выступать тот объект, который его будет потом использовать". Тогда возникают вопросы, например, такие. А что, если создаваемый объект будут использовать несколько объектов - какой из них тогда назначать создателем? И что понимать под "использованием объекта A другим объектом B" - только ли обращение внутри функций объекта B к объекту A напрямую, или также и другие виды взаимодействия объектов (например объект B хранит переменную - ссылку на какую-то переменную объекта A; опосредованное использование - объект B использует объект C, который использует объект A)? Это лишь навскидку, при желании можно и много других вопросов задать, для нахождения ответов на которые нужно изучать теорию.
Насчет кажущейся простоты я сказал выше. Насчет использования известной терминологии: то что при ее использовании собеседникам проще понять друг друга справедливо, ИМХО, только лишь в случае, когда все собеседники эту самую терминологию в полной мере понимают. А я в данном случае понимаю далеко не все, часть того что не понимаю - описал выше.
http://upload.wikimedia.org/wikipedia/ru/d/da/RUP_process.png
то есть, проектирование и кодинг идут параллельно.
Как я понял, не совсем так. Этапы могут пересекаться по времени (и обычно пересекаются), но основная часть разных этапов проходит в разное время и не пересекается. В случае этапов проектирования и кодинга. Сначала проходит основной этап проектирования, на котором вырабатывается основа архитектуры программы, которая должна быть реализована проектом, структура объектов и их взаимоотношений. В итоге получается некоторая принципиальная схема, в соответствии с которой должна вестись разработка проекта, и которая (схема) уже не будет как-то значитально меняться, меняться могут только детали, но в основе своей она должна оставаться неизменной (за исключением случая, когда при дальнейшей разработке выясняется, что реализация невозможна без значительной переделки основной схемы, что говорит о том, что проектирование проведено неправильно). Далее начинается этап кодинга, на котором проектирование также продолжается, но теперь оно заключается только в незначительных доработках и внесении уточнений в спроектированную до этого схему.
У нас же сейчас получается, что проектирование и кодинг идут одновременно, что я считаю неправильно. Считаю что нужно сначала выработать основную схему, потом уже переходить к кодингу, попутно с которым уточнять и детализировать, и в незначительной степени менять схему.
Но в сказанном выше я лишь изложил свое понимание того, что там написано, может быть, на самом деле что-то и не так, конечно...
На этот счет у меня такие мысли...
Думаю, на каждый момент работы над проектом должен быть некоторый план работ на ближайшее время - список элементарных задач, каждая из которых может быть выполнена, без затрат значительного времени и усилий, одним участником. В каждой из задач, перед тем как она может быть кому-то поручена, должна быть четко сформулированная постановка, и требования к результату выполнения задачи.
После формулировки задачи один или несколько участников проекта заявляют о своем желании выполнить эту задачу, называя сроки, сроков предлагаю называть два: один - ориентировочный средний срок, т.е. за какое количество дней участник, как он сам думает, скорее всего сделает задачу, второй - максимальный срок (на случай когда все нет времени, или время есть да что-то не тянет, и т.д.). Если желающих выполнить одну и ту же задачу - несколько, то руководитель проекта (в данном случае - Kogrom) выбирает одного из них, впрочем наверно можно дать возможность сделать одну и ту же задачу нескольким исполнителям, после чего сравнить результаты и в результате обсуждения, выбрать вариант который представляется лучшим.
После того как исполнитель выполняет задачу, он предоставляет полученные результаты на обсуждение (сделать он это должен в заявленные им сроки), по результатам обсуждения задача либо считается как выполненная, либо, при наличии замечаний, отправляется на доработку (которая может быть поручена другому исполнителю).
Примеры таких элементарных задач:
- Кодирование классов, представляющих "игроков". Постановка: <описание что должен делать класс>, результат: готовый код классов.
- Кодирование класса "Ведущий", постановка и результат - аналогично.
Еще насчет ритма. Как я предложил выше, каждый участник сам должен выбирать срок за который он сделает задачу, за которую берется, но при этом все-таки думаю надо назначить максимальный допустимый срок. В качестве этого срока предлагаю взять неделю. Если при какой-то элементарной задаче выяснится, что ни один из участников не готов ее сделать за указанное максимальное время, то это может означать или то, что это время нужно увеличить, или то что элементарная задача должна быть разделена на более элементарные задачи.
Так же думаю, что было бы полезно всем реальным и потенциальным (тот кто готов сказать "я бы может и присоединился но пока точно не знаю") участникам проекта сообщить, в какой роли / ролях они желают (хотели бы) / имеют возможность участвовать в проекте. Это помогло бы руководителю проекта и другим участникам понять, какого участия можно ожидать от каждого участника, и ориентироваться на это при составлении планов, выделении элементарных задач и т.д. Со своей стороны скажу, что ожидаю свое участие в качестве: проектировщика (в ограниченном объеме, т.к. далеко не все знаю), кодера, ну и тестировщика.
Итак, свои мысли высказал. Если мое предложение насчет планирования и выделения элементарных задач принимается, то собственно нужно выделить на ближайшее время элементарную задачу (задачи), и выбрать исполнителей. Напрашивается задача "Создание базовой схемы классов программы и их взаимодействия" (или как-то так), но возможно что задача не совсем элементарная и надо ее декомпозировать...
P.S. Но это еще не все, хотелось бы еще кое-что добавить, но это уже не раньше чем завтра. Пока предлагаю к обсуждению написанное в данном посте, может потом что-то еще дополню.
Ок. Вводим срок итерации - неделя. Хотя, подозреваю, что верный срок - две недели. В конце каждой итерации я буду подводить итоги (если будет доступ в Интернет, конечно). Если две итерации подряд в теме будут только мои сообщения, то проект будет заморожен.
Ещё можно попробовать использовать такие правила:
http://forum.codenet.ru/showpost.php?p=298331&postcount=50
Ок. Делаем черновик базовой схемы классов программы. Желательно, чтобы сопровождалось хоть какой-то графической схемой.
Вполне согласен, нормальное требование чтобы избежать нахождения проекта в подвисшем состоянии.
http://forum.codenet.ru/showpost.php?p=298331&postcount=50
Хм... мне кажется что не получится их здесь использовать в том виде, в каком они там описаны... В тех правилах в каждой итерации присутствует проектирование, у нас же проектирование будет только вначале... Впрочем, для некоторых классов тоже можно выделить отдельный этап проектирования...
Итак, первая задача: черновик базовой схемы классов программы. Предлагаю немного детализировать описание задачи, описав требуемый результат. В итоге должно получиться:
- Перечень классов, которые должны присутствовать в программе.
- Описание взаимодействия классов между собою в процессе выполняемой при работе программы имитации игры в кости (кто выступает создателем объектов каждого класса, какой информацией обмениваются объекты в ходе работы программы и т.д.).
- Внутреннее устройство классов пока разрабатывать не требуется. Например, для класса "кость" (если такой будет) пока не требуется определять, как именно внутри объекта кость будет получаться выдаваемое костью значение. Также как и для класса "игрок-бот" - пока не требуется разрабатывать алгоритм стратегии игры бота, достаточно указать что такой алгоритм должен быть.
Попытаюсь выполнить данную задачу, примерный срок называю - 4 дня (начиная с сегодняшнего), максимальный - 6 дней. Т.е. ориентировочно - в четверг вечером, в крайнем случае - до наступления воскресенья, от меня можно ожидать результата. При выполнении задачи попытаюсь использовать шаблоны GRASP, но не знаю насколько хорошо это у меня получится...
Если найду время - нарисую и схему.
Есть кстати предложение Kogrom'у самому также попытаться выполнить эту задачу, чтобы продемонстрировать, как можно испоьзовать шаблоны GRASP для данного проекта. Но предоставлять свои результаты лучше только после того, как я предоставлю свои. Потом сравнить, у кого что получилось.
Попытаюсь. Возможно, для себя проверю не только полезность GRASP, но и других эээ... технологий.
Небольшая просьба: Ghox, постарайся писать сообщения чуть более лаконично.