Основы программирования на языке Visual Prolog

Экспертная система. Логическая задача. Фейерверк

Разбить на страницы
Показывать лекцию целиком

Настоящая глава посвящена примерам решения задач на языке Visual Prolog. Создается небольшая экспертная система. Приводится пример ее использования для определения мифологических животных. Рассматривается так называемая "головоломка Эйнштейна" (задача, авторство которой приписывается Эйнштейну). Последний параграф посвящен моделированию фейерверка.

15.1. Экспертная система

Рассмотрим экспертную систему о мифологических животных. Система распознает следующих животных: грифон, дракон, единорог, сатир и сфинкс (для того чтобы она распознавала и других животных, достаточно внести сведения о них в базу данных). Они определяются по следующим признакам:

  • по наличию крыльев или рогов;
  • по форме крыльев, если они имеются;
  • по форме и количеству рогов, в случае их наличия;
  • по форме головы;
  • по форме туловища.
  • Программа вычисляет животное следующим образом. Она перебирает животных из базы данных (об этом пользователю не сообщается) и проверяет в диалоге с пользователем, имеются ли у них в наличии необходимые признаки. Если выяснилось, что признак имеется, то впоследствии повторно вопрос о нем не задается. Полученные сведения могут быть противоречивыми. В этом случае программа сообщает, что для ответа ей недостает сведений. Если оказалось, что все признаки некоторого животного имеются в наличии, и что признаки, которыми он не обладает, действительно отсутствуют, то делается заключение, что животное распознано. Пользователю задаются вопросы, на которые он отвечает "да", "нет" или "?". Например, "Верно ли что, форма крыльев — орлиные?". Если пользователь вводит знак вопроса "?", то программа объясняет, почему задается такой вопрос, например: "Идет проверка гипотезы, что чудовище — грифон <далее перечисляются признаки>. Известно, что чудовище — крылатое".

    Все сведения о животных хранятся в двух видах фактов — rule/4 и cond/3. Отношение rule описывает виды животных, в нем указывается список номеров необходимых признаков. Отношение cond описывает сами признаки. Идея реализации экспертной системы взята из примера GENI системы Visual Prolog 5.2. Ниже приводится более простой вариант экспертной системы.

    В директории Exe проекта следует создать файл monsters.txt и поместить в него приведенные ниже факты.

    clauses
        rule(1, "чудовище", "грифон", [1, 6, 12, 18]).
        rule(2, "чудовище", "дракон", [1, 4, 15, 19]).
        rule(3, "чудовище", "единорог", [1, 5, 2, 7, 9, 11, 16]).
        rule(4, "чудовище", "сатир", [2, 8, 10, 14, 17]).
        rule(5, "чудовище", "сфинкс", [3, 13, 18]).
    
        cond(1, "чудовище", "крылатое").
        cond(2, "чудовище", "рогатое").
        cond(3, "чудовище", "без крыльев и рогов").
        cond(4, "форма крыльев", "летучей мыши").
        cond(5, "форма крыльев", "лебединые").
        cond(6, "форма крыльев", "орлиные").
        cond(7, "форма рогов", "прямые").
        cond(8, "форма рогов", "изогнутые").
        cond(9, "количество рогов", "1").
        cond(10, "количество рогов", "2").
        cond(11, "голова", "лошадиная").
        cond(12, "голова", "орлиная").
        cond(13, "голова", "человечья").
        cond(14, "голова", "обезьянья").
        cond(15, "голова", "крокодилья").
        cond(16, "туловище", "лошадиное").
        cond(17, "туловище", "козлиное").
        cond(18, "туловище", "львиное").
        cond(19, "туловище", "змеиное").
        
        topic("чудовище").
    

    Реализация экспертной системы приведена в листинге 15.2.

        open core, console, string, list
    
    class facts - db
        rule: (positive, string, string, positive*).
        cond: (positive, string, string).
        topic: (string).
    
    class facts
        answer: (positive, integer).
    
    class predicates
        expert: (string).
        ask: (positive, positive*) determ.
        ask: (integer, positive, positive*) determ.
        explanation: (positive).
        getAnswer: () -> integer.
        getAnswer: (char) -> integer determ.
        correct: (positive*) determ.
    clauses
        expert(Topic):-
            rule(K, Topic, Goal, QList),
            ask(K, QList),
            correct(QList),
            !,
            writef("Заключение: % - %.", Topic, Goal).
        expert(Topic):-
            writef("Не хватает сведений, чтобы определить %.", Topic).
    
        ask(_, [N | _]):-
            answer(N, 0),     % если признак отсутствует,
            !,     % то это животное не подходит
            fail.
        ask(K, [N | QList]):-
            answer(N, 1),    % если признак присутствует,
            !,      % то следующий вопрос
            ask(K, QList).
        ask(K, [N | QList]):-
            cond(N, X, Y),     % задается вопрос о признаке
            writef("Верно ли, что % - %? (да/нет/?): > ", X, Y),
            A = getAnswer(),    % ответ от пользователя
            !,
            ask(A, K, [N | QList]).   % реакция на ответ
        ask(_, []).
    
        ask(2, K, QList):- !,    % если введен знак ?, то
            explanation(K),     % выдается объяснение,
            ask(K, QList).     % опрос продолжается
        ask(A, _, [N | _]):-
            assert(answer(N, A)),   % ответ запоминается в БД
            fail.
        ask(1, K, [_ | QList]):-     % если ответ положительный,
            ask(K, QList).     % то опрос продолжается
    
        explanation(K):-
            rule(K, Topic, Goal, QList),
            writef("Проверяется гипотеза, что % - %. Это так, если\n",
                Topic, Goal),
            F = {(I):- cond(I, X, Y), !, writef("\t% - %\n", X, Y); succeed},
            forAll(QList, F),
            L = [N || answer(N, 1), isMember(N, QList)],
            L <> [],
            !,
            write("Известно, что\n"),
            forAll(L, F).
        explanation(_).
    
        correct(QList):-
            answer(N, 1),      % если есть признак, 
            not(isMember(N, QList)),     % которого не должно быть,
            !,      % то животное не подходит
            fail;
            succeed().
    
        getAnswer() = R:-
            S = frontChar(toLowerCase(trim(readLine()))),
            R = getAnswer(S),
            !.
        getAnswer() = 0.
    
        getAnswer(S) = 1:-       % положительный ответ
            (S = 'д'; S = 'y'; S = 'l'),     % не зависит от раскладки
            !.
        getAnswer('?') = 2.
    
        run():-
            file::consult("monsters.txt", db),
            topic(Topic),
            expert(Topic),
            !,
            _ = readLine();
            succeed().
    

    15.2. Головоломка Эйнштейна

    Знаменитая головоломка Эйнштейна формулируется следующим образом.

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

  • англичанин живет в красном доме;
  • у испанца есть собака;
  • кофе пьют в зеленом доме;
  • украинец пьет чай;
  • зеленый дом — первый по правую руку от дома цвета слоновой кости;
  • курильщик "Winston" держит улиток;
  • сигареты "Kool" курят в желтом доме;
  • молоко пьют в среднем доме;
  • норвежец живет в крайнем слева доме;
  • мужчина, курящий "Chesterfield", живет в доме, соседнем с домом мужчины, у которого есть лиса;
  • сигареты "Kool" курят в доме, соседнем с тем, где имеется лошадь;
  • мужчина, предпочитающий "Lucky Strike", пьет апельсиновый сок;
  • японец курит сигареты "Parliament";
  • норвежец живет в доме рядом с голубым домом;
  • у одного из мужчин есть зебра;
  • один из мужчин пьет воду.
  • Определите для каждого дома его цвет, а также национальность, любимый напиток, марку сигарет и домашнее животное хозяина [9].

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

    open core, console, list
    
    constants
        empty : symbol = "".
     
    domains    % описание дома
        house = h(symbol Colour, symbol Nationality, symbol Pet, 
            symbol Drink, symbol Cigarette).
    
    class predicates
        colour: (house, symbol Colour) -> house determ.
        nationality: (house, symbol Nationality) -> house determ.
        pet: (house, symbol Pet) -> house determ.
        drink: (house, symbol Drink) -> house determ.
        cigarette: (house, symbol Cigarette) -> house determ.
        unif: (symbol, symbol) determ.
        leftmost: (house [out], positive [out], house*) determ.
        middle: (house [out], positive [out], house*).
        next: (house, positive, house, positive, house*) 
            nondeterm (o,o,o,o,i).
        right: (house, positive, house, positive, house*) 
            nondeterm (o,o,o,o,i).
        right: (house, positive, positive, house, positive, positive, house*) 
            nondeterm (o,i,o,o,i,o,i). 
        solve: () -> house* nondeterm.
    clauses
        colour(h(Cl, N, P, D, S), C) = h(C, N, P, D, S):- 
            unif(Cl, C).
        nationality(h(C, Nt, P, D, S), N) = h(C, N, P, D, S):-
            unif(Nt, N).
        pet(h(C, N, Pt, D, S), P) = h(C, N, P, D, S):-
            unif(Pt, P).
        drink(h(C, N, P, Dr, S), D) = h(C, N, P, D, S):-
            unif(Dr, D).
        cigarette(h(C, N, P, D, Sg), S) = h(C, N, P, D, S):-
            unif(Sg, S).
    
        unif(empty, _):- !.
        unif(X, X).
    
        % крайний слева дом имеет номер 0 и стоит первым в списке
        leftmost(X, 0, [X | _]).
        % средний дом находится в середине списка
        middle(nth(I, L), I, L):-
            I = length(L) div 2.
        % пары соседних домов, где дом B находится справа от дома A
        right(A, I, B, J, L):-
            right(A, 0, I, B, 1, J, L).
    
        right(A, I, I, B, J, J, [A, B | _]).
        right(A, C1, I, B, C2, J, [_ | L]):-
            right(A, C1 + 1, I, B, C2 + 1, J, L).
        % пары соседних домов
        next(A, Na, B, Nb, Houses):-
            right(A, Na, B, Nb, Houses).
        next(A, Na, B, Nb, Houses):-
            right(B, Nb, A, Na, Houses).    
    
        solve() = Houses20:- 
            Houses = [h(empty, empty, empty, empty, empty) ||
                                _ = std::fromTo(0, 4)],
        % англичанин живет в красном доме
            memberIndex_nd(H1, C1, Houses),
            setNth(C1, Houses, 
                colour(nationality(H1, "Englishman"), "red"), Houses1),
        % у испанца есть собака
            memberIndex_nd(H2, C2, Houses1),
            setNth(C2, Houses1, pet(nationality(H2, "Spaniard"), "dog"),
                    Houses2),
        % кофе пьют в зеленом доме
            memberIndex_nd(H3, C3, Houses2),
            setNth(C3, Houses2, drink(colour(H3, "green"), "coffee"), 
                    Houses3),
        % украинец пьет чай
            memberIndex_nd(H4, C4, Houses3),
            setNth(C4, Houses3, drink(nationality(H4, "Ukrainian"), "tea"),
                    Houses4),
        % зеленый дом – первый справа от дома цвета слоновой кости
            right(H5, C5, H6, C6, Houses4), 
            setNth(C5, Houses4, colour(H5, "ivory"), Houses5),
            setNth(C6, Houses5, colour(H6, "green"), Houses6),
        % курильщик "Winston" держит улиток
            memberIndex_nd(H7, C7, Houses6),
            setNth(C7, Houses6, pet(cigarette(H7, "Winston"), "snails"),
                    Houses7),
        % сигареты "Kool" курят в жёлтом доме
            memberIndex_nd(H8, C8, Houses7),
            setNth(C8, Houses7, colour(cigarette(H8, "Kool"), "yellow"), 
                Houses8),
        % молоко пьют в среднем доме
            middle(H9, C9, Houses8), 
            setNth(C9, Houses8, drink(H9, "milk"), Houses9),
        % норвежец живет в крайнем слева доме
            leftmost(H10, C10, Houses9), 
            setNth(C10, Houses9, nationality(H10, "Norwegian"), 
                Houses10),
        % курящий "Chesterfield", живет по соседству с лисой 
            next(H11, C11, H12, C12, Houses10), 
            setNth(C11, Houses10, cigarette(H11, "Chesterfield"), 
                Houses11),
            setNth(C12, Houses11, pet(H12, "fox"), Houses12),
        % "Kool" курят в доме, соседнем с домом, где имеется лошадь
            next(H13, C13, H14, C14, Houses12), 
            setNth(C13, Houses12, cigarette(H13, "Kool"), Houses13),
            setNth(C14, Houses13, pet(H14, "horse"), Houses14),
        % предпочитающий "Lucky Strike" пьет апельсиновый сок
            memberIndex_nd(H15, C15, Houses14),
            setNth(C15, Houses14, drink(cigarette(H15, "Lucky Strike"), 
                                                    "orange juice"), Houses15),
        % японец курит сигареты "Parliament"
            memberIndex_nd(H16, C16, Houses15),
            setNth(C16, Houses15, cigarette(nationality(H16, "Japanese"),
                                                            "Parliament"), Houses16),
        % норвежец живет в доме рядом с голубым домом
            next(H17, C17, H18, C18, Houses16),
            setNth(C17, Houses16, nationality(H17, "Norwegian"), 
                Houses17),
            setNth(C18, Houses17, colour(H18, "blue"), Houses18),
        % у одного из мужчин есть зебра
            memberIndex_nd(H19, C19, Houses18),
            setNth(C19, Houses18, pet(H19, "zebra"), Houses19),
        % один из мужчин пьет воду
            memberIndex_nd(H20, C20, Houses19),
            setNth(C20, Houses19, drink(H20, "water"), Houses20).
    
        run():-
            Houses = solve(),
                forAll(Houses, {(h(C, N, P, D, S)):- 
                    writef("%-10%-14%-10%-16%\n", C, N, P, D, S)}), 
            fail;
            _ = readLine().
    

    15.3. Новогодний фейерверк

    В следующей программе моделируется фейерверк. Она представляет собой анимационную поздравительную открытку (рис. 15.1).

    (рис 15.1) Новогодний фейерверк

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

    open core, console, console_native, math, list, programControl
    
    constants
        % Congratulation-phrases for random choice
        phraseList : string* =
            [
                "Счастливого Нового Года и Рождества!",
                "From Russia with love!",
                "Vivat Visual Prolog!",
                "We wish you a Merry Christmas and a happy New Year!",
                "Joyeux Noёl et bonne Annee!",
                "Frohe Weihnachten und ein gutes neues Jahr!",
                "Buon Natale e felice Anno Nuovo!",
                "Feliz Navidad y Prospero Ano Nuevo!",
                "Glaedelig Jul og Godt Nytar!",
                "Kurisumasu Omedeto!",
                "Natale hilareet Annum Faustum!"
             ].
        % Symbols that will be randomly used for building a firework
        symbolList : string* = ["*", "+", "x"].
    
    class facts
        xp : unsigned16 := 28.
        yp : unsigned16 := 37.
        phrase : string := "".
        n : positive := 3.
    
    class predicates
        ellipse: (integer Rx, integer Ry, integer XCent, integer YCent,
            string Symbol, positive Sleep).
        arc: (integer Rx, integer Ry, integer XCent, integer YCent,
            real PercentBeg, real PercentEnd, string Symb, positive Sleep).
        line: (coord P1, coord P2, string, positive Sleep).
        print: (string Symbol).
        stop: () determ.
    clauses
        print(Symbol):-
            setTextAttribute(math::random(7) + 9),
            write(Symbol).
    
        % drawing an ellipse with the center in (XCent; YCent)
        ellipse(Rx, Ry, XCent, YCent, Symbol, Sleep):-
            arc(Rx, Ry, XCent, YCent, 0, 1, Symbol, Sleep).
    
        % drawing an arc with the center in (XCent; YCent)
        arc(Rx, Ry, XCent, YCent, PercentBeg, PercEnd, Symb, Sleep):-
            X = std::downTo(round(PercEnd *31), round(PercentBeg *31)),
                Angle = (2 * pi) * (X / 32),
                XCur = round(XCent + Rx * cos(Angle)),
                YCur = round(YCent - Ry * sin(Angle)),
                setLocation(coord(XCur + 0, YCur + 0)),
                print(Symb),
                sleep(Sleep),
            fail;
            succeed().
    
        % Drawing a vertical line
        line(coord(X, Y1), coord(X, Y2), Symbol, Sleep):- !,
            (Y = std::fromTo(Y1, Y2),
                setLocation(coord(X, Y)),
                print(Symbol),
                sleep(Sleep),
            fail;
            succeed()).
        % Drawing a non-vertical line
        line(coord(X1, Y1), coord(X2, Y2), Symbol, Sleep):-
            X = std::between(X1, X2),
                Y = round(Y1 + (X - X1) * (Y2 - Y1) / (X2 - X1)),
                setLocation(coord(X, Y + 0)),
                print(Symbol),
                sleep(Sleep),
            fail;
            succeed().
    
        stop():-
            L = getEventQueue(),
            mouse(_, _, 1, _, 0) = getMember_nd(L),
            !.
    
    % Drawing a firework using random symbols from the list
    % and replacing the firework by that of blank spaces
    constants
    % Parametres for 7 branches of the firework
        startx : integer* = [42, 38, 42, 38, 41, 39, 39].
        starty : integer* = [20, 20, 18, 18, 21, 21, 18].
        vx : integer* = [7, -6, 4, -3, 5, -5, -2].
        vy : integer* = [7, 7, 9, 9, 5, 5, 11].
    class facts
        parametres : tuple{integer, integer, integer, integer}* :=
            zip(startx, starty, vx, vy).
    
    class predicates
        printPoint: (real X0, real Xspeed, real T, real Y0, real Yspeed, 
            real G, string Symbol).
        firework: (boolean IsDrawing, real X0, real Y0, real G,
            positive Sleep, string Symbol, positive SymbolListLength).
        getSymbol: (boolean IsDrawing, string Symbol, positive) -> string.
    clauses
        /* Calculation of the trajectory of a thrown object (symbol) for
            forming a firework line. The object is thrown at certain angle
            and falls with free-fall acceleration G */
        printPoint(X0, Xspeed, T, Y0, Yspeed, G, Symbol):-
            X = round(X0 + XSpeed * T),
            Y = round(Y0 - (YSpeed * T - G * T ^ 2/2)),
            setLocation(coord(X + 0, Y + 0)),
            print(Symbol).
    
        firework(IsDrawing, Xc, Yc, G, Sleep, Symbol, N):-
            I = std::fromTo(0, 29),
                forAll(parametres, {(tuple(Cx, Cy, Vx, Vy)):-
                    printPoint(Cx - Xc, Vx, I * 0.1, Cy + Yc, Vy, G,
                                    getSymbol(IsDrawing, Symbol, N))}),
                sleep(Sleep),
            fail;
            succeed().
    
        getSymbol(true, _, N) = nth(random(N), symbolList):- !.
        getSymbol(_, Symbol, _) = Symbol.
    
        run():-
            FSleep = 15,
            setConsoleTitle("Happy New Year"),
            %A circle that will contain the characters
            ellipse(22, 11, 40, 12, "*", 0),
            vpi::alarm(2),
            % Three
             arc(7, 4, 40, 7, -0.25, 0.40, "$", FSleep),
             arc(9, 5, 40, 16, -0.43, 0.25, "$", FSleep),
             sleep(750),
            % Erasing 'three'
            % To erase something we replace it by the same figure
            % formed by blank spaces
            arc(7, 4, 40, 7, -0.25, 0.40, " ", FSleep),
            arc(9, 5, 40, 16, -0.43, 0.25, " ", FSleep),
            vpi::alarm(2),
            % Two
            arc(7, 4, 40, 7, -0.1, 0.50, "$", FSleep),
            line(coord(46, 9), coord(34, 18), "$", FSleep),
            line(coord(33, 19), coord(48, 19), "$", FSleep),
            sleep(750),
            % Erasing 'two'
            arc(7, 4, 40, 7, -0.1, 0.50, " ", FSleep),
            line(coord(46, 9), coord(34, 18), " ", FSleep),
            line(coord(33, 19), coord(48, 19), " ", FSleep),
            vpi::alarm(2),
            % One
            line(coord(33, 11), coord(43, 5), "$", FSleep),
            line(coord(43, 6), coord(43, 19), "$$", FSleep),
            sleep(750),
            % Erasing 'one'
            line(coord(33, 11), coord(43, 5), " ", FSleep),
            line(coord(43, 6), coord(43, 19), "  ", FSleep),
            vpi::alarm(2),
            % Zero
            ellipse(11, 7, 40, 12, "$$", FSleep),
            sleep(200),
            % Erasing 'zero'
            ellipse(11, 7, 40, 12, "  ", 0),
            % Erasing the initial circle
            ellipse(22, 11, 40, 12, " ", 0),
    
            LengthOfPhraseList = length(phraseList),
            LengthOfSymbolList = length(symbolList),
            vpi::alarm(3),
            I = varM::new(0),
            /* The cycle that draws fireworks and writes random
                congratulation-phrases in a random place */
            std::repeat(),
                Xc = random(11),
                Yc = random(5),
                G = 4 + random(3),
                /* The first time a congratulation-phrase appears
                    (all in all one phrase exists during three fireworks) */
                if I:value = 0 then
                    setLocation(coord(xp, yp)),
                    phrase := nth(n, phraseList)
                end if,
                 /* The last time the current phrase appears; it is replaced
                      by a string of blank spaces. Then new random location is
                      chosen and a new congratulation-phrase is written */
                if I:value mod 3 = 0, I:value > 0 then
                    setLocation(coord(xp, yp)),
                    write(string::create(55, " ")),
                    phrase := nth(n, phraseList),
                    xp :=random(getConsoleWidth() - string::length(phrase)),
                    yp := random(30),
                    setLocation(coord(xp, yp)),
                    write(phrase)
                % Not the first or the last time the current phrase appears
                else
                    setLocation(coord(xp, yp)),
                    write(phrase)
                end if,
                I:value := I:value + 1,
                n := random(LengthOfPhraseList),
                % Drawing a firework
                firework(true, Xc, Yc, G, 30, "", LengthOfSymbolList),
                % Erasing the firework
                firework(false, Xc, Yc, G, 3, " ", 0),
            stop(),
            !,
            _ = readLine();
            succeed().
    

    В упражнениях 6 – 9 требуется написать программу, которая решает логическую задачу.

    Упражнения

  • Реализуйте такой вывод в экспертной системе, чтобы система начинала с опроса сведений о признаках животного, и при наличии необходимого набора признаков у какого-либо животного, сразу выдавала бы заключение.
  • Создайте экспертную систему по определению видов растений.
  • Создайте экспертную систему по выбору места летнего отдыха.
  • Создайте экспертную систему по выбору книг для отпуска.
  • Создайте экспертную систему по выбору направления обучения для абитуриента.
  • Мартовский Заяц заявил, что муку украл Болванщик. Что сказали Болванщик и Соня, записано не было. Однако выяснилось, что муку украл лишь один из этих троих и что только он сказал правду. Кто украл муку?
  • Мартовский Заяц сообщил, что масло украл Болванщик. Болванщик сказал, что яйца украла Соня. Соня сказала, что она украла молоко. Каждый из них украл что-то одно. Выяснилось, что тот, кто украл масло, говорил правду, а тот, кто украл яйца, лгал. Кто что украл?
  • Тройка думает, что Туз не в своем уме. Четверка думает, что Тройка и Двойка обе не могут быть не в своем уме. Пятерка думает, что Туз и Четверка либо оба не в своем уме, либо оба в здравом рассудке. Шестерка думает, что Туз и Двойка оба в здравом уме. Семерка думает, что Пятерка не в своем уме. Валет Червей думает, что Шестерка и Семерка не обе не в своем уме. В своем ли уме Валет Червей?
  • На одном международном конгрессе встретились четыре делегата из разных стран. Каждый из них владел двумя языками из четырех — английским, французским, немецким и итальянским. Оказалось, что не было такого языка, на котором они могли говорить вчетвером, и был только один язык, на котором могли говорить трое из них. Никто из делегатов не владеет французским и немецким языками одновременно. Хотя физик не говорит по-английски, он может служить переводчиком, если математик и биолог захотят поговорить друг с другом. Биолог говорит по-немецки и может говорить с химиком, хотя тот не знает ни одного немецкого слова. Физик, математик и химик не могут беседовать втроем на одном языке. Какими двумя языками владеет каждый из этих делегатов ?
  • Измените программу "Фейерверк" (листинг 15.4) так, чтобы почти одновременно производилось по 2-3 залпа с разным числом "ветвей".
  • Страницы:

    Настоящая глава посвящена примерам решения задач на языке Visual Prolog. Создается небольшая экспертная система. Приводится пример ее использования для определения мифологических животных. Рассматривается так называемая "головоломка Эйнштейна" (задача, авторство которой приписывается Эйнштейну). Последний параграф посвящен моделированию фейерверка.

    15.1. Экспертная система

    Рассмотрим экспертную систему о мифологических животных. Система распознает следующих животных: грифон, дракон, единорог, сатир и сфинкс (для того чтобы она распознавала и других животных, достаточно внести сведения о них в базу данных). Они определяются по следующим признакам:

  • по наличию крыльев или рогов;
  • по форме крыльев, если они имеются;
  • по форме и количеству рогов, в случае их наличия;
  • по форме головы;
  • по форме туловища.
  • Программа вычисляет животное следующим образом. Она перебирает животных из базы данных (об этом пользователю не сообщается) и проверяет в диалоге с пользователем, имеются ли у них в наличии необходимые признаки. Если выяснилось, что признак имеется, то впоследствии повторно вопрос о нем не задается. Полученные сведения могут быть противоречивыми. В этом случае программа сообщает, что для ответа ей недостает сведений. Если оказалось, что все признаки некоторого животного имеются в наличии, и что признаки, которыми он не обладает, действительно отсутствуют, то делается заключение, что животное распознано. Пользователю задаются вопросы, на которые он отвечает "да", "нет" или "?". Например, "Верно ли что, форма крыльев — орлиные?". Если пользователь вводит знак вопроса "?", то программа объясняет, почему задается такой вопрос, например: "Идет проверка гипотезы, что чудовище — грифон <далее перечисляются признаки>. Известно, что чудовище — крылатое".

    Все сведения о животных хранятся в двух видах фактов — rule/4 и cond/3. Отношение rule описывает виды животных, в нем указывается список номеров необходимых признаков. Отношение cond описывает сами признаки. Идея реализации экспертной системы взята из примера GENI системы Visual Prolog 5.2. Ниже приводится более простой вариант экспертной системы.

    В директории Exe проекта следует создать файл monsters.txt и поместить в него приведенные ниже факты.

    clauses
        rule(1, "чудовище", "грифон", [1, 6, 12, 18]).
        rule(2, "чудовище", "дракон", [1, 4, 15, 19]).
        rule(3, "чудовище", "единорог", [1, 5, 2, 7, 9, 11, 16]).
        rule(4, "чудовище", "сатир", [2, 8, 10, 14, 17]).
        rule(5, "чудовище", "сфинкс", [3, 13, 18]).
    
        cond(1, "чудовище", "крылатое").
        cond(2, "чудовище", "рогатое").
        cond(3, "чудовище", "без крыльев и рогов").
        cond(4, "форма крыльев", "летучей мыши").
        cond(5, "форма крыльев", "лебединые").
        cond(6, "форма крыльев", "орлиные").
        cond(7, "форма рогов", "прямые").
        cond(8, "форма рогов", "изогнутые").
        cond(9, "количество рогов", "1").
        cond(10, "количество рогов", "2").
        cond(11, "голова", "лошадиная").
        cond(12, "голова", "орлиная").
        cond(13, "голова", "человечья").
        cond(14, "голова", "обезьянья").
        cond(15, "голова", "крокодилья").
        cond(16, "туловище", "лошадиное").
        cond(17, "туловище", "козлиное").
        cond(18, "туловище", "львиное").
        cond(19, "туловище", "змеиное").
        
        topic("чудовище").
    

    Реализация экспертной системы приведена в листинге 15.2.

        open core, console, string, list
    
    class facts - db
        rule: (positive, string, string, positive*).
        cond: (positive, string, string).
        topic: (string).
    
    class facts
        answer: (positive, integer).
    
    class predicates
        expert: (string).
        ask: (positive, positive*) determ.
        ask: (integer, positive, positive*) determ.
        explanation: (positive).
        getAnswer: () -> integer.
        getAnswer: (char) -> integer determ.
        correct: (positive*) determ.
    clauses
        expert(Topic):-
            rule(K, Topic, Goal, QList),
            ask(K, QList),
            correct(QList),
            !,
            writef("Заключение: % - %.", Topic, Goal).
        expert(Topic):-
            writef("Не хватает сведений, чтобы определить %.", Topic).
    
        ask(_, [N | _]):-
            answer(N, 0),     % если признак отсутствует,
            !,     % то это животное не подходит
            fail.
        ask(K, [N | QList]):-
            answer(N, 1),    % если признак присутствует,
            !,      % то следующий вопрос
            ask(K, QList).
        ask(K, [N | QList]):-
            cond(N, X, Y),     % задается вопрос о признаке
            writef("Верно ли, что % - %? (да/нет/?): > ", X, Y),
            A = getAnswer(),    % ответ от пользователя
            !,
            ask(A, K, [N | QList]).   % реакция на ответ
        ask(_, []).
    
        ask(2, K, QList):- !,    % если введен знак ?, то
            explanation(K),     % выдается объяснение,
            ask(K, QList).     % опрос продолжается
        ask(A, _, [N | _]):-
            assert(answer(N, A)),   % ответ запоминается в БД
            fail.
        ask(1, K, [_ | QList]):-     % если ответ положительный,
            ask(K, QList).     % то опрос продолжается
    
        explanation(K):-
            rule(K, Topic, Goal, QList),
            writef("Проверяется гипотеза, что % - %. Это так, если\n",
                Topic, Goal),
            F = {(I):- cond(I, X, Y), !, writef("\t% - %\n", X, Y); succeed},
            forAll(QList, F),
            L = [N || answer(N, 1), isMember(N, QList)],
            L <> [],
            !,
            write("Известно, что\n"),
            forAll(L, F).
        explanation(_).
    
        correct(QList):-
            answer(N, 1),      % если есть признак, 
            not(isMember(N, QList)),     % которого не должно быть,
            !,      % то животное не подходит
            fail;
            succeed().
    
        getAnswer() = R:-
            S = frontChar(toLowerCase(trim(readLine()))),
            R = getAnswer(S),
            !.
        getAnswer() = 0.
    
        getAnswer(S) = 1:-       % положительный ответ
            (S = 'д'; S = 'y'; S = 'l'),     % не зависит от раскладки
            !.
        getAnswer('?') = 2.
    
        run():-
            file::consult("monsters.txt", db),
            topic(Topic),
            expert(Topic),
            !,
            _ = readLine();
            succeed().
    

    15.2. Головоломка Эйнштейна

    Знаменитая головоломка Эйнштейна формулируется следующим образом.

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

  • англичанин живет в красном доме;
  • у испанца есть собака;
  • кофе пьют в зеленом доме;
  • украинец пьет чай;
  • зеленый дом — первый по правую руку от дома цвета слоновой кости;
  • курильщик "Winston" держит улиток;
  • сигареты "Kool" курят в желтом доме;
  • молоко пьют в среднем доме;
  • норвежец живет в крайнем слева доме;
  • мужчина, курящий "Chesterfield", живет в доме, соседнем с домом мужчины, у которого есть лиса;
  • сигареты "Kool" курят в доме, соседнем с тем, где имеется лошадь;
  • мужчина, предпочитающий "Lucky Strike", пьет апельсиновый сок;
  • японец курит сигареты "Parliament";
  • норвежец живет в доме рядом с голубым домом;
  • у одного из мужчин есть зебра;
  • один из мужчин пьет воду.
  • Определите для каждого дома его цвет, а также национальность, любимый напиток, марку сигарет и домашнее животное хозяина [9].

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

    open core, console, list
    
    constants
        empty : symbol = "".
     
    domains    % описание дома
        house = h(symbol Colour, symbol Nationality, symbol Pet, 
            symbol Drink, symbol Cigarette).
    
    class predicates
        colour: (house, symbol Colour) -> house determ.
        nationality: (house, symbol Nationality) -> house determ.
        pet: (house, symbol Pet) -> house determ.
        drink: (house, symbol Drink) -> house determ.
        cigarette: (house, symbol Cigarette) -> house determ.
        unif: (symbol, symbol) determ.
        leftmost: (house [out], positive [out], house*) determ.
        middle: (house [out], positive [out], house*).
        next: (house, positive, house, positive, house*) 
            nondeterm (o,o,o,o,i).
        right: (house, positive, house, positive, house*) 
            nondeterm (o,o,o,o,i).
        right: (house, positive, positive, house, positive, positive, house*) 
            nondeterm (o,i,o,o,i,o,i). 
        solve: () -> house* nondeterm.
    clauses
        colour(h(Cl, N, P, D, S), C) = h(C, N, P, D, S):- 
            unif(Cl, C).
        nationality(h(C, Nt, P, D, S), N) = h(C, N, P, D, S):-
            unif(Nt, N).
        pet(h(C, N, Pt, D, S), P) = h(C, N, P, D, S):-
            unif(Pt, P).
        drink(h(C, N, P, Dr, S), D) = h(C, N, P, D, S):-
            unif(Dr, D).
        cigarette(h(C, N, P, D, Sg), S) = h(C, N, P, D, S):-
            unif(Sg, S).
    
        unif(empty, _):- !.
        unif(X, X).
    
        % крайний слева дом имеет номер 0 и стоит первым в списке
        leftmost(X, 0, [X | _]).
        % средний дом находится в середине списка
        middle(nth(I, L), I, L):-
            I = length(L) div 2.
        % пары соседних домов, где дом B находится справа от дома A
        right(A, I, B, J, L):-
            right(A, 0, I, B, 1, J, L).
    
        right(A, I, I, B, J, J, [A, B | _]).
        right(A, C1, I, B, C2, J, [_ | L]):-
            right(A, C1 + 1, I, B, C2 + 1, J, L).
        % пары соседних домов
        next(A, Na, B, Nb, Houses):-
            right(A, Na, B, Nb, Houses).
        next(A, Na, B, Nb, Houses):-
            right(B, Nb, A, Na, Houses).    
    
        solve() = Houses20:- 
            Houses = [h(empty, empty, empty, empty, empty) ||
                                _ = std::fromTo(0, 4)],
        % англичанин живет в красном доме
            memberIndex_nd(H1, C1, Houses),
            setNth(C1, Houses, 
                colour(nationality(H1, "Englishman"), "red"), Houses1),
        % у испанца есть собака
            memberIndex_nd(H2, C2, Houses1),
            setNth(C2, Houses1, pet(nationality(H2, "Spaniard"), "dog"),
                    Houses2),
        % кофе пьют в зеленом доме
            memberIndex_nd(H3, C3, Houses2),
            setNth(C3, Houses2, drink(colour(H3, "green"), "coffee"), 
                    Houses3),
        % украинец пьет чай
            memberIndex_nd(H4, C4, Houses3),
            setNth(C4, Houses3, drink(nationality(H4, "Ukrainian"), "tea"),
                    Houses4),
        % зеленый дом – первый справа от дома цвета слоновой кости
            right(H5, C5, H6, C6, Houses4), 
            setNth(C5, Houses4, colour(H5, "ivory"), Houses5),
            setNth(C6, Houses5, colour(H6, "green"), Houses6),
        % курильщик "Winston" держит улиток
            memberIndex_nd(H7, C7, Houses6),
            setNth(C7, Houses6, pet(cigarette(H7, "Winston"), "snails"),
                    Houses7),
        % сигареты "Kool" курят в жёлтом доме
            memberIndex_nd(H8, C8, Houses7),
            setNth(C8, Houses7, colour(cigarette(H8, "Kool"), "yellow"), 
                Houses8),
        % молоко пьют в среднем доме
            middle(H9, C9, Houses8), 
            setNth(C9, Houses8, drink(H9, "milk"), Houses9),
        % норвежец живет в крайнем слева доме
            leftmost(H10, C10, Houses9), 
            setNth(C10, Houses9, nationality(H10, "Norwegian"), 
                Houses10),
        % курящий "Chesterfield", живет по соседству с лисой 
            next(H11, C11, H12, C12, Houses10), 
            setNth(C11, Houses10, cigarette(H11, "Chesterfield"), 
                Houses11),
            setNth(C12, Houses11, pet(H12, "fox"), Houses12),
        % "Kool" курят в доме, соседнем с домом, где имеется лошадь
            next(H13, C13, H14, C14, Houses12), 
            setNth(C13, Houses12, cigarette(H13, "Kool"), Houses13),
            setNth(C14, Houses13, pet(H14, "horse"), Houses14),
        % предпочитающий "Lucky Strike" пьет апельсиновый сок
            memberIndex_nd(H15, C15, Houses14),
            setNth(C15, Houses14, drink(cigarette(H15, "Lucky Strike"), 
                                                    "orange juice"), Houses15),
        % японец курит сигареты "Parliament"
            memberIndex_nd(H16, C16, Houses15),
            setNth(C16, Houses15, cigarette(nationality(H16, "Japanese"),
                                                            "Parliament"), Houses16),
        % норвежец живет в доме рядом с голубым домом
            next(H17, C17, H18, C18, Houses16),
            setNth(C17, Houses16, nationality(H17, "Norwegian"), 
                Houses17),
            setNth(C18, Houses17, colour(H18, "blue"), Houses18),
        % у одного из мужчин есть зебра
            memberIndex_nd(H19, C19, Houses18),
            setNth(C19, Houses18, pet(H19, "zebra"), Houses19),
        % один из мужчин пьет воду
            memberIndex_nd(H20, C20, Houses19),
            setNth(C20, Houses19, drink(H20, "water"), Houses20).
    
        run():-
            Houses = solve(),
                forAll(Houses, {(h(C, N, P, D, S)):- 
                    writef("%-10%-14%-10%-16%\n", C, N, P, D, S)}), 
            fail;
            _ = readLine().
    

    15.3. Новогодний фейерверк

    В следующей программе моделируется фейерверк. Она представляет собой анимационную поздравительную открытку (рис. 15.1).

    (рис 15.1) Новогодний фейерверк

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

    open core, console, console_native, math, list, programControl
    
    constants
        % Congratulation-phrases for random choice
        phraseList : string* =
            [
                "Счастливого Нового Года и Рождества!",
                "From Russia with love!",
                "Vivat Visual Prolog!",
                "We wish you a Merry Christmas and a happy New Year!",
                "Joyeux Noёl et bonne Annee!",
                "Frohe Weihnachten und ein gutes neues Jahr!",
                "Buon Natale e felice Anno Nuovo!",
                "Feliz Navidad y Prospero Ano Nuevo!",
                "Glaedelig Jul og Godt Nytar!",
                "Kurisumasu Omedeto!",
                "Natale hilareet Annum Faustum!"
             ].
        % Symbols that will be randomly used for building a firework
        symbolList : string* = ["*", "+", "x"].
    
    class facts
        xp : unsigned16 := 28.
        yp : unsigned16 := 37.
        phrase : string := "".
        n : positive := 3.
    
    class predicates
        ellipse: (integer Rx, integer Ry, integer XCent, integer YCent,
            string Symbol, positive Sleep).
        arc: (integer Rx, integer Ry, integer XCent, integer YCent,
            real PercentBeg, real PercentEnd, string Symb, positive Sleep).
        line: (coord P1, coord P2, string, positive Sleep).
        print: (string Symbol).
        stop: () determ.
    clauses
        print(Symbol):-
            setTextAttribute(math::random(7) + 9),
            write(Symbol).
    
        % drawing an ellipse with the center in (XCent; YCent)
        ellipse(Rx, Ry, XCent, YCent, Symbol, Sleep):-
            arc(Rx, Ry, XCent, YCent, 0, 1, Symbol, Sleep).
    
        % drawing an arc with the center in (XCent; YCent)
        arc(Rx, Ry, XCent, YCent, PercentBeg, PercEnd, Symb, Sleep):-
            X = std::downTo(round(PercEnd *31), round(PercentBeg *31)),
                Angle = (2 * pi) * (X / 32),
                XCur = round(XCent + Rx * cos(Angle)),
                YCur = round(YCent - Ry * sin(Angle)),
                setLocation(coord(XCur + 0, YCur + 0)),
                print(Symb),
                sleep(Sleep),
            fail;
            succeed().
    
        % Drawing a vertical line
        line(coord(X, Y1), coord(X, Y2), Symbol, Sleep):- !,
            (Y = std::fromTo(Y1, Y2),
                setLocation(coord(X, Y)),
                print(Symbol),
                sleep(Sleep),
            fail;
            succeed()).
        % Drawing a non-vertical line
        line(coord(X1, Y1), coord(X2, Y2), Symbol, Sleep):-
            X = std::between(X1, X2),
                Y = round(Y1 + (X - X1) * (Y2 - Y1) / (X2 - X1)),
                setLocation(coord(X, Y + 0)),
                print(Symbol),
                sleep(Sleep),
            fail;
            succeed().
    
        stop():-
            L = getEventQueue(),
            mouse(_, _, 1, _, 0) = getMember_nd(L),
            !.
    
    % Drawing a firework using random symbols from the list
    % and replacing the firework by that of blank spaces
    constants
    % Parametres for 7 branches of the firework
        startx : integer* = [42, 38, 42, 38, 41, 39, 39].
        starty : integer* = [20, 20, 18, 18, 21, 21, 18].
        vx : integer* = [7, -6, 4, -3, 5, -5, -2].
        vy : integer* = [7, 7, 9, 9, 5, 5, 11].
    class facts
        parametres : tuple{integer, integer, integer, integer}* :=
            zip(startx, starty, vx, vy).
    
    class predicates
        printPoint: (real X0, real Xspeed, real T, real Y0, real Yspeed, 
            real G, string Symbol).
        firework: (boolean IsDrawing, real X0, real Y0, real G,
            positive Sleep, string Symbol, positive SymbolListLength).
        getSymbol: (boolean IsDrawing, string Symbol, positive) -> string.
    clauses
        /* Calculation of the trajectory of a thrown object (symbol) for
            forming a firework line. The object is thrown at certain angle
            and falls with free-fall acceleration G */
        printPoint(X0, Xspeed, T, Y0, Yspeed, G, Symbol):-
            X = round(X0 + XSpeed * T),
            Y = round(Y0 - (YSpeed * T - G * T ^ 2/2)),
            setLocation(coord(X + 0, Y + 0)),
            print(Symbol).
    
        firework(IsDrawing, Xc, Yc, G, Sleep, Symbol, N):-
            I = std::fromTo(0, 29),
                forAll(parametres, {(tuple(Cx, Cy, Vx, Vy)):-
                    printPoint(Cx - Xc, Vx, I * 0.1, Cy + Yc, Vy, G,
                                    getSymbol(IsDrawing, Symbol, N))}),
                sleep(Sleep),
            fail;
            succeed().
    
        getSymbol(true, _, N) = nth(random(N), symbolList):- !.
        getSymbol(_, Symbol, _) = Symbol.
    
        run():-
            FSleep = 15,
            setConsoleTitle("Happy New Year"),
            %A circle that will contain the characters
            ellipse(22, 11, 40, 12, "*", 0),
            vpi::alarm(2),
            % Three
             arc(7, 4, 40, 7, -0.25, 0.40, "$", FSleep),
             arc(9, 5, 40, 16, -0.43, 0.25, "$", FSleep),
             sleep(750),
            % Erasing 'three'
            % To erase something we replace it by the same figure
            % formed by blank spaces
            arc(7, 4, 40, 7, -0.25, 0.40, " ", FSleep),
            arc(9, 5, 40, 16, -0.43, 0.25, " ", FSleep),
            vpi::alarm(2),
            % Two
            arc(7, 4, 40, 7, -0.1, 0.50, "$", FSleep),
            line(coord(46, 9), coord(34, 18), "$", FSleep),
            line(coord(33, 19), coord(48, 19), "$", FSleep),
            sleep(750),
            % Erasing 'two'
            arc(7, 4, 40, 7, -0.1, 0.50, " ", FSleep),
            line(coord(46, 9), coord(34, 18), " ", FSleep),
            line(coord(33, 19), coord(48, 19), " ", FSleep),
            vpi::alarm(2),
            % One
            line(coord(33, 11), coord(43, 5), "$", FSleep),
            line(coord(43, 6), coord(43, 19), "$$", FSleep),
            sleep(750),
            % Erasing 'one'
            line(coord(33, 11), coord(43, 5), " ", FSleep),
            line(coord(43, 6), coord(43, 19), "  ", FSleep),
            vpi::alarm(2),
            % Zero
            ellipse(11, 7, 40, 12, "$$", FSleep),
            sleep(200),
            % Erasing 'zero'
            ellipse(11, 7, 40, 12, "  ", 0),
            % Erasing the initial circle
            ellipse(22, 11, 40, 12, " ", 0),
    
            LengthOfPhraseList = length(phraseList),
            LengthOfSymbolList = length(symbolList),
            vpi::alarm(3),
            I = varM::new(0),
            /* The cycle that draws fireworks and writes random
                congratulation-phrases in a random place */
            std::repeat(),
                Xc = random(11),
                Yc = random(5),
                G = 4 + random(3),
                /* The first time a congratulation-phrase appears
                    (all in all one phrase exists during three fireworks) */
                if I:value = 0 then
                    setLocation(coord(xp, yp)),
                    phrase := nth(n, phraseList)
                end if,
                 /* The last time the current phrase appears; it is replaced
                      by a string of blank spaces. Then new random location is
                      chosen and a new congratulation-phrase is written */
                if I:value mod 3 = 0, I:value > 0 then
                    setLocation(coord(xp, yp)),
                    write(string::create(55, " ")),
                    phrase := nth(n, phraseList),
                    xp :=random(getConsoleWidth() - string::length(phrase)),
                    yp := random(30),
                    setLocation(coord(xp, yp)),
                    write(phrase)
                % Not the first or the last time the current phrase appears
                else
                    setLocation(coord(xp, yp)),
                    write(phrase)
                end if,
                I:value := I:value + 1,
                n := random(LengthOfPhraseList),
                % Drawing a firework
                firework(true, Xc, Yc, G, 30, "", LengthOfSymbolList),
                % Erasing the firework
                firework(false, Xc, Yc, G, 3, " ", 0),
            stop(),
            !,
            _ = readLine();
            succeed().
    

    В упражнениях 6 – 9 требуется написать программу, которая решает логическую задачу.

    Упражнения

  • Реализуйте такой вывод в экспертной системе, чтобы система начинала с опроса сведений о признаках животного, и при наличии необходимого набора признаков у какого-либо животного, сразу выдавала бы заключение.
  • Создайте экспертную систему по определению видов растений.
  • Создайте экспертную систему по выбору места летнего отдыха.
  • Создайте экспертную систему по выбору книг для отпуска.
  • Создайте экспертную систему по выбору направления обучения для абитуриента.
  • Мартовский Заяц заявил, что муку украл Болванщик. Что сказали Болванщик и Соня, записано не было. Однако выяснилось, что муку украл лишь один из этих троих и что только он сказал правду. Кто украл муку?
  • Мартовский Заяц сообщил, что масло украл Болванщик. Болванщик сказал, что яйца украла Соня. Соня сказала, что она украла молоко. Каждый из них украл что-то одно. Выяснилось, что тот, кто украл масло, говорил правду, а тот, кто украл яйца, лгал. Кто что украл?
  • Тройка думает, что Туз не в своем уме. Четверка думает, что Тройка и Двойка обе не могут быть не в своем уме. Пятерка думает, что Туз и Четверка либо оба не в своем уме, либо оба в здравом рассудке. Шестерка думает, что Туз и Двойка оба в здравом уме. Семерка думает, что Пятерка не в своем уме. Валет Червей думает, что Шестерка и Семерка не обе не в своем уме. В своем ли уме Валет Червей?
  • На одном международном конгрессе встретились четыре делегата из разных стран. Каждый из них владел двумя языками из четырех — английским, французским, немецким и итальянским. Оказалось, что не было такого языка, на котором они могли говорить вчетвером, и был только один язык, на котором могли говорить трое из них. Никто из делегатов не владеет французским и немецким языками одновременно. Хотя физик не говорит по-английски, он может служить переводчиком, если математик и биолог захотят поговорить друг с другом. Биолог говорит по-немецки и может говорить с химиком, хотя тот не знает ни одного немецкого слова. Физик, математик и химик не могут беседовать втроем на одном языке. Какими двумя языками владеет каждый из этих делегатов ?
  • Измените программу "Фейерверк" (листинг 15.4) так, чтобы почти одновременно производилось по 2-3 залпа с разным числом "ветвей".
  • Вернуться к учебному плану