Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Игра в кости

87
21 декабря 2009 года
Kogrom
2.7K / / 02.02.2008
У меня появилась следующая идея. В данном разделе есть несколько человек, которые постоянно решают мелкие задачки для студентов: Ghox, koodeer и т.д. Но, возможно им захочется сделать что-то большее. Вот я выкопал такую задачку:

http://forum.codenet.ru/showthread.php?t=57128

Вроде бы не особо сложная, но и не очень мелкая. Можно было бы поиграть в создание "настоящей программы". Составить нормальное ТЗ (просто выкинуть из того что есть двузначности, добавить пояснения), придумать требования к взаимодействию с пользователем, выбрать тип лога, обосновать этот выбор, создать программу в хорошем ООП-шном стиле и т.д.

Предполагается, что кто-то начнёт, кто-то продолжит, а я буду критиковать и подсказывать (не потому что шибко умный, а потому что люблю критиковать). За интересные ходы возможно буду раздавать зелёные квадраты (вроде это правилам не противоречит).

Язык программирования конечно же C++, как наиболее популярный :)

Как идея? Найдутся желающие?
Страницы:
12K
15 февраля 2010 года
Ghox
297 / / 26.07.2009
Цитата: Kogrom
Попытаюсь. Возможно, для себя проверю не только полезность GRASP, но и других эээ... технологий.


Будет неплохо.

Цитата: Kogrom
Небольшая просьба: Ghox, постарайся писать сообщения чуть более лаконично.


Да, есть такая проблема у меня - слишком уж длинно пишу, когда можно написать и короче. Постараюсь исправиться. :)

12K
20 февраля 2010 года
Ghox
297 / / 26.07.2009
Пробую представить свой вариант, за 4 дня как-то не получилось у меня...

Классы:
  • Ведущий
  • Игральная кость
  • Игрок, представленный в двух модификациях: игрок-человек и игрок-компьютер.
  • Логгер
  • Интерфейс, в двух модификациях: без логгера и с логгером.

Ведущий имеет в своем составе:
как переменные - следующие объекты (соответственно Ведущий выступает создателем этих объектов):
контейнер игроков (массив, вектор или что-то другое)
объект "кость"
объект "интерфейс"
функции:
функция, реализующая процесс игры
функция совершения броска кости

Игрок (пока не детализирую случаи игрока-человека и игрока-компьютеры):
функция:
совершение входа - серия бросков кости, совершаемая посредством ведущего

Игральная кость:
функция:
возвращает некоторое случайное число, являющееся аналогом результата броска реальной физической кости

Логгер:
функция:
вывод в лог сообщения, передаваемого в виде строки в аргумент вызова функции

Интерфейс, в двух вариантах:
Интерфейс без логгера;
Интерфейс с логгером - имеет в своем составе объект "логгер"
функции:
вывод сообщения в интерфейс, в случае варианта когда используется интерфейс с логгером - сообщение выводится также и в логгер
ввод информации из интерфейса

Процесс создания объектов:
Создается объект "ведущий", при создании ведущего (в конструкторе) производятся действия:
1. Создается объект интерфейс, сначала - в варианте без логгера. Через интерфейс запрашивается - будет ли использоваться логгер. Если пользователь возвращает ответ "да, будет", то объект интерфейс заменяется на объект "интерфейс с логгером".
2. Создается объект "кость".
3. Создается контейнер объектов-игроков и заполняется объектами-игроками (если в качестве контейнера используется обычный статический массив игроков, объявленный как переменная класса - то просто наполняется массив игроками).

Процесс игры:
1. Ведущий поочередно передает ход от игрока к игроку, вызывая соответствующую функцию игрока, передавая при это количество очков имеющихся у противника (которое ведущий запрашивает у игрока-противника). Каждая передача хода сопровождается выводом соотв. сообщений в интерфейс.
2. Игрок при совершении хода:
Один или несколько раз совершает бросок кости, посредством соотв. функции ведущего.
2.1. Ведущий при вызове этой функции:
бросает кость;
результат броска отправляет как сообщение в интерфейс;
определяет, может ли игрок продолжать ход, в соответствии с правилами, результат этой проверки возвращается игроку. В случае, когда, по текущим правилам, выпала единица и игрок продолжать ход не может, также выводится соотв. сообщение в интерфейс.
2.2. Игрок перед каждым броском, после первого броска:
анализирует возвращенный ведущим результат проверки - сгорели ли у него очки, или он может продолжать ход:
2.2.1. в первом случае - завершает ход (выход из функции хода игрока, к накопленным очкам ничего не суммируется);
2.2.2. во втором случае - сначала проводит проверку, не стал ли он, с учетом накопленных ранее и выпавших за текущий ход очков, победителем:
если стал победителем - то обращается к ведущему, чтобы тот обозначил его как победителя, и завершает ход;
если пока победителем не стал - то принимает решение - стоит ли дальше продолжать броски. Если решает завершить ход, то выпавшие за ход очки суммируются к накопленным за предыдущие ходы, и происходит выход из функции.
3. Также ведущий перед очередной передачей хода игроку, определяет, не стал ли кто-то из игроков победителем (возможно, с использованием какой-то переменной - стал ли кто-то из игроков победителем, и кто именно победитель, эта переменная устанавливается в нужное значение в описанном выше случае, когда игрок определил что он выиграл). Если кто-то стал победителем - игра прекращается.

P.S. Графическую схему пока не нарисовал. Знаю, что будут замечания, и что надо бы еще дорабатывать / переделывать описание, но чтобы не затягивать - пока предоставляю то что есть, руководствуясь обозначенным самим Kogrom'ом в одном из его более ранних учебных проектов принципом:
Цитата: Kogrom
Минусы. Я так и не смог убедить других участников, что в таких проектах лучше сделать хоть кое-как, чем никак. Хотят удивить шедевром, наверное.

87
20 февраля 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ghox
Знаю, что будут замечания, и что надо бы еще дорабатывать / переделывать описание, но чтобы не затягивать - пока предоставляю то что есть, руководствуясь обозначенным самим Kogrom'ом в одном из его более ранних учебных проектов принципом



Это правильно.
Теперь мои идеи.

1. Объект Ведущий владеет Интерфейсом (или в терминологии MVC - Видом). Вроде бы это логично и для консоли вполне исполнимо, но если переходишь к GUI, то для сохранения этой иерархии приходится использовать финты. Потому для себя я решил, что Вид должен содержать в себе всё остальное (это не значит, что он всем управляет).

2. Интерфейс напрямую владеет Логгером. Думаю, это не верно. Интерфейс должен выполнять лишь обмен данными с пользователем. Другой работы он не выполняет. Тогда Вид легче поменять на GUI-шный.

3. С одной стороны, Логгер, как объект, пишущий в файл пока не нужен. С другой стороны, хорошо бы иметь подобный объект, который создает в памяти какую-то структуру со всеми ходами. Это позволит игрокам иметь данные для выработки стратегии. Хотя и это пока рано.

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

а. Объект кость удалить - использовать вместо неё функцию в базовом игроке. Так мы сделаем архитектуру более ясной (пусть даже пожертвуем возможной гибкостью).
б. Объект Игрок-Компютер не будет иметь хитрой стратегии, зависящей от ходов Человека. Стратегия будет жесткая или случайная. То есть либо Компьютер будет определять, нужно ли ещё бросить кость рандомом, либо из некоторого булевского массива.
в. Для быстроты проверки уменьшим число выигрышных очков до 20.

После создания модели будет легче рассуждать об архитектуре.

Я примерно такую модель уже создал, но использовал более лаконичный язык, чем C++ (понятно какой). Так проще и быстрее получить работающую модель. Однако, чтобы не раздражать других участников, сюда не выложил.

12K
21 февраля 2010 года
Ghox
297 / / 26.07.2009
Насчет работы с GUI - честно говоря не знаю как с ним обстоит дело, т.к. никогда использовать не приходилось, поэтому ничего сказать не могу. Поэтому будем считать что ты прав в твоих утверждениях насчет GUI в замечаниях 1 и 2, но мне нужны кое-какие уточнения (вопросы - ниже).
Цитата: Kogrom
1. Объект Ведущий владеет Интерфейсом (или в терминологии MVC - Видом). Вроде бы это логично и для консоли вполне исполнимо, но если переходишь к GUI, то для сохранения этой иерархии приходится использовать финты. Потому для себя я решил, что Вид должен содержать в себе всё остальное (это не значит, что он всем управляет).


Хмм... Как-то не совсем понял... Ты предлагаешь, что в составе класса Вид (Интерфейс) должны будут содержаться (как члены класса) объект Ведущий и прочие объекты используемые в программе? Я правильно понял твою идею?

Цитата: Kogrom
2. Интерфейс напрямую владеет Логгером. Думаю, это не верно. Интерфейс должен выполнять лишь обмен данными с пользователем. Другой работы он не выполняет. Тогда Вид легче поменять на GUI-шный.


Т.е. логгер, если он будет, должен быть объектом, которым интерфейс непосредственно не управляет (хотя, возможно, в силу замечания 1, и содержит его как свою внутреннюю переменную)?

Цитата: Kogrom
3. С одной стороны, Логгер, как объект, пишущий в файл пока не нужен. С другой стороны, хорошо бы иметь подобный объект, который создает в памяти какую-то структуру со всеми ходами. Это позволит игрокам иметь данные для выработки стратегии. Хотя и это пока рано.


Т.е. ты предлагаешь логгер пока исключить, и добавить его потом?

Цитата: Kogrom
4. Программистам сложно переварить столько текста, понять, будет ли данный алгоритм красивым. Проще разобраться с кодом и с программой. Предлагаю построить некоторую модель игры.


А в каком виде ты хотел бы видеть модель? Могу сделать пример кода для того, что я описал - набросок иллюстрирующий мою словесную схему, но сначала лучше все-таки получить от тебя пояснения по моим вопросам в данном посте.

Цитата: Kogrom
Для начала предлагаю внести некоторые упрощения:

а. Объект кость удалить - использовать вместо неё функцию в базовом игроке. Так мы сделаем архитектуру более ясной (пусть даже пожертвуем возможной гибкостью).
б. Объект Игрок-Компютер не будет иметь хитрой стратегии, зависящей от ходов Человека. Стратегия будет жесткая или случайная. То есть либо Компьютер будет определять, нужно ли ещё бросить кость рандомом, либо из некоторого булевского массива.
в. Для быстроты проверки уменьшим число выигрышных очков до 20.


Согласен, думаю пока можно пойти на такие упрощения.

87
21 февраля 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ghox
Ты предлагаешь, что в составе класса Вид (Интерфейс) должны будут содержаться (как члены класса) объект Ведущий и прочие объекты используемые в программе?


Это только вариант. Объясню, почему мне нравится такая структура. В ООПшных GUI-шных приложениях (и может не только в них) интерфейс обычно является объектом, который, грубо говоря, создаётся вначале работы программы и удаляется в конце. При этом удаление (а иногда и создание) зависит от событий, вызванных действием пользователя. Например, пользователь выбирает некий пункт меню или кликает по крестику в верхнем правом углу.

В случае, когда Ведущий (Модель+Контроллер) владеет Видом, получится так: пользователь кликнул крест, Вид сообщил Ведущему, Ведущий позволил Виду удалится, Ведущий удалился. Это в лучшем случае. В худшем придётся разбивать удаление Ведущего на два этапа, чтобы части, запущенные в отдельных потоках не обращались к удаленному Виду.

В случае, когда Вид владеет Ведущим, то требуется по крайней мере на одно действие меньше.

Цитата: Ghox
Т.е. логгер, если он будет, должен быть объектом, которым интерфейс непосредственно не управляет (хотя, возможно, в силу замечания 1, и содержит его как свою внутреннюю переменную)?


Да. Интерфейс исполняет только функцию общения с пользователем. Так его проще заменить. Не обязательно на GUI. Но, например, на интерфейс, который общается с ботом. Тогда в кости сможет играть не только человек, но и сторонняя программа.

Цитата: Ghox
Т.е. ты предлагаешь логгер пока исключить, и добавить его потом?


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

Цитата: Ghox
А в каком виде ты хотел бы видеть модель? Могу сделать пример кода для того, что я описал - набросок иллюстрирующий мою словесную схему, но сначала лучше все-таки получить от тебя пояснения по моим вопросам в данном посте.



Нужен не набросок, а прототип. То есть работающая программа, содержащая некоторые элементы будущей. При том важно посмотреть, как будут состыковываться основные элементы: Интерфейс, Ведущий, Абстрактный Игрок, Игрок-Компьютер, Игрок-Человек.

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

Зато Игрока-Человека можно сделать практически так, как он будет в конечной программе.

Что даст прототип? Он позволит как-то проверить наши теоретические схемы, увидеть дефекты, ненужности, необходимость в неких дополнительных объектах, функциях.

12K
23 февраля 2010 года
Ghox
297 / / 26.07.2009
Цитата: Kogrom
Это только вариант. Объясню, почему мне нравится такая структура. В ООПшных GUI-шных приложениях (и может не только в них) интерфейс обычно является объектом, который, грубо говоря, создаётся вначале работы программы и удаляется в конце. При этом удаление (а иногда и создание) зависит от событий, вызванных действием пользователя. Например, пользователь выбирает некий пункт меню или кликает по крестику в верхнем правом углу.

В случае, когда Ведущий (Модель+Контроллер) владеет Видом, получится так: пользователь кликнул крест, Вид сообщил Ведущему, Ведущий позволил Виду удалится, Ведущий удалился. Это в лучшем случае. В худшем придётся разбивать удаление Ведущего на два этапа, чтобы части, запущенные в отдельных потоках не обращались к удаленному Виду.

В случае, когда Вид владеет Ведущим, то требуется по крайней мере на одно действие меньше.


Ну я вообще, когда говорил про объект-интерфейс, немного другое имел в виду. Как я понимаю, в программе есть некий глобальный интерфейс (в случае консоли - представлен потоками cout и cin, в случае GUI - экземплярами классов GUI, представляющих например окна Windows). И еще объявляется отдельно класс "Интерфейс", который на самом деле является посредником между глобальным интерфейсом и каким-то другим классом, который этот класс-посредник использует. И объект "ведущий" имеет в своем составе объект этого класса-посредника для обращения через него к глобальному интерфейсу. Я представлял себе все так, и вовсе не имел в виду заключить в класс Ведущий весь глобальный класс Интерфейс (как похоже понимаешь ты с моих слов).

Цитата: Kogrom
Нужен не набросок, а прототип. То есть работающая программа, содержащая некоторые элементы будущей. При том важно посмотреть, как будут состыковываться основные элементы: Интерфейс, Ведущий, Абстрактный Игрок, Игрок-Компьютер, Игрок-Человек.

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

Зато Игрока-Человека можно сделать практически так, как он будет в конечной программе.

Что даст прототип? Он позволит как-то проверить наши теоретические схемы, увидеть дефекты, ненужности, необходимость в неких дополнительных объектах, функциях.


Честно говоря, пока не вижу смысла сейчас, пока не выработана и не согласована структура программы (в плане взаимодействия классов), делать работающий код... Мне непонятно, что такое позволит сейчас увидеть работающий код, чего не даст увидеть словесное описание или набросок кода (пусть нерабочий), показывающий взаимодействие между классами... Все что ты говоришь - ИМХО можно и без рабочего кода определить.

Но может позже это прояснится для меня. Поэтому пока сделал работающие примеры. Первый - для моего исходного варианта 1, второй - упрощенный в соответствии с указаниями. Выложу их чуть позже (сегодня), надо вкратце их прокомментировать.

12K
23 февраля 2010 года
Ghox
297 / / 26.07.2009
Вариант 1 - в соответствии с моим описанием, присутствует и логгер - информация, выводимая на экран, дублируется (если логгер включен) в лог-файл с именем вида Dices_текущее_время.log. В данном варианте я на различиях между игроком-человеком и компьютером решил не акцентироваться, поэтому сделал вариант что оба игрока - компьютерные, и пользователь только наблюдает за их игрой.
Код:
#include <ctime>
#include <string>
#include <sstream>
#include <iostream>
#include <fstream>
using namespace std;

// игральная кость
class Dice
{
public:
    unsigned int roll();
};

unsigned int Dice::roll()
{
    srand((unsigned int)time(0));
    return rand() % 6 + 1;
}

// логгер
class Logger
{
    ofstream os;
public:
    Logger();
    ~Logger();
    void out(const string&);
};

Logger::Logger()
{
    string filename = "Dices_";
    time_t *t;
    time(t);
    tm *current_time = localtime(t);
    char timestr[15];
    strftime(timestr, 15, "%Y%m%d%H%M%S", current_time);
    filename += timestr;
    filename += ".log";
    os.open(filename.c_str());
    if(!os.is_open())
    {
        cout << "Unable open log\n";
        exit(1);
    }
}

Logger::~Logger()
{
    os.close();
}

void Logger::out(const string& str)
{
    os << str;
}

// интерфейс без логгера
class Interface
{
public:
    Interface() {}
    virtual ~Interface() {}
    virtual void out(const string&);
    char in();
};

void Interface::out(const string& str)
{
    cout << str;
}

char Interface::in()
{
    char result;
    cin >> result;
    return result;
}

// интерфейс с логгером
class Interface1 : public Interface
{
    Logger log;
public:
    Interface1() {}
    virtual ~Interface1() {}
    virtual void out(const string&);
};

void Interface1::out(const string& str)
{
    cout << str;
    log.out(str);
}

// Ведущий (объявление)
class GameField;

// Игрок
class Player
{
    static const unsigned int maxPoints = 20;
    GameField *pGame;
    const unsigned int otherPlayersQ;
    const unsigned int playerID;
    unsigned int totalPoints;
public:
    Player(GameField*, unsigned int, unsigned int);
    ~Player() {}
    void makeTurn(const unsigned int*);
    unsigned int getPoints() const  { return totalPoints; }
};

// Ведущий (определение)
class GameField
{
    static const string iFaceTypRequest;
    Dice dice;
    Interface *iFace;
    const unsigned int playersQuantity;
    Player **players;
    unsigned int winnerID;
    unsigned int *playersPoints;
public:
    GameField(unsigned int = 2);
    ~GameField();
    void play();
    bool rollDice(unsigned int&, unsigned int);
    void setWinner(unsigned int);
};
const string GameField::iFaceTypRequest = "Game with logging? (y/n)\n";

Player::Player(GameField* game, unsigned int opq, unsigned int ID) :
            pGame(game), otherPlayersQ(opq), playerID(ID), totalPoints(0) {}

void Player::makeTurn(const unsigned int *oPoints)
{
    bool bStop = false;
    unsigned int points = 0, rollPoints;
    while(!bStop)
    {
        bStop = pGame->rollDice(rollPoints, playerID);
        if(bStop)
            points = 0;
        else
        {
            points += rollPoints;
            if(bStop = (totalPoints + points) > maxPoints)
                pGame->setWinner(playerID);
            else
                bStop = points > 5;
        }
    }
    totalPoints += points;
}

GameField::GameField(unsigned int pq) : playersQuantity(pq), winnerID(pq)
{
    iFace = new Interface;
    iFace->out(iFaceTypRequest);
    if(iFace->in() == 'y')
    {
        delete iFace;
        iFace = new Interface1;
    }
    players = new Player*[playersQuantity];
    for(unsigned int i = 0; i < playersQuantity; ++i)
        players = new Player(this, playersQuantity - 1, i);
    playersPoints = new unsigned int[playersQuantity - 1];
}

GameField::~GameField()
{
    for(unsigned int i = 0; i < playersQuantity; --i)
        delete players;
    delete[] players;
    delete[] playersPoints;
    delete iFace;
}

void GameField::play()
{
    unsigned int i = playersQuantity - 1;
    while(winnerID == playersQuantity)
    {
        i = (i == playersQuantity - 1) ? 0 : i + 1;
        stringstream msgString;
        msgString << "Player # " << i << " turn:\n";
        iFace->out(msgString.str());
        for(unsigned int j = 0, k = 0; j < playersQuantity; ++j)
            if(j != i)
                playersPoints[k++] = players[j]->getPoints();
        players->makeTurn(playersPoints);
        stringstream msgString2;
        msgString2 << "Total " << players->getPoints() << " points\n";
        iFace->out(msgString2.str());
        _sleep(500);
    }
    iFace->out(string("Game over\n"));
}

bool GameField::rollDice(unsigned int& result, unsigned int ID)
{
    result = dice.roll();
    stringstream msgString;
    if(result == 1)
    {
        msgString << "Player # " << ID << " rolls 1 point and loses all points in the turn\n";
        iFace->out(msgString.str());
        return true;
    }
    msgString << "Player # " << ID << " rolls " << result << " points\n";
    iFace->out(msgString.str());
    return false;
}

void GameField::setWinner(unsigned int ID)
{
    winnerID = ID;
    stringstream msgString;
    msgString << "Player # " << ID << " wins\n";
    iFace->out(msgString.str());
}

int main()
{
    GameField Game;
    Game.play();
}
87
23 февраля 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ghox
Ну я вообще, когда говорил про объект-интерфейс, немного другое имел в виду.


Я прошу ознакомиться с MVC, чтобы использовать примерно одну терминологию. Вроде бы, у тебя говорилось о Контроллере. А Ведущий тогда - Модель, или какая-то её часть. Но и тут не очевидно, кто кому владелец.

Цитата: Ghox
Честно говоря, пока не вижу смысла сейчас, пока не выработана и не согласована структура программы (в плане взаимодействия классов), делать работающий код... Мне непонятно, что такое позволит сейчас увидеть работающий код, чего не даст увидеть словесное описание или набросок кода (пусть нерабочий), показывающий взаимодействие между классами... Все что ты говоришь - ИМХО можно и без рабочего кода определить.



Тут надо вникнуть, чем модель Водопада в разработке ПО отличается от итеративного процесса.

Я вижу, что необходимое словесное описание структуры программы у нас есть - это набор основных классов. Чтобы ясно увидеть какими ниточками эти классы должны быть связаны, удобнее поработать с кодом.

12K
23 февраля 2010 года
Ghox
297 / / 26.07.2009
Вариант 2 - в соответствии с предложенными Kogrom'ом упрощениями, но пока решил не делать, чтобы Интерфейс включал все объекты.

Классы Кость и Логгер исключены.

Изменения класса Ведущий:
переменные:
удаляется переменная "кость";
функции:
удаляется функция бросания кости
добавляется функция вывода в интерфейс информации о результате броска кости, передаваемой игроком

Изменения класса Игрок:
теперь представлен в двух видах (путем наследования от базового класса Игрок):
Игрок-компьютер
Игрок-человек

Изменения класса Интерфейс:
теперь представлен в одном варианте - без логгера

Процесс создания объектов:
1. Создается объект интерфейса.
2. Создается массив объектов-игроков и заполняется объектами-игроками, первым игроком добавляется игрок-человек - остальные - игроки-боты.

Процесс игры:
1. Ведущий поочередно передает ход от игрока к игроку, вызывая соответствующую функцию игрока, передавая при это количество очков имеющихся у противника (которое ведущий запрашивает у игрока-противника). Каждая передача хода сопровождается выводом соотв. сообщений в интерфейс.
2. Игрок при совершении хода:
Один или несколько раз совершает бросок кости, теперь посредством своей внутренней функции (не обращаясь к ведущему).
После каждого броска:
2.1. Ведущему отправляется информация о результате броска кости (для вывода в интерфейс).
2.2. Игрок определяет, может ли он продолжать ход (не выпала ли единица)
2.2.1. если выпала единица - завершает ход (выход из функции хода игрока, к накопленным очкам ничего не суммируется);
2.2.2. иначе - сначала проводит проверку, не стал ли он, с учетом накопленных ранее и выпавших за текущий ход очков, победителем:
если стал победителем - то обращается к ведущему, чтобы тот обозначил его как победителя, и завершает ход;
если пока победителем не стал - то принимает решение (в случае игрока-человека - идет запрос к пользователю) - стоит ли дальше продолжать броски. Если решает завершить ход, то выпавшие за ход очки суммируются к накопленным за предыдущие ходы, и происходит выход из функции.
3. Также ведущий перед очередной передачей хода игроку, определяет, не стал ли кто-то из игроков победителем. Если кто-то стал победителем - игра прекращается.
Код:
#include <ctime>
#include <string>
#include <sstream>
#include <iostream>
#include <fstream>
using namespace std;

// Интерфейс
class Interface
{
public:
    Interface() {}
    ~Interface() {}
    void out(const string&);
    char in();
    static bool getConfirmation();
};

void Interface::out(const string& str)
{
    cout << str;
}

char Interface::in()
{
    char result;
    cin >> result;
    return result;
}

bool Interface::getConfirmation()
{
    char answer;
    cout << "Do you want to continue? y/n" << endl;
    cin >> answer;
    return answer == 'y';
}

// Ведущий (объявление)
class GameField;

// Игрок (базовый класс)
class Player
{
protected:
    static const unsigned int maxPoints = 20;
    GameField *pGame;
    const unsigned int otherPlayersQ;
    const unsigned int playerID;
    unsigned int totalPoints;
    unsigned int rollDice();
public:
    Player(GameField*, unsigned int, unsigned int);
    virtual ~Player() {}
    virtual void makeTurn(const unsigned int*) = 0;
    unsigned int getPoints() const  { return totalPoints; }
};

// Ведущий (определение)
class GameField
{
    Interface *iFace;
    const unsigned int playersQuantity;
    Player **players;
    unsigned int winnerID;
    unsigned int *playersPoints;
public:
    GameField(unsigned int = 2);
    ~GameField();
    void play();
    bool rollDice(unsigned int&, unsigned int);
    void setWinner(unsigned int);
    void announceRoll(unsigned int, unsigned int);
};

Player::Player(GameField* game, unsigned int opq, unsigned int ID) :
            pGame(game), otherPlayersQ(opq), playerID(ID), totalPoints(0) {}

unsigned int Player::rollDice()
{
    srand((unsigned int)time(0));
    return rand() % 6 + 1;
}

// Игрок-компьютер
class RobotPlayer : public Player
{
public:
    RobotPlayer(GameField*, unsigned int, unsigned int);
    virtual ~RobotPlayer() {}
    virtual void makeTurn(const unsigned int*);
};

RobotPlayer::RobotPlayer(GameField* game, unsigned int opq, unsigned int ID):
            Player(game, opq, ID) {}

void RobotPlayer::makeTurn(const unsigned int *oPoints)
{
    bool bStop = false;
    unsigned int points = 0, rollPoints;
    while(!bStop)
    {
        rollPoints = rollDice();
        pGame->announceRoll(rollPoints, playerID);
        if(bStop = rollPoints == 1)
            points = 0;
        else
        {
            points += rollPoints;
            if(bStop = (totalPoints + points) > maxPoints)
                pGame->setWinner(playerID);
            else
                bStop = points > 5;
        }
    }
    totalPoints += points;
}

// Игрок-человек
class HumanPlayer : public Player
{
    bool (*confirmFunc)();
public:
    HumanPlayer(GameField*, unsigned int, unsigned int, bool (*func)());
    virtual ~HumanPlayer() {}
    virtual void makeTurn(const unsigned int*);
};

HumanPlayer::HumanPlayer(GameField *game,
                        unsigned int opq,
                        unsigned int ID,
                        bool (*func)()) :
        Player(game, opq, ID), confirmFunc(func) {}

void HumanPlayer::makeTurn(const unsigned int *oPoints)
{
    bool bStop = false;
    unsigned int points = 0, rollPoints;
    while(!bStop)
    {
        rollPoints = rollDice();
        pGame->announceRoll(rollPoints, playerID);
        if(bStop = rollPoints == 1)
            points = 0;
        else
        {
            points += rollPoints;
            if(bStop = (totalPoints + points) > maxPoints)
                pGame->setWinner(playerID);
            else
                bStop = !confirmFunc();
        }
    }
    totalPoints += points;
}

GameField::GameField(unsigned int pq) : playersQuantity(pq), winnerID(pq)
{
    iFace = new Interface;
    players = new Player*[playersQuantity];
    players[0] = new HumanPlayer(this, playersQuantity - 1, 0, Interface::getConfirmation);
    for(unsigned int i = 1; i < playersQuantity; ++i)
        players = new RobotPlayer(this, playersQuantity - 1, i);
    playersPoints = new unsigned int[playersQuantity - 1];
}

GameField::~GameField()
{
    for(unsigned int i = 0; i < playersQuantity; --i)
        delete players;
    delete[] players;
    delete[] playersPoints;
    delete iFace;
}

void GameField::play()
{
    unsigned int i = playersQuantity - 1;
    while(winnerID == playersQuantity)
    {
        i = (i == playersQuantity - 1) ? 0 : i + 1;
        stringstream msgString;
        msgString << "Player # " << i << " turn:\n";
        iFace->out(msgString.str());
        for(unsigned int j = 0, k = 0; j < playersQuantity; ++j)
            if(j != i)
                playersPoints[k++] = players[j]->getPoints();
        players->makeTurn(playersPoints);
        stringstream msgString2;
        msgString2 << "Total " << players->getPoints() << " points\n";
        iFace->out(msgString2.str());
        _sleep(500);
    }
    iFace->out(string("Game over\n"));
}

void GameField::announceRoll(unsigned int result, unsigned int ID)
{
    stringstream msgString;
    msgString << "Player # " << ID << " rolls " << result << " points\n";
    iFace->out(msgString.str());
}

void GameField::setWinner(unsigned int ID)
{
    winnerID = ID;
    stringstream msgString;
    msgString << "Player # " << ID << " wins\n";
    iFace->out(msgString.str());
}

int main()
{
    GameField Game;
    Game.play();
}
12K
23 февраля 2010 года
Ghox
297 / / 26.07.2009
Цитата: Kogrom
Я прошу ознакомиться с MVC, чтобы использовать примерно одну терминологию. Вроде бы, у тебя говорилось о Контроллере. А Ведущий тогда - Модель, или какая-то её часть. Но и тут не очевидно, кто кому владелец.


ОК, попробую почитать, но наверно уже не сегодня (в Википедии глянул, но этого для меня мало для полного понимания о чем идет речь).

Цитата: Kogrom
Я вижу, что необходимое словесное описание структуры программы у нас есть - это набор основных классов. Чтобы ясно увидеть какими ниточками эти классы должны быть связаны, удобнее поработать с кодом.


То что удобнее (в некоторых случаях) с кодом работать чем со схемами - это-то понятно, мне непонятно, зачем работающий (компилящийся и выполняющийся) код сейчас нужен?

Цитата: Kogrom
То есть работающая программа, содержащая некоторые элементы будущей.


Разве не хватило бы кода (наброска), в котором все ниточки связей между классами были бы показаны, но при этом то что происходит внутри классов (то что не относится к взаимодействию между классами) было бы закомментарено? Я в общем-то это и имел в виду здесь (хотя наверно неясно выразился):

Цитата: Ghox
Могу сделать пример кода для того, что я описал - набросок иллюстрирующий мою словесную схему


P.S. Извиняюсь если излишне резок в высказываниях, ничем обидеть не хотел. Ну и заодно извиняюсь за глупые вопросы если таковые есть. :)

87
23 февраля 2010 года
Kogrom
2.7K / / 02.02.2008
Критика.

Пишу сразу по двум вариантам. Кстати, понравилось то, что во втором варианте меньше строк, хотя мы добавили 2 класса.

1. Игрок знает о Ведущем, а Ведущий знает об Игроке. Это лишняя связь. Так как Игрок не должен знать о конкретном Ведущем. Грубо говоря, Игрок по ошибке может задействовать метод play() Ведущего.

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

2. Что требуется от Игрока? Делать ходы, принимать решения о прекращении хода и, возможно, помнить суммарное количество очков за ход, за игру. Игрок же в реализациях сообщает, что собрался ходить, игрок определяет, кто выиграл. Это неверно. Слишком много задач.

3. Мелкие замечания.
3.1. Используются цифровые идентификаторы игроков. Это как-то нечеловечно. Я должен помнить какой я игрок - первый, пятый...
3.2 Каждый раз спрашивается "Хотите ли вести лог?" Нормального пользователя это будет злить после третьего запуска игры.
3.3. Методы makeTurn игроков содержат много дублирования.
3.4. Вот этот код озадачил:

unsigned int i = playersQuantity - 1;
...
i = (i == playersQuantity - 1) ? 0 : i + 1;

Предлагаю подумать, как изменить структуру, чтобы игроки не знали о Ведущем. Сам я имею представление, как это сделать, но чтобы было интереснее не буду здесь говорить.
87
23 февраля 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ghox
ОК, попробую почитать, но наверно уже не сегодня (в Википедии глянул, но этого для меня мало для полного понимания о чем идет речь).


Вникать особо не надо. Вполне хватит приблизительного понимания терминологии.

Цитата: Ghox
То что удобнее (в некоторых случаях) с кодом работать чем со схемами - это-то понятно, мне непонятно, зачем работающий (компилящийся и выполняющийся) код сейчас нужен?


Чисто психологически приятнее иметь какой-то работающий код. Честно говоря, я не компилировал второй вариант, но теоретически уже можно играть. И это должно радовать разработчиков, вселять уверенность, что всё получится.

Цитата: Ghox
Извиняюсь если излишне резок в высказываниях, ничем обидеть не хотел. Ну и заодно извиняюсь за глупые вопросы если таковые есть.


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

87
24 февраля 2010 года
Kogrom
2.7K / / 02.02.2008
Решил подробнее расписать некоторые моменты, использую термины GRASP.

Цитата: Kogrom
1. Игрок знает о Ведущем, а Ведущий знает об Игроке. Это лишняя связь. Так как Игрок не должен знать о конкретном Ведущем. Грубо говоря, Игрок по ошибке может задействовать метод play() Ведущего.


Конечно, ошибка с play() - это слишком фантастично. Но у меня часто была другая ситуация. В реальной программе классы Ведущий и Игрок имели бы свои заголовочный и исполнимый файл. Если они независимы, то могут компилироваться отдельно. Если же Игрок включает хедер Ведущего, то любое изменение в хедере Ведущего приведет к необходимости перекомпиляции Игрока. Пусть даже мы будем добавлять закрытые данные и функции. Отсюда необоснованные потери времени.

То есть получаем нарушение шаблона Low Coupling (Слабая связанность).

Цитата: Kogrom
2. Что требуется от Игрока? Делать ходы, принимать решения о прекращении хода и, возможно, помнить суммарное количество очков за ход, за игру. Игрок же в реализациях сообщает, что собрался ходить, игрок определяет, кто выиграл. Это неверно. Слишком много задач.



Когда задач много, то и много зависимостей. Если много зависимостей, то нарушается шаблон High Cohesion (Сильное зацепление). Это мешает использовать класс в других программах, так как его сложно оторвать от остальной программы. Кроме того, такой класс труднее понять.

Прошу не воспринимать это как критику, а как анализ. Возможно, Ghox-у будет интересно и подобный самоанализ производить. Или найдёт ошибки в моём анализе.

87
28 февраля 2010 года
Kogrom
2.7K / / 02.02.2008
Итоги за эту неделю.

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

Между тем, я просматривал тему и задумался над этим постом:
http://forum.codenet.ru/showpost.php?p=314638&postcount=43

Тут меня привлёк стиль изложения обязанностей классов. Но думаю, что надо сделать немного более абстрактно. По такому шаблону:

<Имя класса>

Обязанности класса:

1. <Обязанность 1>
2. <Обязанность 1>
...
N. <Обязанность N>

Взаимодействующие (связанные) классы:

1. <Класс 1>
2. <Класс 2>
...
M. <Класс M>

N, M - не более семи. Лучше - не более пяти.

Связанным классом будут считаться только те, о которых класс знает.
Попробую в таком стиле что-то изложить на следующей неделе.
12K
02 марта 2010 года
Ghox
297 / / 26.07.2009
Цитата: Kogrom
Никто ничего не разместил. Возможно, тут была обида на критику или сложности с задачей, которую я предложил (сделать так, чтобы игрок не знал о ведущем). Просто потеря интереса не принимается, так как просто ничего не бывает :)


Обиды нет никакой у меня. Я к критике нормально отношусь, за исключением случаев когда она преподносится в хамском виде. Тут хамства никакого не было, так что все нормально. :)

А вот то что сложности есть - это да. Сложновато пока все это для меня, да и был в последнее время загружен на работе...

Пока попробую описать варианты попытки устранения замечания 1.

Цитата: Kogrom
1. Игрок знает о Ведущем, а Ведущий знает об Игроке. Это лишняя связь. Так как Игрок не должен знать о конкретном Ведущем. Грубо говоря, Игрок по ошибке может задействовать метод play() Ведущего.

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


Цитата: Kogrom
Предлагаю подумать, как изменить структуру, чтобы игроки не знали о Ведущем. Сам я имею представление, как это сделать, но чтобы было интереснее не буду здесь говорить.


Цитата: Kogrom
Конечно, ошибка с play() - это слишком фантастично. Но у меня часто была другая ситуация. В реальной программе классы Ведущий и Игрок имели бы свои заголовочный и исполнимый файл. Если они независимы, то могут компилироваться отдельно. Если же Игрок включает хедер Ведущего, то любое изменение в хедере Ведущего приведет к необходимости перекомпиляции Игрока. Пусть даже мы будем добавлять закрытые данные и функции. Отсюда необоснованные потери времени.

То есть получаем нарушение шаблона Low Coupling (Слабая связанность).


Сейчас процесс совершения хода одним Игроком реализован в виде однократного вызова соотв. функции хода Игрока. Но в процессе хода происходит серия бросков кости, и результат каждого броска должен быть выведен в Интерфейс. Игрок об Интерфейсе ничего не знает, о нем знает только Ведущий. Поэтому информация о каждом броске должна так или иначе проходить через Ведущего.

В текущем моем варианте сделано так, что отправление информации осуществляется прямым обращением игрока к Ведущему (вызов игроком функций Ведущего). Что влечет за собой необходимость для Игрока "знать" об устройстве Ведущего.

Варианты как можно было бы избавиться от прямой связи Игрок -> Ведущий при передаче результатов броска кости в Интерфейс:

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

2-й вариант. Передачу информации о броске кости делать с использованием указателей на функции, которые передаются игроку при его инициализации. Т.е. аналогично тому, как мы решили сделать для класса "Игрок-человек", чтобы обеспечить диалог объекта-Игрок с пользователем во время совершения хода, и при этом обеспечить чтобы Игрок ничего не знал об Интерфейсе. Тут, может быть, так же можно сделать: игрок выводит информацию через указатели на функции, что именно делают эти функции - ничего не знает.

3-й вариант. Создать ту самую структуру о которой ты говорил ранее, когда говорил о логгере: объект, который будет хранить информацию о совершенных игроками ходах и бросках кости. Игроки и Ведущий знают об устройстве этой структуры, и владеют ею. Допустим, Ведущий имеет объект этой структуры как свою переменную, а Игроки имеют в своем составе указатель на объект структуры (при инициализации Игрока Ведущий передает адрес своего внутреннего объекта). Игрок при совершении хода обновляет содержимое структуры, записывая в нее информацию о своих бросках, и итоговом результате хода. Ведущий, после окончания хода Игрока, анализирует изменения в структуре, и выводит информацию в Интерфейс. Недостаток. Вывод информации о бросках в Интерфейс возможен только после завершения хода Игрока. Что в случае Игрока-человека неправильно, т.к. при совершении хода пользователем информация о результате броска должна выводиться в Интерфейс сразу после броска, чтобы пользователь мог принять решение о завершении хода.

4-й вариант. Вместо единой функции совершения хода Игрока использовать функцию одиночного броска кости Игроком. Тогда Ведущий, при совершении хода Игроком, вывзывает эту функцию до тех пор, пока не выпала единица либо пока Игрок не подал сигнал что он желает завершить ход.

Вот пока что есть у меня. Насчет второго замечания - надо еще подумать... А насчет третьего замечания (вернее группы замечаний выделенных в пункт 3) - считаю что пока преждевременно их рассматривать, т.к. эти замечания относятся к внутренней логике классов и влияние на взаимодействие классов не оказывают. Ими заняться можно и попозже. Тем более что мой вариант не являлся предлагаемым рабочим вариантом, а лишь затребованным тобой примером рабочего кода, иллюстрирующим мои предыдущие схемы.

87
02 марта 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ghox
3-й вариант. Создать ту самую структуру о которой ты говорил ранее, когда говорил о логгере: объект, который будет хранить информацию о совершенных игроками ходах и бросках кости. Игроки и Ведущий знают об устройстве этой структуры, и владеют ею. Допустим, Ведущий имеет объект этой структуры как свою переменную, а Игроки имеют в своем составе указатель на объект структуры (при инициализации Игрока Ведущий передает адрес своего внутреннего объекта). Игрок при совершении хода обновляет содержимое структуры, записывая в нее информацию о своих бросках, и итоговом результате хода. Ведущий, после окончания хода Игрока, анализирует изменения в структуре, и выводит информацию в Интерфейс. Недостаток. Вывод информации о бросках в Интерфейс возможен только после завершения хода Игрока. Что в случае Игрока-человека неправильно, т.к. при совершении хода пользователем информация о результате броска должна выводиться в Интерфейс сразу после броска, чтобы пользователь мог принять решение о завершении хода.

4-й вариант. Вместо единой функции совершения хода Игрока использовать функцию одиночного броска кости Игроком. Тогда Ведущий, при совершении хода Игроком, вывзывает эту функцию до тех пор, пока не выпала единица либо пока Игрок не подал сигнал что он желает завершить ход.



Думаю, можно эти подходы объединить. Так как игроку всё равно надо знать об объекте-Таблице (далее так буду звать объект, запоминающий данные), чтобы вырабатывать стратегию, то нет особого смысла игроку хранить данные о своих ходах в себе.

Тогда можно сделать так:

1. Ведущий попросил Игрока сделать бросок кости. Игрок делает бросок и записывает его результаты в Таблицу.

2. Игрок принимает решение, следует ли сделать ещё один бросок. Это решение он Возвращает Ведущему (как возвращаемое значение функции, с помощью которой Ведущий к нему обратился).

3. Ведущий смотрит в Таблицу и выводит свежие данные из неё. В принципе, он может выводить все данные за последний ход (не только за последний бросок), включая и сумму очков. А может заодно и общие результаты.

12K
04 марта 2010 года
Ghox
297 / / 26.07.2009
Цитата: Kogrom
Думаю, можно эти подходы объединить. Так как игроку всё равно надо знать об объекте-Таблице (далее так буду звать объект, запоминающий данные), чтобы вырабатывать стратегию, то нет особого смысла игроку хранить данные о своих ходах в себе.

Тогда можно сделать так:

1. Ведущий попросил Игрока сделать бросок кости. Игрок делает бросок и записывает его результаты в Таблицу.

2. Игрок принимает решение, следует ли сделать ещё один бросок. Это решение он Возвращает Ведущему (как возвращаемое значение функции, с помощью которой Ведущий к нему обратился).

3. Ведущий смотрит в Таблицу и выводит свежие данные из неё. В принципе, он может выводить все данные за последний ход (не только за последний бросок), включая и сумму очков. А может заодно и общие результаты.


Опять отвечаю не совсем вовремя...

Идея мне, в общем-то, нравится. Тем более все равно планировалось что в программе будет некая внутренняя структура которая будет хранить информацию о ходах, просто ввести ее хотели попозже...

Тогда насчет данного класса Таблица нужно определить:

1. Какая информация является необходимой для поддержки ее хранения в Таблице? Пока идеи что таблица обязательно должна хранить:
- Информацию по каждому ходу каждого из игроков, в составе которой должны храниться записи о каждом броске игрока в течение хода. Эти записи должны храниться в такой последовательности, которая позволяет извлекать записи о бросках в хронологическом порядке. Т.е. контейнер записей каждого хода должен быть таким, чтобы по каждой записи можно было определить, что эта запись - о первом броске, эта - о втором и т.д. Хотя может это уже мелочи (насчет порядка хранения бросков).
- Думаю, что нужно также хранить информацию о том, какой игрок совершает текущий ход, и состояние хода (завершен / не завершен).
- Возможно, что было бы полезно для каждого из завершенных ходов хранить признак - завершился ли ход по выбросу единицы и сгоранию очков игрока, или завершил добровольно и сохранил очки.

2. Какие обязанности назначить данному объекту-Таблице?
- Первое - конечно же, обязанности приема информации о ходе / броске кости и записи в свой внутренний контейнер.
- Также должна быть обязанность выдачи информации по запросу внешнего объекта, вызывающего соотв. функции Таблицы.
- Возмжно, что можно назначить Таблице другие обязанности, но над этим надо еще отдельно подумать.

87
04 марта 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ghox
2. Какие обязанности назначить данному объекту-Таблице?
- Первое - конечно же, обязанности приема информации о ходе / броске кости и записи в свой внутренний контейнер.
- Также должна быть обязанность выдачи информации по запросу внешнего объекта, вызывающего соотв. функции Таблицы.
- Возмжно, что можно назначить Таблице другие обязанности, но над этим надо еще отдельно подумать.



Поздно сейчас, потому только некоторые идеи.

Внутреннее строение Таблицы - её дело. То есть все её дынные закрыты. Нам сейчас требуется только несколько функций. Их и надо попытаться реализовать, чтобы "ощутить" Таблицу.

Игрок может только результаты броска записывать. Признак того, что он потерял очки - единица в последнем ходе.

Тогда Ведущий может принимать решение, засчитан ход или нет. Или лучше сама Таблица в этом случае в результат хода запишет 0. И это тоже будет признак, что выпала единица.

12K
07 марта 2010 года
Ghox
297 / / 26.07.2009
Цитата: Kogrom
Внутреннее строение Таблицы - её дело. То есть все её дынные закрыты.


Я наверно недостаточно ясно выразился... Когда я говорил о данных - я имел в виду - выдачу каких данных (и соответственно хранение в каком-то виде, но это уже вопрос реализации, его я напрямую не касался) должна обеспечивать Таблица по запросам к ней внешних объектов. Внутреннее устройство Таблицы - это конечно же дело таблицы, его я не пытался рассматривать.

Цитата: Kogrom
Нам сейчас требуется только несколько функций. Их и надо попытаться реализовать, чтобы "ощутить" Таблицу.

Игрок может только результаты броска записывать. Признак того, что он потерял очки - единица в последнем ходе.

Тогда Ведущий может принимать решение, засчитан ход или нет.


Для начала предлагаю такие функции:
- Запись информации о результате броска некоторым игроком. В вызове функции передается номер игрока, совершающего ход, и количество выпавших очков. Это будет единственная функция, которая будет менять данные в Таблице.
- Получение информации о суммарном количестве очков, выпваших за последний ход игрока, ходившего последним.
- Получение информации о суммарном количестве очков за все ходы какого-то конкретного игрока.

Т.е. примерно так:

Код:
class GameTable
{
    // ...
public:
    GameTable(unsigned int pq); // аргумент - кол-во игроков
    ~GameTable();
    // ...
    void writeRollResult(unisgned int palyerID, unsigned int points);
    unsigned int lastTurnPoints() const;
    unsigned int totalPlayerPoints(unsigned int playerID) const;
    // ...
};

Цитата: Kogrom
Или лучше сама Таблица в этом случае в результат хода запишет 0. И это тоже будет признак, что выпала единица.


Т.е. на таблицу, помимо обязанностей записи, хранения и выдачи информации о совершенных ходах, добавляется обязанность обеспечения контроля правила игры - если выпала единица, то очки сгорают. Не будет ли это поручением классу разнородных обязанностей, что, как я понимаю, является некоторым нарушением шаблона High Cohesion (Сильное зацепление)?

87
07 марта 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ghox
Для начала предлагаю такие функции:
- Запись информации о результате броска некоторым игроком. В вызове функции передается номер игрока, совершающего ход, и количество выпавших очков. Это будет единственная функция, которая будет менять данные в Таблице.
- Получение информации о суммарном количестве очков, выпваших за последний ход игрока, ходившего последним.
- Получение информации о суммарном количестве очков за все ходы какого-то конкретного игрока.

Т.е. примерно так:
Код:
class GameTable
{
    // ...
public:
    GameTable(unsigned int pq); // аргумент - кол-во игроков
    ~GameTable();
    // ...
    void writeRollResult(unisgned int palyerID, unsigned int points);
    unsigned int lastTurnPoints() const;
    unsigned int totalPlayerPoints(unsigned int playerID) const;
    // ...
};


Думаю, это будет нормально. Единственное, что меня смущает - в конструкторе передаётся количество игроков. Не пойму, зачем это нужно Таблице.

Цитата: Ghox
Т.е. на таблицу, помимо обязанностей записи, хранения и выдачи информации о совершенных ходах, добавляется обязанность обеспечения контроля правила игры - если выпала единица, то очки сгорают. Не будет ли это поручением классу разнородных обязанностей, что, как я понимаю, является некоторым нарушением шаблона High Cohesion (Сильное зацепление)?


Возможно. Но я думал с позиции шаблона Information Expert (Информационный эксперт). Думаю, он более приоритетный. Лучше, когда объект сам управляет своими данными. Хотя, возможно, что я ошибаюсь.

12K
13 марта 2010 года
Ghox
297 / / 26.07.2009
Цитата: Kogrom
Думаю, это будет нормально. Единственное, что меня смущает - в конструкторе передаётся количество игроков. Не пойму, зачем это нужно Таблице.


Ну, тут я уже начал думать о внутренней реализации Таблицы, и подумал что знать количество игроков таблице понадобится (или может быть понадобиться)... Хотя конечно не факт. Но думаю это не суть важно сейчас, какие аргументы должен иметь конструктор класса Таблица.

Цитата: Kogrom
Возможно. Но я думал с позиции шаблона Information Expert (Информационный эксперт). Думаю, он более приоритетный. Лучше, когда объект сам управляет своими данными. Хотя, возможно, что я ошибаюсь.


Тут меня смущает то, что таблица будет преобразовывать передаваемые в нее данные по некоему правилу, которое является внешним по отношению к устройству и назначению Таблицы. Мы в итоге получаем Таблицу, "заточенную" под какие-то конкретные правила, и для использования ее в каком-то другом варианте Игры в кости, с другими правилами, придется переписывать класс. Более того, представим ситуацию, когда в программе нужно поддержать несколько вариантов правил игры, и пользователь, перед тем как начать игру, имеет возможность выбрать, по каким правилам будет проходить игра. Для поддержки этого, в варианте когда "правило сгорания" очков добавлено в саму Таблицу, придется добавлять в Таблицу чужеродный для нее функционал. Мне кажется, удобно было бы иметь отдельный класс "Контролер", который будет хранить в себе правила игры, осуществлять контроль их выполнения, и проводить предобработку записываемой в Таблицу информации.

Но! Все это пока расчеты на будущее. С учетом того, что мы пока приняли значительные упрощения реализации программы (от класса Кость пока отказались, Логгер исключили, вместо гибкой и хитрой стратегии ботов пока взяли самую примитивную), думаю можно и здесь пока сделать упрощение, назначив контроль "правила сгорания" самой Таблице.

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

87
13 марта 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ghox
Мне кажется, удобно было бы иметь отдельный класс "Контролер", который будет хранить в себе правила игры, осуществлять контроль их выполнения, и проводить предобработку записываемой в Таблицу информации.

Но! Все это пока расчеты на будущее. С учетом того, что мы пока приняли значительные упрощения реализации программы (от класса Кость пока отказались, Логгер исключили, вместо гибкой и хитрой стратегии ботов пока взяли самую примитивную), думаю можно и здесь пока сделать упрощение, назначив контроль "правила сгорания" самой Таблице.



Наверное, это будет походить на что-то сложно осуществимое, но почему бы Ведущему просто не передать Таблице объект Правил Сгорания? Точнее, это должен быть объект, с помощью которого Таблица рассчитывает ячейку "Итого" за ход. Пусть Таблица и действует в соответствии с этим объектом.

Но пока это трудно представить. Пусть Таблица обходится без него для начала.

12K
20 марта 2010 года
Ghox
297 / / 26.07.2009
Опять долго не отвечал, на прошлых выходных не получилось, а в течение рабочей недели - опять на работе загруз...

Вариант 3, на основе варианта 2, с внесением обсужденных в предыдущих постах изменений.

Добавляется класс Таблица
функции:
запись информации о результате броска кости неким игроком
выдача информации о последнем броске кости
выдача информации о суммарном количестве очков последнего ходившего игрока за последний ход
выдача информации по текущему ходу - может ли игрок далее продолжать броски (контроль "правила сгорания")
выдача общего количества очков игрока за всю игру

Ведущий, теперь в такой реализации
переменные:
объект "Таблица"
контейнер Игроков
объект "Интерфейс"
функции:
функция, реализующая процесс игры

Игрок, представлен в двух видах:
Игрок-компьютер
Игрок-человек
переменные:
объект "Таблица", представляющий из себя тот же объект что и в Ведущем (реализован в виде указателя либо ссылки)
функция (для разных видов игроков - разные реализации):
совершение одного броска кости, возвращается результат - последний ли это бросок в рамках текущего хода, или нет.

Интерфейс
функции:
вывод сообщения в интерфейс
ввод информации из интерфейса

Цитата: Kogrom
Тут меня привлёк стиль изложения обязанностей классов. Но думаю, что надо сделать немного более абстрактно. По такому шаблону:


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

Цитата: Kogrom
Взаимодействующие (связанные) классы:

1. <Класс 1>
2. <Класс 2>
...
M. <Класс M>

N, M - не более семи. Лучше - не более пяти.


Если принять такое положение:

Цитата: Kogrom
Связанным классом будут считаться только те, о которых класс знает.


То в такой реализации, связанные классы будут иметься только для двух классов - Ведущий и Игрок:

Класс "Ведущий" связан с классами:
1. Таблица
2. Игрок
3. Интерфейс


Класс "Игрок":
1. связан с классом Таблица

Остальные два класса - "Интерфейс" и "Таблица" - ничего о других классах не "знают".

Процесс создания объектов:
Создается объект "Ведущий", при его создании создается объект
1. Создается объект "Таблица".
2. Создается массив объектов-игроков и заполняется объектами-игроками, первым игроком добавляется игрок-человек - остальные - игроки-боты. При инициализации каждого игрока, Ведущий передает как аргумент конструктора, свой объект "Таблицу".
3. Создается объект "Интерфейс"

Процесс игры, по следующему циклу.
1. Ведущий выбирает среди Игроков того, чъя очередь ходить, и выводит в Интерфейс что ходит такой-то игрок.
2. Для выбранного Игрока производит серию вызовов функции броска кости, цикл операций:
2.1. Вызов функции броска кости Игроком.
2.2. Игрок в функции броска, производит "бросок".
2.3. Результат броска Игрок передает в Таблицу - вызовом ее соотв. функции.
2.4. Таблица записывает результат, с учетом "правила сгорания".
2.5. Игрок запрашивает у Таблицы, может ли он продолжать броски:
2.5.1. Если Таблица возвращает, с учетом правила, что нет, то возвращает Ведущему результат что бросок последний.
2.5.2. Если таблица возвращает что можно еще делать броски, то принимает решение - стоит ли продолжать броски или надо остановиться. В случае Игрока-человека - идет запрос пользователю. При принятии решения Игрок может делать запросы к Таблице, для получения количества очков - своих и противников. Вариант своего решения Игрок передает Ведущему как результат выполнения функции.
2.6. После завершения хода, Ведущий делает запрос к Таблице и выводит информацию о последнем броске в Интерфейс.
2.7. Далее анализируется возвращенный результат функции - завершен ли ход или нет:
2.7.1. Если игрок вернул что броски будут продолжаться, то переход на начало цикла (п. 2.1).
2.7.2. Если Игрок вернул что ход завершен, то:
2.7.2.1. Вывод в Интерфейс соответствующей информации.
2.7.2.2. Запрос у Таблицы общего количества очков по последнему ходившему Игроку, и проверка - не стал ли Игрок победителем:
2.7.2.2.1. Если еще не стал - то передача хода другому Игроку - переход на начало цикла (п. 1).
2.7.2.2.2. Если стал победителем - то выдает соотв. сообщение в Интерфейс, игра завершается.

P.S. Как вариант, можно навесить на Таблицу еще и обязанность определения победителя. Если мы добавляем контроль одного из правил ("правила сгорания") в Таблицу, то может быть имеет смысл и другое правило туда же внести - чтобы консолидировать обязанности контроля правил в одном классе. Возможно, что в итоге потом будет проще ввести класс "Контролер".

87
20 марта 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ghox
Опять долго не отвечал, на прошлых выходных не получилось, а в течение рабочей недели - опять на работе загруз...


У меня не только работы прибавилось, но ещё и внезапно пришлось переезжать в другую квартиру. Так что понимаю.

Цитата: Ghox
P.S. Как вариант, можно навесить на Таблицу еще и обязанность определения победителя. Если мы добавляем контроль одного из правил ("правила сгорания") в Таблицу, то может быть имеет смысл и другое правило туда же внести - чтобы консолидировать обязанности контроля правил в одном классе. Возможно, что в итоге потом будет проще ввести класс "Контролер".



Я всё прочитал и особо возражать не буду, но предложу некоторый другой взгляд. Если оторваться от программирования и представить, что играют два человека (Игрока), а третий, Ведущий, как-то оглашает текущие результаты игры.

Для подсчета очков они используют таблицу в Excel. Таким образом автоматически рассчитывается количество набранных очков за все ходы и за один ход.

Вопросы:

1. Как она рассчитывает очки за все ходы? Учитывает ли очки, полученные в незавершенном ходе? Вроде так будет проще и логичнее. Или нет?
2. Как будет правильнее рассчитывать "итого" за определенный ход: заложить сложное правило, с учетом единицы, или тупо вручную стирать все очки в строке, когда выпадет единица? Или может Таблица будет тупо рассчитывать сумму, а потом умножать её на определенное число, заданное пользователем (0 или 1)?

Хранение правил можно поручить Игроку-предку. Теоретически.

12K
21 марта 2010 года
Ghox
297 / / 26.07.2009
Цитата: Kogrom
Я всё прочитал и особо возражать не буду, но предложу некоторый другой взгляд. Если оторваться от программирования и представить, что играют два человека (Игрока), а третий, Ведущий, как-то оглашает текущие результаты игры.

Для подсчета очков они используют таблицу в Excel. Таким образом автоматически рассчитывается количество набранных очков за все ходы и за один ход.

Вопросы:

1. Как она рассчитывает очки за все ходы? Учитывает ли очки, полученные в незавершенном ходе? Вроде так будет проще и логичнее. Или нет?


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

Цитата: Kogrom
2. Как будет правильнее рассчитывать "итого" за определенный ход: заложить сложное правило, с учетом единицы, или тупо вручную стирать все очки в строке, когда выпадет единица? Или может Таблица будет тупо рассчитывать сумму, а потом умножать её на определенное число, заданное пользователем (0 или 1)?


Думаю что нужно добавить в Таблицу, в состав хранимой информации о каждом ходе, специальный признак - учитывать ли очки данного хода при вычислении общей суммы очков, или нет. Этот признак Таблица будет использовать при подсчете очков. Полагаю что по умолчанию этот признак должен ставиться в значение "учитывать", и для сброса в значение "не учитывать" в Таблице должна быть специальная функция, доступная для вызова извне. И вызываться она должна именно только извне - со стороны какого-то внешнего (по отношению к основному функционалу Таблицы) кода. Правда, пока этот "внешний код", как я понял, будет все-таки внутри самой Таблицы.

Цитата: Kogrom
Хранение правил можно поручить Игроку-предку. Теоретически.


Вот тут не понял - что значит "игрок-предок"? Игрок, который совершает ход?

87
22 марта 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ghox
Но думаю, что в текущей реализации программы, востребованным будет только вариант учета всех очков, в том числе и за незавершенный ход.


Хорошо. Так и сделаем.

Цитата: Ghox
Думаю что нужно добавить в Таблицу, в состав хранимой информации о каждом ходе, специальный признак - учитывать ли очки данного хода при вычислении общей суммы очков, или нет. Этот признак Таблица будет использовать при подсчете очков. Полагаю что по умолчанию этот признак должен ставиться в значение "учитывать", и для сброса в значение "не учитывать" в Таблице должна быть специальная функция, доступная для вызова извне. И вызываться она должна именно только извне - со стороны какого-то внешнего (по отношению к основному функционалу Таблицы) кода. Правда, пока этот "внешний код", как я понял, будет все-таки внутри самой Таблицы.



Ранее я говорил, что в таблицу можно ввести правило расчета "итого". Теперь правило сократилось до признака. Думаю, такой признак удобнее вносить внешним кодом. Например, с помощью игрока.

Цитата: Ghox
Вот тут не понял - что значит "игрок-предок"?


У классов игрока-компьютера и игрока-человека есть класс-родитель, содержащий в себе все общие функции. Я говорил про него.

Думаю, что дальше рассуждать смысла мало - надо сделать новую версию кода.

12K
27 марта 2010 года
Ghox
297 / / 26.07.2009
Цитата: Kogrom
Ранее я говорил, что в таблицу можно ввести правило расчета "итого". Теперь правило сократилось до признака. Думаю, такой признак удобнее вносить внешним кодом. Например, с помощью игрока.

Думаю, что дальше рассуждать смысла мало - надо сделать новую версию кода.


Поскольку аргументы Kogrom'а насчет необходимости написания сейчас (на этапе проектирования) работающего кода показались мне субъективными и неубедительными, решил на этот раз полностью рабочий код не делать, сделать, как и хотел ранее, набросок поясняющий схему.

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

Ну и еще: в итоге контроль правил ("правило сгорания" и определение победителя) сейчас внедрен в Таблицу. То, чтобы признак учета / неучета очков выставлять внешней по отношению к таблице функцией - пока решил не делать.

Код:
#include <ctime>
#include <string>
#include <sstream>
#include <iostream>
using namespace std;

// Интерфейс:
class Interface
{
public:
    Interface() {}
    ~Interface() {}
    void out(const string&);
    char in();
    static bool getConfirmation();
};

// определения функций класса Interface

// Таблица
class GameTable
{
    static const unsigned int maxPoints = 20;
    // приватные члены
public:
    GameTable();
    ~GameTable();
    void writeRollResult(unsigned int playerID, unsigned int points);
    unsigned int lastRollResult() const;
    unsigned int lastTurnPoints() const;
    bool canContinue() const;
    unsigned int totalPlayerPoints(unsigned int playerID) const;
    bool isWinner(unsigned int playerID) const;
};

// определения функций класса GameTable

// Игрок (базовый класс):
class Player
{
protected:
    GameTable *table;
    const unsigned int totalPlayersQuantity;
    const unsigned int playerID;
    // ...
    bool rollDice();
public:
    Player(unsigned int ID, unsigned int tpq, GameTable *tbl);
    virtual ~Player() {}
    virtual bool makeRoll() = 0;
};

Player::Player(unsigned int ID, unsigned int tpq, GameTable *tbl) :
            playerID(ID), totalPlayersQuantity(tpq), table(tbl) { /* */ }

bool Player::rollDice()
{
    srand((unsigned int)time(0));
    table->writeRollResult(playerID, (rand() % 6 + 1));
    return table->canContinue();
}

// Игрок-компьютер
class RobotPlayer : public Player
{
    static const unsigned int desiredTurnPoints = 5;
public:
    RobotPlayer(unsigned int ID, unsigned int tpq, GameTable *tbl) :
            Player(ID, tpq, tbl) {}
    virtual ~RobotPlayer() {}
    virtual bool makeRoll();
};

bool RobotPlayer::makeRoll()
{
    return rollDice()
            && !(table->isWinner(playerID))
            && table->lastTurnPoints() < desiredTurnPoints;
}

// Игрок-человек
class HumanPlayer : public Player
{
    bool (*confirmFunc)();
public:
    HumanPlayer(unsigned int playerID, unsigned int tpq, GameTable *tbl, bool (*func)()) :
                Player(ID, tpq, tbl), confirmFunc(func) {}
    virtual ~HumanPlayer() {}
    virtual bool makeRoll();
};

bool HumanPlayer::makeRoll()
{
    return rollDice()
            && !(table->isWinner(playerID))
            && confirmFunc();
}

// Ведущий:
class GameField
{
    Interface *iFace;
    const unsigned int playersQuantity;
    Player **players;
    GameTable table;
public:
    GameField(unsigned int = 2);
    ~GameField();
    void play();
};

GameField::GameField(unsigned int pq) : playersQuantity(pq)
{
    iFace = new Interface;
    players = new Player*[playersQuantity];
    players[0] = new HumanPlayer(0, playersQuantity, &table, Interface::getConfirmation);
    for(unsigned int i = 1; i < playersQuantity; ++i)
        players = new RobotPlayer(i, playersQuantity, &table);
}

GameField::~GameField()
{
    for(unsigned int i = 0; i < playersQuantity; ++i)
        delete players;
    delete[] players;
    delete iFace;
}

void GameField::play()
{
    bool hasWinner = false;
    unsigned int currentPlayer = 0;
    while(!hasWinner)
    {
        // здесь определение значения currentPlayer

        stringstream msgString;
        msgString << "Player # " << i << " turn:\n";
        iFace->out(msgString.str());

        bool stop = false;
        while(!stop)
        {
             stop = players[currentPlayer]->makeRoll();
             stringstream msgString2;
             msgString << "Player # " << ID << " rolls " << table.lastRollResult() << " points\n";
             iFace->out(msgString.str());      
        }

        stringstream msgString2;
        msgString2 << "Total " << table.totalPlayerPoints(currentPlayer) << " points\n";
        iFace->out(msgString2.str());

        hasWinner = table.isWinner(currentPlayer);
        _sleep(500);
    }
    stringstream msgString;
    msgString << "Player # " << currentPlayer << " wins\n";
    iFace->out(msgString.str());
    iFace->out(string("Game over\n"));
}

int main()
{
    GameField Game;
    Game.play();
}
87
29 марта 2010 года
Kogrom
2.7K / / 02.02.2008
Извиняюсь за то, что не ответил сразу - не было доступа в Интернет.
Цитата: Ghox
Поскольку аргументы Kogrom'а насчет необходимости написания сейчас (на этапе проектирования) работающего кода показались мне субъективными и неубедительными, решил на этот раз полностью рабочий код не делать, сделать, как и хотел ранее, набросок поясняющий схему.

При написании данного наброска, пришел к выводу, что будет все-таки логичней определять победителя в Таблице.



С одной стороны, мои аргументы неубедительны, с другой стороны, пока писал код пришёл к выводу...

Чтобы сделать другие выводы, надо попытаться написать код Таблицы. Кроме того, надо что-то делать с GameField:: play(). Он должен выглядеть как-то так:

Код:
void GameField::play()
{
    bool hasWinner = false;
    unsigned int currentPlayer = 0;
    while(!hasWinner)
    {
        hasWinner = this->MakeTurn(&currentPlayer);
    }
    this->PrintWinner(currentPlayer);
    this->AskEndGame();
}

Правил грубо, желая показать только размер текста функции.

В остальном, если не обращать внимания на мелочи типа "магической цифры" 6, то мне нравится то, что вышло. Ну и я предпочитаю контейнеры STL, а не динамически создаваемые массивы. С ними как-то спокойнее. Да и добавить компьютерных игроков будет проще.
12K
29 марта 2010 года
Ghox
297 / / 26.07.2009
Цитата: Kogrom
С одной стороны, мои аргументы неубедительны, с другой стороны, пока писал код пришёл к выводу...


Похоже что ты меня неправильно понял... Я не имел в виду, что вообще писать какой-то код не имеет смысла. Я имел в виду - не имеет смысла делать полностью работающий код (я даже специально выделял это слово, выделил и еще раз). Достаточно сделать набросок, в котором присутствует все, что относится к взаимодействию классов в процессе игры, все остальное - пока не делать. Причем, как видишь, чтобы сделать тот вывод (то что ты пишешь "с другой стороны, пока писал код пришёл к выводу..."), мне оказалось вполне достаточно этого наброска.

Добиваться чтобы программа была работающей - я считаю что это сейчас бесполезно затраченные время и усилия, т.к. придется писать внутреннее устройство классов (иначе не будет работать программа). Что пока преждевременно, все равно все может сильно поменяться и код тогда будет выброшен как ненужный.

Твои аргументы насчет необходимости писать именно работающий код, а не набросок:

Цитата: Kogrom
Чисто психологически приятнее иметь какой-то работающий код. Честно говоря, я не компилировал второй вариант, но теоретически уже можно играть. И это должно радовать разработчиков, вселять уверенность, что всё получится.


Довольно-таки субъективно и неубедительно. Иметь работающий код сейчас: что приятно - может быть, но что полезно - нет. :)

Цитата: Kogrom
Чтобы сделать другие выводы, надо попытаться написать код Таблицы. Кроме того, надо что-то делать с GameField:: play(). Он должен выглядеть как-то так:

Правил грубо, желая показать только размер текста функции.

В остальном, если не обращать внимания на мелочи типа "магической цифры" 6, то мне нравится то, что вышло. Ну и я предпочитаю контейнеры STL, а не динамически создаваемые массивы. С ними как-то спокойнее. Да и добавить компьютерных игроков будет проще.


Эти замечания уже по внутреннему устройству класса Ведущий, устранение их ничего во взаимодействии классов не изменит. Думаю надо это потом разбирать, после завершения этапа проектирования классов и связей между ними.

И вообще я предпочел бы, чтобы работа проекта велась примерно по той схеме как я предлагал: сначала - этап проектирования общей архитектуры, потом - проработка отдельных классов. Т.е. не пытаться параллельно и схему проектировать и классы сразу же кодировать. Такой подход хорош хотя бы тем, что можно будет выделить отдельные задачи на проработку классов. Какими-нибудь из которых, может быть, захотят заняться другие участники, желающие попрактиковаться в разработке на C++ и поучаствовать в проекте. Просто сейчас другие участники (koodeer например), видимо, боятся соваться в эту тему, видя масштабы наших "дискуссий". А когда будут выделены отдельные шаги на проработку каждого класса, которые по силам и неопытному участнику - может и присоединится кто... Хотя не факт конечно... Но думаю, что если мы будем сразу же сами же и классы кодить, то так и останемся единственными участниками проекта. Что на какой-то реальный проект как-то не похоже, а похоже на разработку программы участником Ghox'ом под наставничеством участника Kogrom'а. :)

Так что предлагаю завершать довольно таки затянувшийся (по моей вине конечно же) этап проектирования, приняв полученную схему как более-менее окончательную, и начинать проектировать / кодировать отдельные классы, выделяя для этого отдельные шаги. Для завершения проектирования, могу, для большей ясности, попробовать все-таки и графическую схему нарисовать.

87
29 марта 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ghox
Добиваться чтобы программа была работающей - я считаю что это сейчас бесполезно затраченные время и усилия, т.к. придется писать внутреннее устройство классов (иначе не будет работать программа).


Не должно быть работающей программы во всех деталях. Должен быть работающий макет.

А если нужен набросок, то он и должен быть наброском, который по крупному показывает структуру. То есть, тогда не надо показывать в деталях реализацию функций.

Цитата: Ghox
И вообще я предпочел бы, чтобы работа проекта велась примерно по той схеме как я предлагал: сначала - этап проектирования общей архитектуры, потом - проработка отдельных классов.



Тут велик риск, что когда мы начнём писать код, то всю архитектуру придется переписывать сначала, что она будет трудна в реализации. Поэтому нужны "разведки в код", какие-то эксперименты, какие-то дешёвые модели.

Да и вообще, лучше почитать чем плоха модель водопада.

Цитата: Ghox
Просто сейчас другие участники (koodeer например), видимо, боятся соваться в эту тему, видя масштабы наших "дискуссий". А когда будут выделены отдельные шаги на проработку каждого класса, которые по силам и неопытному участнику - может и присоединится кто... Хотя не факт конечно...


Я думаю, что других участников нет.

Цитата: Ghox
на какой-то реальный проект как-то не похоже, а похоже на разработку программы участником Ghox'ом под наставничеством участника Kogrom'а. :)


Так почти и есть. Только я не наставник, а советник. Код не пишу, потому что koodeer попросил. Хотя, наверное, надо писать код, который я не буду выкладывать (чтобы лучше понимать процесс).

Цитата: Ghox
Так что предлагаю завершать довольно таки затянувшийся (по моей вине конечно же) этап проектирования, приняв полученную схему как более-менее окончательную, и начинать проектировать / кодировать отдельные классы, выделяя для этого отдельные шаги. Для завершения проектирования, могу, для большей ясности, попробовать все-таки и графическую схему нарисовать.


Ок.

Основной вопрос, который я услышал: "А где же другие участники?" Думаю, для них нет ролей. Например, есть участник форума Нездешний, который может работать в таких проектах. Но он, вероятно, подключится, только если ему в личку написать что-то умное (предложить какую-то интересную роль). И так же с остальными: только через личные сообщения, только называя по именам, только предлагая конкретные роли. То есть надо что-то выдумывать, какие-то роли.

Мне видятся всякие фантастические роли: типа написания варианта с GUI или для сети, или на другом языке. Оно не очень вкладывается в модель от Ghox, но может кого-то заинтересовать, так как при этом участник будет на равных.

535
02 апреля 2010 года
Нездешний
537 / / 17.01.2008
[QUOTE="Korgom"]Но он, вероятно, подключится, только если ему в личку написать что-то умное[/QUOTE]Вероятно, так. А он до вчерашнего дня не знал о существовании этой темы ;)
Дел полно, на форуме бываю в последнее время редко и далеко не во все темы заглядываю. Так что действительно лучше о чем-то таком в личку или в аську

Постараюсь отслеживать тему. Вот вам вариантик начерно :D
Код:
#include <iostream>
#include <stdlib.h>
#include <vector>
#include <numeric> //for accumulate algorithm
#include <string>
#include <sstream>

//---------- interfaces ------------------
class IDice
{
public:
    virtual unsigned int Throw() const = 0;
};

class IStrategy
{
public:
    virtual bool NeedNextThrow(
        const unsigned int MyPoints,
        const std::vector<unsigned int> vOpponentsPoints,
        const std::vector<unsigned int> vPrevThrows
        ) = 0;
};

class IPlayer
{
public:
    virtual unsigned int GetPoints() const = 0;
    virtual void Turn(const std::vector<unsigned int> vOpponentsPoints) = 0;
};

//IView
typedef void (*PFuncOutData) (const std::string s);
typedef bool (*PFuncGetDecision) ();

//---------- implementations ----------------
template <unsigned int CountFacets>
class TDice: public IDice
{
public:
    TDice(): IDice()
    {
        srand( (unsigned)time(NULL) );
    }

    unsigned int Throw() const
    {
        return rand() % CountFacets[COLOR="Red"] + 1[/COLOR];
    }
};

class TAutoStrategy: public IStrategy
{
public:
    bool NeedNextThrow(
        const unsigned int MyPoints,
        const std::vector<unsigned int> vOpponentsPoints,
        const std::vector<unsigned int> vPrevThrows
        )
    {
        if (vPrevThrows.size() > 3) return false;
        return true;
    }
};

template <PFuncGetDecision pfGetDecision>
class THumanStrategy: public IStrategy
{
public:
    bool NeedNextThrow(
        const unsigned int MyPoints,
        const std::vector<unsigned int> vOpponentsPoints,
        const std::vector<unsigned int> vPrevThrows
        )
    {
        return pfGetDecision();
    }
};

template <typename StrategyTrait, typename DiceTrait>
class TPlayer: public IPlayer
{
    StrategyTrait Strategy;
    DiceTrait Dice;
    unsigned int Points;
    std::vector<unsigned int> vPrevThrows;
public:
    TPlayer(): Points(0) {}

    unsigned int GetPoints() const
    {
        return Points;
    }

    void Turn(const std::vector<unsigned int> vOpponentsPoints)
    {
        while (Strategy.NeedNextThrow(Points, vOpponentsPoints, vPrevThrows))
        {
            int curPoint = Dice.Throw();
            if ([COLOR="red"]1[/COLOR] == curPoint) return;

            vPrevThrows.push_back(curPoint);
        }

        Points += accumulate(vPrevThrows.begin(), vPrevThrows.end(), 0);
        vPrevThrows.clear();
    }
};

template <PFuncOutData pfOutData>
class TGame
{
    std::vector<IPlayer*> vPlayers;
public:
    void AddPlayer(IPlayer *pPlayer)
    {
        vPlayers.push_back(pPlayer);
    }

    IPlayer* Process()
    {
        //who throws first?
        srand( (unsigned) time (NULL) );
        unsigned int curIndex = rand() % vPlayers.size();

        //game processing
        std::vector<unsigned int> vOpponentsPoints(0);
        while (true)
        {
            vPlayers[curIndex]->Turn(vOpponentsPoints);
            unsigned int curPoints = vPlayers[curIndex]->GetPoints();

            std::ostringstream sout;
            sout << "Player " << curIndex << ", points " << curPoints << "\n";
            pfOutData(sout.str());

            if (curPoints >= 100)   return vPlayers[curIndex];
            if (++curIndex >= vPlayers.size())  curIndex = 0;
        }
    }
};

using namespace std;

//view functions
bool GetDecision()
{
    cout << "Next throw? (press 'y' for yes):";
    char cAnswer;
    cin >> cAnswer;
    if (cAnswer == 'y') return true;
    return false;
}

void OutData(const string s)
{
    cout << s;
}

int main()
{
    TPlayer<TAutoStrategy, TDice<6> > Player1;
    TPlayer<THumanStrategy<GetDecision>, TDice<6> > Player2;

    TGame<OutData> Game;
    Game.AddPlayer(&Player1);
    Game.AddPlayer(&Player2);
    Game.Process();

    return 0;
}
87
02 апреля 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Нездешний
Вероятно, так. А он до вчерашнего дня не знал о существовании этой темы



Хорошего человека и звать не надо - сам приходит. Или это Ghox позвал?

Цитата: Нездешний
Постараюсь отслеживать тему.



Хорошо. То есть в теме опять 3 человека (koodeer давно не отмечается - значит потерял интерес). Надо будет роли придумать.

Пока вижу следующие роли:
1. Программист.
2. Советчик.
3. Заказчик.

Программисты обсуждают ООП-шную структуру программы, выдумывают взаимодействие объектов, классов, создают код на C++, комментируют проектирование и код других программистов. Кстати, такое комментирование было бы очень желательным.

Советчики комментируют, советуют, могут немного проектировать, но сами код не пишут. Могут лишь показывать как они бы корректировали некоторые функции, классы.

Заказчик следит, чтобы код, проект не слишком отходил от ТЗ. С ним согласовываются такие отхождения. Кроме того, он может выдумывать усовершенствования. Заказчик один.

То есть я тут выполняю роли заказчика и советчика (хотя могу и роль программиста). Другим, вероятно, будет интереснее выполнять роль программиста.

Хотелось бы узнать, кто что думает по поводу таких ролей.

Цитата: Нездешний
Вот вам вариантик начерно :D


Во первых, я не увидел, где будет учитываться единичка. Возможно, в IStrategy, её потомках (индекс "I" значит интерфейс, значит в предках?).

Не совсем понял, почему был выбран полиморфизм на шаблонах для игроков. Может оно и лучше, гибче, но добавляет связь с классом стратегий.

И опять же, везде этот злосчастный класс "Кость" из одной функции. Я не утверждаю, что он не понадобится позднее, но сейчас он затрудняет восприятие, добавляет ненужных связей между классами.

Мы начали придумывать, как лучше приделать к этому лог. Пока было принято решение, что будем использовать таблицу в памяти, которую потом, при желании, можно будет выгрузить в файл. Эта же таблица будет позволять игрокам просматривать все данные о ходах игры. В варианте от Нездешнего вся эта таблица (в упрощенном виде) многократно копируется функцией IStrategy:: NeedNextThrow. Понятно, что это легко разрешить с помощью ссылок, но оно ещё и восприятие усложняет.

В целом код красивый. Использование STL и шаблонов радует.

535
02 апреля 2010 года
Нездешний
537 / / 17.01.2008
[QUOTE=Kogrom]Во первых, я не увидел, где будет учитываться единичка.[/QUOTE]Это ты о чем?

[QUOTE=Kogrom]почему был выбран полиморфизм на шаблонах для игроков[/QUOTE]Исключительно из-за того, чтобы не ограничиваться двумя игроками (вдруг взбрело в голову :) ). В моем варианте их сейчас может быть сколько угодно. Если ограничиться двумя - полиморфизм не нужен

[QUOTE=Kogrom]злосчастный класс "Кость" из одной функции[/QUOTE]Из-за инициализации srand в конструкторе

UPD.
[QUOTE=Kogrom]злосчастный класс "Кость"[/QUOTE]И вообще, самое злосчастное во всем этом, что стратегия поведения человека-игрока требует связи со View - вот оно, злодейство где!
87
02 апреля 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Нездешний
Это ты о чем?


ТЗ:
http://forum.codenet.ru/showpost.php?p=307932&postcount=8
Смысл в том, что если у игрока выпадает единица, то очки за текущий ход сгорают и начитает ходить другой игрок.


Цитата: Нездешний
Исключительно из-за того, чтобы не ограничиваться двумя игроками (вдруг взбрело в голову :) ).


Игроков и у нас может быть много. Но у нас только 2 типа. В принципе, и тут 2 типа, но добавить новый проще.

Цитата: Нездешний
Из-за инициализации srand в конструкторе


А почему оно требуется именно тут, в конструкторе кости?

Цитата: Нездешний
UPD.
И вообще, самое злосчастное во всем этом, что стратегия поведения человека-игрока требует связи со View - вот оно, злодейство где!


Ну так ты же решил это с помощью функции, как и мы. Хотя гибче было бы использовать специальный объект (функтор или что-то типа того). Но это на будущее.

535
02 апреля 2010 года
Нездешний
537 / / 17.01.2008
[QUOTE=Kogrom]Смысл в том, что если у игрока выпадает единица, то очки за текущий ход сгорают и начитает ходить другой игрок[/QUOTE]А, ну да, а я вместо нее ноль написал. Сейчас поправлю

[QUOTE=Kogrom]А почему оно требуется именно тут, в конструкторе кости?[/QUOTE]А потому что и rand требуется в основном для этой самой кости. Логично это убрать с глаз долой, чтоб больше никто не знал о внутренней реализации этой самой кости, и не заморачивался, нужно ли перед использованием кости что-то делать
87
02 апреля 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Нездешний
А потому что и rand требуется в основном для этой самой кости. Логично это убрать с глаз долой, чтоб больше никто не знал о внутренней реализации этой самой кости, и не заморачивался, нужно ли перед использованием кости что-то делать



Она (srand) ещё используется и для выбора игрока, который будет первым ходить. Плюс к тому, она будет выполнятся для каждой кости (которых зачем-то две... А, понял, чтобы игроки могли играть с костями, у которых разное число граней). Проще уж в TGame её засунуть.

Добавлено позже:

Скорее всего, у меня не будет возможности зайти на форум в эти выходные. Но я приглашаю в качестве советчиков сюда участников форума Proger_XP и oltzowwa, если им интересно, если есть идеи.

87
09 апреля 2010 года
Kogrom
2.7K / / 02.02.2008
Опять что-то сломалось в организации проекта...
Цитата: Ghox
И вообще я предпочел бы, чтобы работа проекта велась примерно по той схеме как я предлагал: сначала - этап проектирования общей архитектуры, потом - проработка отдельных классов. Т.е. не пытаться параллельно и схему проектировать и классы сразу же кодировать. Такой подход хорош хотя бы тем, что можно будет выделить отдельные задачи на проработку классов. Какими-нибудь из которых, может быть, захотят заняться другие участники, желающие попрактиковаться в разработке на C++ и поучаствовать в проекте.



Ок. Попробую выполнить роль участника, который прорабатывает отдельный компонент. Пусть это будет Таблица.

Как говорилось выше, у нас таблица будет определять выигрывающего и признак сброса суммы бросков за ход. С другой стороны, это будет слишком большая зависимость Таблицы от меняющихся правил игры. Поэтому сделаем независимую Таблицу и наследованную от неё Игровую Таблицу (этого наследника можно будет потом распотрошить и выкинуть, если будет необходимость).

Таблица будет содержать объекты - Страницы с данными. Каждая из Страниц будет ассоциирована с определенным игроком через идентификатор.

Страница будет содержать строки. Строки будут делиться на обычные и Главную строку. В строках будут ячейки, содержащие данные о бросках кости и одна ячейка для суммы очков за ход. Так же будет и в Главной Строке, но в её ячейках будут данные об очках за весь ход, и ячейка с суммой очков за всю игру. Проще говоря, в строке будет массив + одно данное целого типа.

Для каждой строки будет сделана функция суммирования с флагом: если флаг установлен, то производится суммирование, иначе возвращается 0.

Для Главной Строки будет введена дополнительная функция: установка ячейки, так как её последняя ячейка будет меняться при каждом броске кости.

В Странице должны быть функции добавления новой строки и добавления нового данного (очков).

В Таблице должны быть функции, для добавления страницы, получения копии (или константной ссылки) страницы с определенный идентификатором, для получения общей суммы очков для определенный страницы.

Игровая Таблица будет добавлять константы содержащие 1 и 100, то есть результат для сброса суммы хода и число очков для выигрыша. Она будет содержать функции, выдающие флаг сброса суммы и признак выигрыша.

Вышло много букв... Если что, постараюсь как-нибудь изобразить всё это. Пока что прикреплю код с моделью на Python, в которой я реализовал такую таблицу (играют 3 компьютерных игрока). Прикрепляю её не для других участников, но чтобы не потерять :)

12K
10 апреля 2010 года
Ghox
297 / / 26.07.2009
Схему нарисовать все так и не нашел времени - надо полистать книгу по UML чтобы посмотреть как эти схемы рисуются...

Были мысли, чтобы высказать свои возражения по одного из предыдущих постов Kogrom'а (пост 71 от 29.03.2010, 17:16) - по поводу все того же вопроса по поводу необходимости создания работающей программы на этапе проектирования... Но наверно пока не буду. Думаю что чтобы здесь что-то дальше обсуждать и возражать, мне надо ознакомится с теорией, хотя бы вот с этим:
Цитата: Kogrom
Да и вообще, лучше почитать чем плоха модель водопада.


По поводу участия в проекте участника Нездешний.

Цитата: Kogrom
Хорошего человека и звать не надо - сам приходит. Или это Ghox позвал?


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

По поводу ролей. Прежде всего - мне как-то сомнительно что у нас получится вообще какие-то иные роли выделить, кроме как оппозиции "Заказчик" - "Исполнители". Чтобы детализировать исполнителей, надо тогда достаточно хорошо детализировать что именно от них ожидается...

Цитата: Kogrom
Программисты обсуждают ООП-шную структуру программы, выдумывают взаимодействие объектов, классов, создают код на C++, комментируют проектирование и код других программистов. Кстати, такое комментирование было бы очень желательным.


По-моему, слишком общее описание роли. Должен ли участник на роли "Программист" выполнять (или пытаться выполнять) все эти действия - и обсуждать структуру программы в соответствии с принципами ООП, и разрабатывать взаимодействие классов, и код писать, и т.д.? Или достаточно делать только часть этого, например - что если кто-нибудь скажет "я согласен писать код для отдельных частных задач данного проекта (например, написать такой-то класс), но не желаю участвовать в проектировании и остальном"?

И еще - участники Программисты должны придерживаться некоей одной "официальной" линии работы над проектом, согласовываемой с Заказчиком, или все или часть программистов могут делать по своему - свое проектирование, свое кодирование (или даже сразу кодирование без какого-либо проектирования)? Как например вышло с участником Нездешний, который сделал вариант по какой-то своей схеме?

Цитата: Kogrom
Советчики комментируют, советуют, могут немного проектировать, но сами код не пишут. Могут лишь показывать как они бы корректировали некоторые функции, классы.


То что говорят советчики - носит рекомендательный характер, или обязательный? При возникновении разногласий между советчиком и программистом - обязательно ли в итоге должен быть достигнут консенсус, или допустимо что оба останутся при своем мнении?

Теперь - насчет начала разработки отдельных компонентов. Как я понимаю, Kogrom взял себе задачу разработки класса Таблица. Не буду возражать против такого назначения, и не буду пытаться критиковать его вариант, хотя я наверно сделал бы по-другому.

По нашей схеме разработки, остались еще три класса: Ведущий, Игрок (здесь имеется в виду обобщенный игрок), Интерфейс. Предлагаю кому-нибудь из желающих потенциальных участников (например, упоминавшиися Kogrom'ом участникам Proger_XP и oltzowwa - он видимо знаком с ними и считает что они могли бы поучаствовать; но думаю и другие желающие могут присоединяться) взять себе какой-нибудь из этих классов, на проектирование и разработку, или хотя бы на проектирование (кодирование оставить на потом или оставить другим). Правда, Интерфейс похоже будет очень простой класс, хотя при желании можно сделать из него что-то более солидное - например, вместо простецкого консольного прикрутить что-нибудь из Windows. Но правда я сам сделать этого уже не смогу - как сделать это - не знаю, да и изучать, честно говоря, пока нет желания (работа с интерфейсами для меня никакого интереса не представляет, по крайней мере сейчас).

Если желающих взяться за какие-либо классы не будет, то согласен сам проработать все классы (хотя Kogrom также может взять что-то себе - хотя бы тот же Интерфейс).

535
12 апреля 2010 года
Нездешний
537 / / 17.01.2008
[QUOTE=Ghox]Участник Нездешний пошел не по той линии проектирования, согласно которой мы работали в данной теме[/QUOTE]Ага, участник Нездешний отклонился от линии партии :D В частности потому, что линию партии в данном треде отыскать весьма тяжело. "Многабукаф", "ниасилил" и все такое :) . Ткните носом в описание обязанностей и взаимодействия классов или в картинку какую что ли.

Стремление "и тебя сосчитать" - всех распределить по заказчикам-советникам-программистам-исполнителям-надсмотрщикам-ипрочимтипам тоже несколько пугает :)

[QUOTE=Kogrom]Мы начали придумывать, как лучше приделать к этому лог[/QUOTE]А чем он так уж концептуально отличается от вывода в вид?

В общем, пока линия партии меня еще не поймала, напишу еще вариант, с логом и классом вида вместо отдельных функций. Вместо указателя на функцию получения решения от пользователя использовал делегат. Реализация делегатов (до трех параметров - дальше влом было писать, кто захочет - сделает) - во вложении.
Код:
#include <iostream>
#include <stdlib.h>
#include <vector>
#include <numeric> //for accumulate algorithm
#include <string>
#include <sstream>
#include <fstream>

#include "Delegate.h"

#define NULL_THROW 1
#define LOG_FILENAME "game.log"

//---------- interfaces ------------------
class IDice
{
public:
    virtual unsigned int Throw() const = 0;
};

class IStrategy
{
public:
    virtual bool NeedNextThrow(
        const unsigned int MyPoints,
        const std::vector<unsigned int> vOpponentsPoints,
        const std::vector<unsigned int> vPrevThrows
        ) = 0;
};

class IPlayer
{
public:
    virtual unsigned int GetPoints() const = 0;
    virtual void Turn(const std::vector<unsigned int> vOpponentsPoints) = 0;
};

class IView
{
public:
    virtual void OutString(const std::string s) = 0;
    virtual bool GetUserDecision() = 0;
};

class ILog
{
public:
    virtual void AddLogString(const std::string s) = 0;
};

//---------- implementations ----------------
template <unsigned int AmountFacets>
class TDice: public IDice
{
public:
    TDice(): IDice()
    {
        srand( (unsigned)time(NULL) );
    }

    unsigned int Throw() const
    {
        return rand() % AmountFacets + 1;
    }
};

class TAutoStrategy: public IStrategy
{
public:
    bool NeedNextThrow(
        const unsigned int MyPoints,
        const std::vector<unsigned int> vOpponentsPoints,
        const std::vector<unsigned int> vPrevThrows
        )
    {
        if (vPrevThrows.size() > 3) return false;
        return true;
    }
};

class THumanStrategy: public IStrategy
{
public:
    Delegate<bool> OnGetDecision;

    bool NeedNextThrow(
        const unsigned int MyPoints,
        const std::vector<unsigned int> vOpponentsPoints,
        const std::vector<unsigned int> vPrevThrows
        )
    {
        return OnGetDecision();
    }
};

class TPlayer: public IPlayer
{
    IStrategy *pStrategy;
    IDice *pDice;
    unsigned int Points;
    std::vector<unsigned int> vPrevThrows;
public:
    TPlayer(IStrategy *Strategy, IDice *Dice):
        pStrategy(Strategy), pDice(Dice), Points(0) {}

    virtual ~TPlayer() {}

    unsigned int GetPoints() const
    {
        return Points;
    }

    void Turn(const std::vector<unsigned int> vOpponentsPoints)
    {
        while (pStrategy->NeedNextThrow(Points, vOpponentsPoints, vPrevThrows))
        {
            int curPoint = pDice->Throw();
            if (NULL_THROW == curPoint) return;

            vPrevThrows.push_back(curPoint);
        }

        Points += accumulate(vPrevThrows.begin(), vPrevThrows.end(), 0);
        vPrevThrows.clear();
    }
};

class TGame
{
    std::vector<IPlayer*> vPlayers;
    std::vector<IView*> vViews;
    std::vector<ILog*> vLogs;
public:
    void AddPlayer(IPlayer *pPlayer)
    {
        vPlayers.push_back(pPlayer);
    }

    void AddView(IView *pView)
    {
        vViews.push_back(pView);
    }

    void AddLog(ILog *pLog)
    {
        vLogs.push_back(pLog);
    }

    IPlayer* Process()
    {
        //who throws first?
        srand( (unsigned) time (NULL) );
        unsigned int curIndex = rand() % vPlayers.size();

        //game processing
        std::vector<unsigned int> vOpponentsPoints(0);
        while (true)
        {
            vPlayers[curIndex]->Turn(vOpponentsPoints);
            unsigned int curPoints = vPlayers[curIndex]->GetPoints();

            std::ostringstream sout;
            sout << "Player " << curIndex << ", points " << curPoints << "\n";
            for (unsigned i = 0; i < vLogs.size(); i++) vLogs->AddLogString(sout.str());
            for (unsigned i = 0; i < vViews.size(); i++)    vViews->OutString(sout.str());

            if (curPoints >= 100)   return vPlayers[curIndex];
            if (++curIndex >= vPlayers.size())  curIndex = 0;
        }
    }
};

class TView: public IView
{
public:
    virtual void OutString(const std::string s)
    {
        std::cout << s;
    }

    virtual bool GetUserDecision()
    {
        std::cout << "Next throw? (press 'y' for yes):";
        char cAnswer;
        std::cin >> cAnswer;
        if (cAnswer == 'y') return true;
        return false;
    }
};

class TLog: public ILog
{
    std::ofstream fout;
public:
    TLog(const std::string sLogFileName)
    {
        fout.open(sLogFileName.c_str());
    }

    virtual ~TLog()
    {
        fout.close();
    }

    virtual void AddLogString(const std::string s)
    {
        if (fout.good())    fout << s;
    }
};

using namespace std;

int main()
{
    TView View;
    TLog Log(LOG_FILENAME);

    TDice<6> Dice;

    TAutoStrategy AutoStrategy;
    THumanStrategy HumanStrategy;
    HumanStrategy.OnGetDecision = MakeDelegate<TView, bool>(&View, &TView::GetUserDecision);

    TPlayer Player0(&AutoStrategy, &Dice), Player1(&HumanStrategy, &Dice);

    TGame Game;
    Game.AddView(&View);
    Game.AddLog(&Log);
    Game.AddPlayer(&Player0);
    Game.AddPlayer(&Player1);
    Game.Process();

    return 0;
}

}
87
12 апреля 2010 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ghox
По поводу ролей.



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

Цитата: Ghox
При возникновении разногласий между советчиком и программистом - обязательно ли в итоге должен быть достигнут консенсус, или допустимо что оба останутся при своем мнении?


Естественно, что последнее слово по коду остаётся за программистом. Главное - чтобы можно было играть в соответствии с ТЗ.

Цитата: Ghox
Как я понимаю, Kogrom взял себе задачу разработки класса Таблица. Не буду возражать против такого назначения, и не буду пытаться критиковать его вариант, хотя я наверно сделал бы по-другому.


Надо критиковать. Это же дополнительное упражнение.

Цитата: Ghox
Если желающих взяться за какие-либо классы не будет, то согласен сам проработать все классы (хотя Kogrom также может взять что-то себе - хотя бы тот же Интерфейс).



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

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

Тут я увидел другое. При открытом взаимодействии программист-советчик проект не так быстро "замерзает". То есть, тут мы почти создали аналог "экстремального программирования" (XP).

Вывод: в учебном проекте код должны писать один-два человека. Остальные могут либо советовать, находить ошибки, либо выполнять совсем уж мелкие работы по коду. Отдельно можно говорить про тестирование.

Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог