Игра в кости
http://forum.codenet.ru/showthread.php?t=57128
Вроде бы не особо сложная, но и не очень мелкая. Можно было бы поиграть в создание "настоящей программы". Составить нормальное ТЗ (просто выкинуть из того что есть двузначности, добавить пояснения), придумать требования к взаимодействию с пользователем, выбрать тип лога, обосновать этот выбор, создать программу в хорошем ООП-шном стиле и т.д.
Предполагается, что кто-то начнёт, кто-то продолжит, а я буду критиковать и подсказывать (не потому что шибко умный, а потому что люблю критиковать). За интересные ходы возможно буду раздавать зелёные квадраты (вроде это правилам не противоречит).
Язык программирования конечно же C++, как наиболее популярный :)
Как идея? Найдутся желающие?
Ну, наверное надо какой-то документик оформить, в котором она была бы отражена. Пока не придумал, как это оформить, где хранить.
Но основная линия заключается в обмене опытом с помощью задачи, для решения которой, грубо говоря, требуется более 3-х строчек кода.
Может и пугает. Но бардак пугает больше. Предложи какую-то организацию, если есть идеи.
Ну, например, можно использовать такую модель: Ghox пишет свою игру, Нездешний пишет свою игру. Kogrom работает как критик, как помощник, если потребуется. Код может переходить из одного проекта в другой. Каждый может критиковать код другого, что-то советовать.
Если он не отличается, то почему в твоём новом варианте они не являются наследниками одного предка? Ну и потом, "концептуально" - слово которое можно интерпретировать как угодно. Например, в логе желательно сохранять больше данных, чем выводим в консольный вид.
И наконец, немного спорное утверждение. Мы делаем игру, а не систему управления или сервер. Поэтому тут лог должен сохраняться по желанию пользователя, если игра была интересная. То есть он сохраняется в конце игры, а не по ходу её.
Далее по коду. Осталось дублирование инициализации генератора случайных чисел. Не совсем ясно, зачем нужны контейнеры с видами и логами. Почему жертвуем читаемостью взамен гибкости, которую скорее всего не будем использовать?
То же на счет делегатов. Зачем оно тут, в этой игрушке? Нужен конкретный пример, как оно поможет. А лучше несколько примеров (так как читаемость важнее навороченной гибкости, так как, думаю, большую гибкость даёт именно читаемость).
[QUOTE=Kogrom]Не совсем ясно, зачем нужны контейнеры с видами и логами[/QUOTE]согласен, контейнеры можно убрать
[QUOTE=Kogrom]на счет делегатов. Зачем оно тут[/QUOTE]Указатель на метод абстрактного класса (IView::GetUserDecision) как-то c ходу не удалось объявить ;)
Значит надо составить какой-то обновляемый документик. По науке нужно что-то типа wiki. Не по науке можно делать какую-нибудь pdf-ку и выкладывать её по определенному адресу (например, можно для этого использовать проект на гугле, который остался от первого Учебного проекта).
А почему нельзя хранить данные лога в виде какого-нибудь объекта в ОЗУ?
Зачем на метод? Можно сделать указатель на объект. Если упрощенно, то как-то так:
{
virtual bool Get() = 0;
};
class GetResult: public IGetResult
{
public:
bool Get() {return StaticGet();}
private:
static bool StaticGet()
{
char res = 'n';
cout << "y/n? ";
cin >> res;
return (res == 'y');
}
};
int main()
{
IGetResult *getter = new GetResult();
cout << getter->Get();
return 0;
}
Ну, можно для красоты оператор () перегрузить.
Извините за корявый нейминг...
[QUOTE=Kogrom]Зачем на метод? Можно сделать указатель на объект.[/QUOTE]А зачем здесь функтор? Помнится, кто-то критиковал класс ради одной функции (Dice) несколькими постами ранее ;) Да и тем самым разобьем вид. Если уж избавляться от делегатов, то просто передать классу THumanStrategy указатель на весь интерфейс вида IView
Вот мы и выделили сущность - таблицу. То есть объект, который хранит данные о всех бросках и ходах игры. Из него легко списывается лог в любой момент времени.
Эта сущность нужна для того, чтобы игроки (особенно, компьютерные) могли на её основе вырабатывать стратегию.
У тебя она есть в неявном виде, разорванная на куски.
Это и есть передача указателя на интерфейс, а не функтор. Идея тут в том, что мы отрываемся от статической функции, а не в использовании алгоритмов STL.
Кстати, у тебя при передаче функции в стратегию используется конкретный вид... мда. Можно было просто указатель на функцию использовать.
Хм, будем считать это намёком. Если будет время, попробую прочитать всё написанное, может сбацаю документ.
1-й документ (StudyProject) - об организации учебных проектов.
Вроде бы придумал структуру, при которой каждый участник получает относительную независимость от других.
2-й документ (DiceTZ) - ТЗ.
3-й документ (DiceNow) - текущее состояние.
Не очень удобно цитировать из картинки, а программу для чтения карты мне ставить лень. Хотя карта типа xml, но надо ещё текст расшифровывать...
Ладно. Без цитат.
1. На счет выделения главной строки в отдельную переменную. Пока не понял аргументацию. Что мешает объекту, который записывает лог, читать и упорядочивать данные из таблицы как он пожелает?
2. Не вместо страниц ссылки на игроков, а вместо идентификаторов ссылки. Но тоже не совсем тут однозначно. У обоих подходов есть свои плюсы и минусы.
3. Может и правильно, что Ведущий (Игра) контролирует выигрыш. Но, мне думается, что лучше выделить отдельный контроллер правил. У меня он пока что наследуется от таблицы, но не обязательно делать так.
На счёт того, жив ли проект. Он странный какой-то. То вспыхнет, то погаснет. Но я стараюсь сделать так, чтобы моё сообщение было последним.
Это FreeMind, portable-верию можно взять отсюда (рабочая, проверял).
Идея в том, чтобы Таблица была удобной формой для хранения полной информации об игре, причём это не значит, что её внутреннее представление должно быть именно "таблицей" (массовом).
Строки мне представляются двумерным массивом типа:
player: TPlayer;
total: word; // сумма очков за весь ход, хотя по идее её всегда можно посчитать, когда нужно.
points: array of byte; // конкретные очки, которые он выбросил.
end;
Плюс к Таблице добавляется поле с количеством очков, набранным каждым Игроком: array of { player: TPlayer; total: dword; }.
Главная строка, как я её понял, получается разнесённой между этими двумя полями.
А, да. Просто где-то в доках проскакивало то, что каждый Игрок имеет свою Таблицу. Моя идея в том, чтоТаблица - одна на один объект Игры, и имеет такие поля:
Total: array of ...; // см. сумму очков по игроку.
CurrentTurn: record // либо текущее состояние игры, если игра ещё идёт, либо последний ход (= выигрыш кого-то), если игра закончилась.
TotalTurns: word; // количество ходов за всю игру от всех игроков.
Player: TPlayer; // тот, кто сейчас ходит или тот, кто выиграл.
Index: word; // индекс строки в массиве Turns: [Player] [Index]. можно сделать указателем, дабы удобнее было обращаться сразу к записи.
end;
Хм, вообще, раз и turns и total имеют указатель на объект игрока, можно их объединить в один массив, но мне эта идея не нравится - слишком скученно будет.
У меня это тоже отдельный объект ("для этого можно иметь наследуемый private-объект"), правда, он ни от чего не наследуется - его задача в простейшем случае выдать True/False, например:
function TRules.WhoHasWon(Table: TTable): TPlayer;
begin
LastTurn := Table.Turns[ Table.CurrentTurn.Player ][ Table.CurrentTurn.Index ];
if LastTurn.Total >= 100 then
Result := LastTurn.Player
else
Result := NIL;
end;
Здесь я возвращаю победителя, а не просто T/F, дабы правила могли быть и обратными, например: побеждает тот, кто продержится с наименьшим количеством очков всю игру.
Таким образом, правила можно полностью вынести из объекта Игры.
По поводу самого объекта Игра. Мне он представляется так (упрощённо, конечно):
Rules: TRules;
Table: TTable;
Players: array of TPlayer;
end;
...
// пока будем считать, что к вызову Start() массив Игроков синхронизирован с массивом Turns и до. в Таблице.
procedure TGame.Start;
begin
while True do
begin
Winner := Rules.WhoHasWon(Table);
if Winner = NIL then
begin
Table.NewRound;
// по Правилам, конец хода тогда, когда Игрок выкинул 1
while True do
begin
TurnState := Rules.TurnState(Table.CurrentTurn);
case TurnState of
tsPlayerEnd: Table.EndLastTurn;
tsLostTurn: Table.LastTurnLost; // Игрок выкинул 1.
tsContinue: Table.RecordTurn( Players[Table.CUrrentTurn.Player].Turn );
end;
end;
end
else
// выигрыш
...
end;
end;
Ну да, я заметил :) Будем оживлять.
http://ru.wikipedia.org/wiki/GRASP
Особенно меня интересуют шаблоны Low Coupling (Слабая связанность) и High Cohesion (Сильное зацепление).
Если говорить упрощенно, то использование этих шаблонов должно привести к тому, что созданные классы будут настолько слабо привязаны к конкретному проекту, что их легко можно будет использовать в другом. Для связки их в единый проект в худшем случае придется использовать объекты-адаптеры, или какие-то наследники. А в лучшем - они и так должны зацепиться.
Например, мы вытаскиваем таблицу из этой конкретной игры и, не меняя, используем в программе типа упрощенного Excel. Дорого ли нам обойдется такая процедура? А сложно ли использовать класс игрока в другом проекте? А Ведущего (Игру)?
Думаю, в предложенном Proger_XP варианте Таблица слишком много знает об игре. На это указывают имена функций.
Кстати, почему Delphi? Язык, конечно, хороший, я его понимаю (кроме тонкостей), но хочется узнать почему он был выбран для примеров.
Особенно меня интересуют шаблоны Low Coupling (Слабая связанность) и High Cohesion (Сильное зацепление).
Да, я именно про связность и думал в процессе.
Так и я думал. Все объекты (Таблица, Игрок, Правила) должны работать внутри Игры, то есть Игра будет вызывать их методы, вроде адаптера, хотя она скорее ничего не адаптирует, а просто вызывает методы в нужной последовательности.
Я постарался это отразить в TGame.Start, опишу подробнее ниже.
Таблицу можно вытащить и использовать, она ничего не знает о своём окружении, кроме Игроков - да и то потому, что её цель хранить данные о ходах этих самых игроков.
На крайний случай можно из таблицы сделать шаблон, как в Си (правда это будет перебор, имхо).
Ведущий по определению не может быть никуда вынесен, по крайней мере в моём понимании, так как он имеет смысл только в этой конкретной игре, поскольку он её и двигает, жонглируя прочими объектами.
Хм, по-моему имена как раз указывают на обратное. Вообще, Таблица с таким же успехом может быть не классом, а записью, но методы призваны убрать дублирование кода.
- NewRound - просто добавить новый элемент в Turns и Total.
- EndLastTurn - пока просто увеличивает счётчик ходов на 1.
- LastTurnLost - просто вычитает ходы и очки в Turns и Total, использую инфу из CurrentTurn.
- RecordTurn - пока не чётко представляю, что должен возвращать Player.Turn и, соответственно, принимать этот метод, но это может быть просто число выпавших очков. Так что всё, что делает метод - увеличивает счётчик очков и ходов.
Единственное, что меня смущает - то, что Правила в WhoHasWon принимают объект Таблицы. С другой стороны, это позволит ввести более гибкие правила и вынести их логику из Ведущего, ибо ему не надо будет выбирать, что конкретно из Таблицы передавать в Правила.
Чисто из субъективных соображений. Я мог бы написать на PHP или Ruby, но по-моему в Delphi ООП видно лучше всего. А Си, даже шарп, мне меньше нравится.
В принципе, можно и на страницы не делить - все строки размещать на одной. Так, возможно, будет проще для лога, но труднее для анализа Игроком-Компьютером.
С другой стороны, я не совсем понимаю, зачем нужен счетчик ходов. Это же просто число строк, то есть размер контейнера, хранящего строки. Он и так должен быть.