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

Игры

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

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

14.1. Топологическая игра "Ползунок"

Топологическая игра "Ползунок" заключается в следующем. Игра ведется на решетке размером 5 на 6 клеток (рис. 14.1).

(рис 14.1) Партия топологической игры "Ползунок"

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

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

    open core, console, console_native

class facts
    humanCell: (coord).

class predicates
    winMove: (positive, positive, coord*, coord*) nondeterm (i,i,i,o).
    step: (coord*, coord*, coord) nondeterm (i,o,o) (i,o,i).
    next: (coord, coord) nondeterm (i,o) (i,i).
    move: (positive, coord*).
    show: (coord*).
    loseMove: (coord*) determ.
clauses
    loseMove(Polyline):-
        step(Polyline, _, Cell),
        list::isMember(Cell, Polyline),
        !.

    winMove(_Player1, _Player2, Polyline, Polyline1):-
        step(Polyline, Polyline1, Cell),
        list::isMember(Cell, Polyline).
    winMove(Player1, Player2, Polyline, Polyline1):-
        step(Polyline, Polyline1, _),
        not(winMove(Player2, Player1, Polyline1, _)).

    move(2, Polyline):-
        step(Polyline, Polyline1, Cell),
        list::isMember(Cell, Polyline),
        !,
        show(Polyline1),
        setLocation(coord(0, 14)),
        write("Компьютер выиграл!\n\n", Polyline1).
    move(2, Polyline):-
        winMove(2, 1, Polyline, Polyline1),
        !,
        move(1, Polyline1).
    move(2, Polyline):-
        step(Polyline, Polyline1, _),
        not(loseMove(Polyline1)),
        !,
        move(1, Polyline1).
    move(2, Polyline):-
        step(Polyline, Polyline1, _),
        !,
        move(1, Polyline1).
    move(1, Polyline):-
        show(Polyline),
        setLocation(coord(0, 14)),
        write(Polyline),
        std::repeat(),
            write("\n\nВаш ход.\nВведите новую клетку в виде"
                " coord(2,3): "),
            hasDomain(coord, Cell),
            Cell = read(), clearInput(),
        step(Polyline, Polyline1, Cell),
        assert(humanCell(Cell)),
        !,
        if list::isMember(Cell, Polyline) then
            write("\nПоздравляю! Вы выиграли!")
        else
            move(2, Polyline1)
        end if.
    move(_, _).

    step([A, B | Polyline], [X, A, B | Polyline], X):-
        next(A, X),
        not(X = B).
    step(Polyline, list::append(Polyline, [X]), X):-
        [A, B | _] = list::reverse(Polyline),
        next(A, X),
        not(X = B).

    next(coord(X, Y), coord(X - 1, Y)):- X > 1.
    next(coord(X, Y), coord(X + 1, Y)):- X < 6.
    next(coord(X, Y), coord(X, Y - 1)):- Y > 1.
    next(coord(X, Y), coord(X, Y + 1)):- Y < 5.

    show(Polyline):-
        clearOutput(),
        foreach I = std::fromTo(1, 6) do
            setLocation(coord(3 * I, 0)), write(I)
        end foreach,
        foreach J = std::fromTo(1, 5) do
            setLocation(coord(0, 2 * J)), write(J)
        end foreach,
        Attr0 = getTextAttribute(),
        foreach coord(X, Y) = list::getMember_nd(Polyline) do
            Attr = if humanCell(coord(X, Y)), ! then 13 else 11 end if,
            setTextAttribute(Attr),
            setLocation(coord(3 * X, 2 * Y)), write("*")
        end foreach,
        setTextAttribute(Attr0).

    run():-
        write("Кто ходит первым? (1 - человек, 2 - компьютер): "),
        Player = read(), clearInput(),
        if Player = 1 then
            write("Введите начало отрезка в виде coord(3,3): "),
            hasDomain(coord, Cell1),
            Cell1 = read(), clearInput(),
            write("Введите конец отрезка: "),
            hasDomain(coord, Cell2),
            Cell2 = read(), clearInput(),
            next(Cell1, Cell2),
            assert(humanCell(Cell1)),
            assert(humanCell(Cell2)),
            move(2, [Cell1, Cell2])
        else
            move(1, [coord(3, 3), coord(4, 3)])
         end if,
         !,
        _ = readLine();
        write("Ошибка ввода"),
        _ = readLine().

Предикат hasDomain/2 связывает переменную с именем домена. Предикат getTextAttribute возвращает текущие атрибуты текста окна консоли.

Метод ван Эмдена может быть использован только для полей небольших размеров, так как перебор во время поиска выигрышного хода делается довольно большой. Подробный разбор, демонстрирующий этапы проектирования и компьютерной реализации топологической игры "Ползунок", как в консольных, так и в GUI-приложениях на языке Visual Prolog, проделал В. А. Юхтенко (PDC SPb) [12].

14.2. Игра "Поле чудес"

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

    open core, console

class facts
    word: (positive, string).
clauses
    word(1, "пароход").
    word(2, "паровоз").
    word(3, "самолет").
    word(4, "аэроплан").
    word(5, "корабль").
    word(6, "поезд").
    word(7, "дирижабль").
    word(8, "зонд").
    word(9, "аэростат").
    word(10, "стратостат").
    word(11, "ракета").
    word(12, "электровоз").

class facts
    number : positive := 0.
    currentWord : string := "".
    secretWord : string := "".
    attempt : positive := 0.

class predicates
    human_machine: ().
    replace : (string, string) -> string.
clauses
    human_machine():-
        No = math::random(number) + 1,
        word(No, Word),
        NormWord = string::toUpperCase(Word),
        currentWord := NormWord,
        Len = string::length(NormWord),
        secretWord := string::create(Len, "_"),
        std::repeat(),
            nl, write(string::replaceAll(secretWord, "_", " _ ")),
            write("\n\nНазовите слово или букву: "),
            String = string::toUpperCase(readLine()),
            attempt := attempt + 1,
            LenOfStr = string::length(String),
            if LenOfStr = 1, not(_ = string::search(secretWord, String))
            then if not(_ = string::search(currentWord, String)) then
                        write("\nНет такой буквы в этом слове.\n")
                    else secretWord := replace(secretWord, String) end if
            end if,
        ((string::isWhiteSpace(currentWord); String = NormWord),
        writef("\n\nСлово: %!\nКоличество попыток: %!", NormWord,
            attempt);
        LenOfStr > 1,
        writef("\n\nСлово: %\nВы назвали % и проиграли!",
            NormWord, String)),
        !.
    human_machine().

    replace(SecretWord, Letter) = replace(SecretWord1, Letter):-
        Pos = string::search(currentWord, Letter),
        !,
        currentWord := string::replacePart(currentWord, Pos, 1, " "),
        SecretWord1 = string::replacePart(SecretWord, Pos, 1, Letter).
    replace(SecretWord, _) = SecretWord.

    run():-
        write("Машина загадывает, человек угадывает!\n\n"),
        number := list::length([X || word(X, _)]),
        human_machine(),
        _ = readLine().

Предикат toUpperCase/2 переводит все символы строки в верхний регистр. Предикат search/2 возвращает позицию подстроки в строке. Предикат replaceAll/3 заменяет в строке все вхождения одной подстроки другой подстрокой. Предикат replacePart/4 заменяет подстрокой заданное количество элементов с указанной позиции.

14.3. Игра "Отгадай числа"

"Отгадай числа" — это игра для двух участников, которая состоит в следующем. Игроки загадывают по n целых чисел в пределах от 0 до 9 включительно, числа не повторяются. Цель игры — найти набор чисел, который загадал противник. Порядок чисел в наборе значения не имеет. Участник называет n чисел. Если среди них более половины совпадают с числами, загаданными противником, то право хода остается за ним. В противном случае ход переходит к другому игроку. Побеждает игрок, который первым правильно назовет весь набор чисел, загаданный противником. Данная игра является одним из вариантов игры "Быки и коровы" [9], в котором имеются только "коровы".

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

    open core, console, list

constants
    diglist : unsigned* = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0].

class facts
    num: (unsigned, unsigned*, positive).
    digs : unsigned* := [].
    n : positive := 0.
class predicates
    readDigits: (positive) -> unsigned*.
    add: (unsigned, unsigned*, unsigned* [out], positive, 
        positive [out]).
    gen: (positive, unsigned) -> unsigned*.
    gen: (positive, unsigned, unsigned*) -> unsigned*.
    count: (unsigned*, unsigned*) -> positive.
    f: (positive) -> string.
clauses
    readDigits(N) = reverse(digs):-
        writef("Введите % % от 0 до 9 через пробел: > ", N, f(N)),
        digs := [], n := N,
        std::repeat(),
            try hasDomain(unsigned, X), X = read()
            catch _ do
                clearInput(), write("\nПовторите попытку: > "), fail
            end try,
            add(X, digs, L, n, N1),
            digs := L, n := N1,
        n = 0,
        clearInput(),
        !.
    readDigits(_) = [].

    f(N) = "чисел":-
        N > 4,
        !.
    f(_) = "числа".

    gen(N, R) = reverse(gen(N, R, [])).

    gen(N, R, L) = gen(N1, R, L1):-
        N > 0,
        !,
        add(math::random(R), L, L1, N, N1).
    gen(_, _, L) = L.

    add(X, L, [X | L], N, N - 1):-
        X < 10,
        not(isMember(X, L)),
        !.
    add(_, L, L, N, N).

    count(L, L1) = length(filter(L, {(X):- isMember(X, L1)})).

class predicates
    game: (positive).
    game: (positive, unsigned, unsigned*, unsigned*).
    game: (positive, unsigned, unsigned*, unsigned*, positive).
    move: (unsigned, positive) -> unsigned*.
    writeMessage: (unsigned, unsigned*, unsigned*).
    writeVariants: (unsigned).
    subset: (positive, unsigned*) -> unsigned* nondeterm.
    candidate: (positive) -> unsigned* nondeterm.
    isBad: (unsigned*) determ.
    toStr: (unsigned) -> string.
clauses
    subset(0, _) = []:- !.
    subset(N, [X | L]) = [X | subset(N - 1, L)].
    subset(N, [_ | L]) = subset(N, L).

    candidate(N) = L:-
        L = subset(N, diglist),
        not(isBad(L)).

    isBad(L):-
        num(0, L1, N),
        N <> count(L, L1),
        !.

    writeVariants(X):-
        num(X, L, N),
            write(L, " - ", N), nl,
        fail;
        succeed().

    game(N):-
        writef("Загадайте % различных чисел от 0 до 9\n", N),
        L = readDigits(N),
        write("\nНачинается игра\n"),
        game(N, 0, gen(N, length(diglist) + 0), L).

    game(N, X, L, L1):-
        L2 = move(X, N),
        K = count(L1, L2),
        write(L2, " - ", K), nl,
        assert(num(X, L2, K)),
        game(N, X, L, L1, K).

    game(N, X, L, L1, N):- !,
        writeMessage(X, L, L1).
    game(N, X, L, L1, K):-
        K > N div 2,
        !,
        game(N, X, L, L1).
    game(N, X, L, L1, _):-
        game(N, 1 - X, L1, L).

    move(0, N) = L:-
        write("\nХод компьютера\n"),
        L = candidate(N),
        !.
    move(_, N) = readDigits(N):-
        write("\nВаш ход\n"),
        writeVariants(1).

    toStr(0) = "компьютер":- !.
    toStr(_) = "человек".

    writeMessage(X, L, L1):-
        Player1 = toStr(X),
        Player2 = toStr(1 - X),
        writef("Выиграл %!\n% задумал %, % задумал %.\n",
            Player1, Player1, L, Player2, L1).

    run():-
        write("Количество угадываемых чисел: "),
        N = read(), clearInput(),
        game(N),
        _ = readLine().

Упражнения

  • Реализуйте вариант топологической игры "Ползунок", в котором игрок, замкнувший траекторию, проигрывает, а не выигрывает.
  • Реализуйте игру "Поле чудес" в режиме "пользователь загадывает, компьютер отгадывает".
  • Реализуйте игру "Быки о коровы" в режимах

  • пользователь загадывает, компьютер отгадывает;
  • компьютер загадывает, пользователь отгадывает;
  • загадывают оба, выигрывает тот, кто отгадает первым.
  • Реализуйте игру "Крестики-нолики" пользователя с компьютером.
  • Реализуйте игру в камни пользователя с компьютером. Имеется груда камней. За один ход можно взять от одного до трех камней. Игроки ходят по очереди. Проигрывает тот, кто берет последний камень.
  • Игра "Ним" заключается в следующем. Имеется n кучек, в которых лежат камни. Игроки ходят по очереди. За один ход игрок может взять любое ненулевое число камней из произвольной кучки. Напишите программу для игры пользователя с компьютером, в которой игрок, взявший последний камень

  • выигрывает;
  • проигрывает.
  • Реализуйте игру в баллы пользователя с компьютером. Компьютер запрашивает число n баллов, которые нужно набрать (например 20), а также несколько чисел, которые можно называть (например 2, 5, 6) и имя игрока, который ходит первым. Игроки по очереди называют одно из разрешенных чисел. Названные числа суммируются. Выигрывает участник, после хода которого сумма становится равной n или вынудивший противника превысить это число.
  • Реализуйте игру в 37 пользователя с компьютером. Игроки по очереди называют одно из чисел от 1 до 5, названные числа суммируются. Нельзя повторять число, которое назвал противник на предыдущем ходе. Выигрывает участник, получивший ровно 37 очков или вынудивший противника превысить это число.
  • Страницы:

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

    14.1. Топологическая игра "Ползунок"

    Топологическая игра "Ползунок" заключается в следующем. Игра ведется на решетке размером 5 на 6 клеток (рис. 14.1).

    (рис 14.1) Партия топологической игры "Ползунок"

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

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

        open core, console, console_native
    
    class facts
        humanCell: (coord).
    
    class predicates
        winMove: (positive, positive, coord*, coord*) nondeterm (i,i,i,o).
        step: (coord*, coord*, coord) nondeterm (i,o,o) (i,o,i).
        next: (coord, coord) nondeterm (i,o) (i,i).
        move: (positive, coord*).
        show: (coord*).
        loseMove: (coord*) determ.
    clauses
        loseMove(Polyline):-
            step(Polyline, _, Cell),
            list::isMember(Cell, Polyline),
            !.
    
        winMove(_Player1, _Player2, Polyline, Polyline1):-
            step(Polyline, Polyline1, Cell),
            list::isMember(Cell, Polyline).
        winMove(Player1, Player2, Polyline, Polyline1):-
            step(Polyline, Polyline1, _),
            not(winMove(Player2, Player1, Polyline1, _)).
    
        move(2, Polyline):-
            step(Polyline, Polyline1, Cell),
            list::isMember(Cell, Polyline),
            !,
            show(Polyline1),
            setLocation(coord(0, 14)),
            write("Компьютер выиграл!\n\n", Polyline1).
        move(2, Polyline):-
            winMove(2, 1, Polyline, Polyline1),
            !,
            move(1, Polyline1).
        move(2, Polyline):-
            step(Polyline, Polyline1, _),
            not(loseMove(Polyline1)),
            !,
            move(1, Polyline1).
        move(2, Polyline):-
            step(Polyline, Polyline1, _),
            !,
            move(1, Polyline1).
        move(1, Polyline):-
            show(Polyline),
            setLocation(coord(0, 14)),
            write(Polyline),
            std::repeat(),
                write("\n\nВаш ход.\nВведите новую клетку в виде"
                    " coord(2,3): "),
                hasDomain(coord, Cell),
                Cell = read(), clearInput(),
            step(Polyline, Polyline1, Cell),
            assert(humanCell(Cell)),
            !,
            if list::isMember(Cell, Polyline) then
                write("\nПоздравляю! Вы выиграли!")
            else
                move(2, Polyline1)
            end if.
        move(_, _).
    
        step([A, B | Polyline], [X, A, B | Polyline], X):-
            next(A, X),
            not(X = B).
        step(Polyline, list::append(Polyline, [X]), X):-
            [A, B | _] = list::reverse(Polyline),
            next(A, X),
            not(X = B).
    
        next(coord(X, Y), coord(X - 1, Y)):- X > 1.
        next(coord(X, Y), coord(X + 1, Y)):- X < 6.
        next(coord(X, Y), coord(X, Y - 1)):- Y > 1.
        next(coord(X, Y), coord(X, Y + 1)):- Y < 5.
    
        show(Polyline):-
            clearOutput(),
            foreach I = std::fromTo(1, 6) do
                setLocation(coord(3 * I, 0)), write(I)
            end foreach,
            foreach J = std::fromTo(1, 5) do
                setLocation(coord(0, 2 * J)), write(J)
            end foreach,
            Attr0 = getTextAttribute(),
            foreach coord(X, Y) = list::getMember_nd(Polyline) do
                Attr = if humanCell(coord(X, Y)), ! then 13 else 11 end if,
                setTextAttribute(Attr),
                setLocation(coord(3 * X, 2 * Y)), write("*")
            end foreach,
            setTextAttribute(Attr0).
    
        run():-
            write("Кто ходит первым? (1 - человек, 2 - компьютер): "),
            Player = read(), clearInput(),
            if Player = 1 then
                write("Введите начало отрезка в виде coord(3,3): "),
                hasDomain(coord, Cell1),
                Cell1 = read(), clearInput(),
                write("Введите конец отрезка: "),
                hasDomain(coord, Cell2),
                Cell2 = read(), clearInput(),
                next(Cell1, Cell2),
                assert(humanCell(Cell1)),
                assert(humanCell(Cell2)),
                move(2, [Cell1, Cell2])
            else
                move(1, [coord(3, 3), coord(4, 3)])
             end if,
             !,
            _ = readLine();
            write("Ошибка ввода"),
            _ = readLine().
    

    Предикат hasDomain/2 связывает переменную с именем домена. Предикат getTextAttribute возвращает текущие атрибуты текста окна консоли.

    Метод ван Эмдена может быть использован только для полей небольших размеров, так как перебор во время поиска выигрышного хода делается довольно большой. Подробный разбор, демонстрирующий этапы проектирования и компьютерной реализации топологической игры "Ползунок", как в консольных, так и в GUI-приложениях на языке Visual Prolog, проделал В. А. Юхтенко (PDC SPb) [12].

    14.2. Игра "Поле чудес"

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

        open core, console
    
    class facts
        word: (positive, string).
    clauses
        word(1, "пароход").
        word(2, "паровоз").
        word(3, "самолет").
        word(4, "аэроплан").
        word(5, "корабль").
        word(6, "поезд").
        word(7, "дирижабль").
        word(8, "зонд").
        word(9, "аэростат").
        word(10, "стратостат").
        word(11, "ракета").
        word(12, "электровоз").
    
    class facts
        number : positive := 0.
        currentWord : string := "".
        secretWord : string := "".
        attempt : positive := 0.
    
    class predicates
        human_machine: ().
        replace : (string, string) -> string.
    clauses
        human_machine():-
            No = math::random(number) + 1,
            word(No, Word),
            NormWord = string::toUpperCase(Word),
            currentWord := NormWord,
            Len = string::length(NormWord),
            secretWord := string::create(Len, "_"),
            std::repeat(),
                nl, write(string::replaceAll(secretWord, "_", " _ ")),
                write("\n\nНазовите слово или букву: "),
                String = string::toUpperCase(readLine()),
                attempt := attempt + 1,
                LenOfStr = string::length(String),
                if LenOfStr = 1, not(_ = string::search(secretWord, String))
                then if not(_ = string::search(currentWord, String)) then
                            write("\nНет такой буквы в этом слове.\n")
                        else secretWord := replace(secretWord, String) end if
                end if,
            ((string::isWhiteSpace(currentWord); String = NormWord),
            writef("\n\nСлово: %!\nКоличество попыток: %!", NormWord,
                attempt);
            LenOfStr > 1,
            writef("\n\nСлово: %\nВы назвали % и проиграли!",
                NormWord, String)),
            !.
        human_machine().
    
        replace(SecretWord, Letter) = replace(SecretWord1, Letter):-
            Pos = string::search(currentWord, Letter),
            !,
            currentWord := string::replacePart(currentWord, Pos, 1, " "),
            SecretWord1 = string::replacePart(SecretWord, Pos, 1, Letter).
        replace(SecretWord, _) = SecretWord.
    
        run():-
            write("Машина загадывает, человек угадывает!\n\n"),
            number := list::length([X || word(X, _)]),
            human_machine(),
            _ = readLine().
    

    Предикат toUpperCase/2 переводит все символы строки в верхний регистр. Предикат search/2 возвращает позицию подстроки в строке. Предикат replaceAll/3 заменяет в строке все вхождения одной подстроки другой подстрокой. Предикат replacePart/4 заменяет подстрокой заданное количество элементов с указанной позиции.

    14.3. Игра "Отгадай числа"

    "Отгадай числа" — это игра для двух участников, которая состоит в следующем. Игроки загадывают по n целых чисел в пределах от 0 до 9 включительно, числа не повторяются. Цель игры — найти набор чисел, который загадал противник. Порядок чисел в наборе значения не имеет. Участник называет n чисел. Если среди них более половины совпадают с числами, загаданными противником, то право хода остается за ним. В противном случае ход переходит к другому игроку. Побеждает игрок, который первым правильно назовет весь набор чисел, загаданный противником. Данная игра является одним из вариантов игры "Быки и коровы" [9], в котором имеются только "коровы".

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

        open core, console, list
    
    constants
        diglist : unsigned* = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0].
    
    class facts
        num: (unsigned, unsigned*, positive).
        digs : unsigned* := [].
        n : positive := 0.
    class predicates
        readDigits: (positive) -> unsigned*.
        add: (unsigned, unsigned*, unsigned* [out], positive, 
            positive [out]).
        gen: (positive, unsigned) -> unsigned*.
        gen: (positive, unsigned, unsigned*) -> unsigned*.
        count: (unsigned*, unsigned*) -> positive.
        f: (positive) -> string.
    clauses
        readDigits(N) = reverse(digs):-
            writef("Введите % % от 0 до 9 через пробел: > ", N, f(N)),
            digs := [], n := N,
            std::repeat(),
                try hasDomain(unsigned, X), X = read()
                catch _ do
                    clearInput(), write("\nПовторите попытку: > "), fail
                end try,
                add(X, digs, L, n, N1),
                digs := L, n := N1,
            n = 0,
            clearInput(),
            !.
        readDigits(_) = [].
    
        f(N) = "чисел":-
            N > 4,
            !.
        f(_) = "числа".
    
        gen(N, R) = reverse(gen(N, R, [])).
    
        gen(N, R, L) = gen(N1, R, L1):-
            N > 0,
            !,
            add(math::random(R), L, L1, N, N1).
        gen(_, _, L) = L.
    
        add(X, L, [X | L], N, N - 1):-
            X < 10,
            not(isMember(X, L)),
            !.
        add(_, L, L, N, N).
    
        count(L, L1) = length(filter(L, {(X):- isMember(X, L1)})).
    
    class predicates
        game: (positive).
        game: (positive, unsigned, unsigned*, unsigned*).
        game: (positive, unsigned, unsigned*, unsigned*, positive).
        move: (unsigned, positive) -> unsigned*.
        writeMessage: (unsigned, unsigned*, unsigned*).
        writeVariants: (unsigned).
        subset: (positive, unsigned*) -> unsigned* nondeterm.
        candidate: (positive) -> unsigned* nondeterm.
        isBad: (unsigned*) determ.
        toStr: (unsigned) -> string.
    clauses
        subset(0, _) = []:- !.
        subset(N, [X | L]) = [X | subset(N - 1, L)].
        subset(N, [_ | L]) = subset(N, L).
    
        candidate(N) = L:-
            L = subset(N, diglist),
            not(isBad(L)).
    
        isBad(L):-
            num(0, L1, N),
            N <> count(L, L1),
            !.
    
        writeVariants(X):-
            num(X, L, N),
                write(L, " - ", N), nl,
            fail;
            succeed().
    
        game(N):-
            writef("Загадайте % различных чисел от 0 до 9\n", N),
            L = readDigits(N),
            write("\nНачинается игра\n"),
            game(N, 0, gen(N, length(diglist) + 0), L).
    
        game(N, X, L, L1):-
            L2 = move(X, N),
            K = count(L1, L2),
            write(L2, " - ", K), nl,
            assert(num(X, L2, K)),
            game(N, X, L, L1, K).
    
        game(N, X, L, L1, N):- !,
            writeMessage(X, L, L1).
        game(N, X, L, L1, K):-
            K > N div 2,
            !,
            game(N, X, L, L1).
        game(N, X, L, L1, _):-
            game(N, 1 - X, L1, L).
    
        move(0, N) = L:-
            write("\nХод компьютера\n"),
            L = candidate(N),
            !.
        move(_, N) = readDigits(N):-
            write("\nВаш ход\n"),
            writeVariants(1).
    
        toStr(0) = "компьютер":- !.
        toStr(_) = "человек".
    
        writeMessage(X, L, L1):-
            Player1 = toStr(X),
            Player2 = toStr(1 - X),
            writef("Выиграл %!\n% задумал %, % задумал %.\n",
                Player1, Player1, L, Player2, L1).
    
        run():-
            write("Количество угадываемых чисел: "),
            N = read(), clearInput(),
            game(N),
            _ = readLine().
    

    Упражнения

  • Реализуйте вариант топологической игры "Ползунок", в котором игрок, замкнувший траекторию, проигрывает, а не выигрывает.
  • Реализуйте игру "Поле чудес" в режиме "пользователь загадывает, компьютер отгадывает".
  • Реализуйте игру "Быки о коровы" в режимах

  • пользователь загадывает, компьютер отгадывает;
  • компьютер загадывает, пользователь отгадывает;
  • загадывают оба, выигрывает тот, кто отгадает первым.
  • Реализуйте игру "Крестики-нолики" пользователя с компьютером.
  • Реализуйте игру в камни пользователя с компьютером. Имеется груда камней. За один ход можно взять от одного до трех камней. Игроки ходят по очереди. Проигрывает тот, кто берет последний камень.
  • Игра "Ним" заключается в следующем. Имеется n кучек, в которых лежат камни. Игроки ходят по очереди. За один ход игрок может взять любое ненулевое число камней из произвольной кучки. Напишите программу для игры пользователя с компьютером, в которой игрок, взявший последний камень

  • выигрывает;
  • проигрывает.
  • Реализуйте игру в баллы пользователя с компьютером. Компьютер запрашивает число n баллов, которые нужно набрать (например 20), а также несколько чисел, которые можно называть (например 2, 5, 6) и имя игрока, который ходит первым. Игроки по очереди называют одно из разрешенных чисел. Названные числа суммируются. Выигрывает участник, после хода которого сумма становится равной n или вынудивший противника превысить это число.
  • Реализуйте игру в 37 пользователя с компьютером. Игроки по очереди называют одно из чисел от 1 до 5, названные числа суммируются. Нельзя повторять число, которое назвал противник на предыдущем ходе. Выигрывает участник, получивший ровно 37 очков или вынудивший противника превысить это число.
  • Вернуться к учебному плану