Анимация движения символов в консоли

Все исходники /  Язык программирования F# /  OS Windows /  Desktop /  Исходники приложений / Анимация движения символов в консоли

Анимация в консольном окне

Анимация движения символов в консольном окне

Консольное окно прекрасный инструмент для тестирования программных алгоритмов и изучения языка программирования F#. Библиотека платформы .NET предоставляет удобную оболочку для работы с консольным окном. Консоль не требуется никаких элементов управления - написал программный код и запускай программу.

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

Структура программы анимации символов

Исходный код разделён на три функциональных модуля F#:
  • Eraser - содержит два типа: запись Coordinates для хранения координат и класс Rectangle формирующий прямоугольную фигуру из символов.
  • Game - обеспечивает анимацию движения прямоугольника символов.
  • Program - отвечает за запуск и закрытие программы, предоставляет интерфейс взаимодействия с пользователем при помощи клавиатуры.

Модуль Eraser

Модуль F# Eraser предназначен для формирования из одиночных символов фигуры прямоугольника. Координаты каждого символа в пределах консольного окна хранятся в полях записи Coordinates. Ключевое слово mutable перед именами полей определяет поля записи как переменные, у таких полей можно изменять значения оператором <-. Изменяемые поля необходимы для изменения хранимых координат при перемещении символов.
type Coordinates = { mutable row: int; mutable col: int }

// Пример изменения координат:
let pos: Coordinates  = { row = 0; col = 0 } // создание экземпляра записи
// Присвоение полям новых координат.
pos.row <- 12
pos.col <- 5

Прямоугольник из символов создаётся классом Rectangle. Конструктор класса принимает два параметра: число строк и столбцов. Эти размерности используются в классе для создания двумерного массива arrayPos. Двумерный массив для элементов типа Coordinates хранит координаты каждого символа в группе. В классе Rectangle имеется открытый метод Move direction для передвижения фигуры из символов на один шаг в указанном направлении.

Стирание символа основывается на печатании пробела по координатам символа. Алгоритм передвижения состоит из чередования циклов стирания части символов и рисования их по новым координатам:
  • - при вертикальных перемещениях стираются верхний или нижний ряд символов, затем изменяются на единицу координаты строк, далее рисуются символы в новом расположении по вертикали;
  • - при горизонтальных перемещениях стираются первый или последний столбец символов, затем изменяются на единицу координаты столбов, далее рисуются символы в новом расположении по горизонтали.

Rectangle умеет передвигать символы только на один шаг. Свойства класса Pos, Right, Left, Top, Bottom выдают информацию о координатах символов, и также крайних рядов и столбцов для вычисления касания помех и целей. Создание непрерывной анимации не входит в функции класса.

type Rectangle(numrows: int, numbercols: int) =
    // Одиночный символ группы.
    let symbol = "●"
    // Координаты символов по рядам и столбцам.
    let arrayPos: Coordinates[,]=
        Array2D.create numrows numbercols { row = 0; col = 0 }
    // Установка позиция курсора для рисования символа
    // в любом месте консольного окна.
    let cursorpos pos =
        Console.CursorLeft <- pos.col
        Console.CursorTop <- pos.row
    // Рисование символа в данной координате.
    let drawsymbols pos =
        cursorpos pos
        printf "%s" symbol
    // Стирание символа в данной координате.
    let erasesymbols pos =
        cursorpos pos
        printf "%s" " "
    // Стирание необходимых рядов и столбцов.
    let erase direction =
        match direction with
        | 1 -> // erase right
            for i = 0 to numrows - 1 do
                let leftcol = arrayPos.[i, 0]
                erasesymbols leftcol
        | 2 -> // erase left
            for i = 0 to numrows - 1 do
                let rightcol = arrayPos.[i, numbercols - 1]
                erasesymbols rightcol
        | 3 -> // erase down
            for i = 0 to numbercols - 1 do
                let toprow = arrayPos.[0, i]
                erasesymbols toprow
        | 4 -> // erase up
            for i = 0 to numbercols - 1 do
                let bottomcol = arrayPos.[numrows - 1, i]
                erasesymbols bottomcol
        | _ -> ()

    let lastIndex dimension = arrayPos.GetLength(dimension) - 1
    // Инициализация прямоугольника символов.
    do
        for row = 0 to numrows - 1 do
            for col = 0 to numbercols - 1 do
                arrayPos.[row, col] <- { row = row; col = col }
                drawsymbols arrayPos.[row, col]

    // Получение координат прямоугольника.
    member x.Pos = arrayPos

    // Одномерные массивы крайних символов для каждой стороны.
    // Крайние ряды и столбцы для проверки касания.
    member x.Right = arrayPos[*, lastIndex(1)]
    member x.Left = arrayPos[*, 0]
    member x.Top = arrayPos[0, *]
    member x.Bottom = arrayPos[lastIndex(0), *]

    // Передвижение на один шаг в указанном направлении.
    member x.Move direction =

        let mutable x = 0
        let mutable y = 0

        // Определитель направления движения.
        match direction with
        // направо
        | 1 when arrayPos.[0, lastIndex (1)].col < (Console.WindowWidth - 1) -> x <- 1
        // налево
        | 2 when arrayPos.[0, 0].col > 0 -> x <- -1
        // вниз
        | 3 when arrayPos.[lastIndex (0), 0].row < Console.WindowHeight -> y <- 1
        // вверх
        | 4 when arrayPos.[0, 0].row > 0 -> y <- -1
        | _ -> ()

        // Передвижение только если выбрано реальное направление.
        if x <> 0 || y <> 0 then
            erase direction

            // Каждый символ передвигаем на шаг в указанном направлении.
            // Функция iter применяется к каждому элементу массива.
            arrayPos
            |> Array2D.iter
                (fun pos ->
                    pos.col <- pos.col + x
                    pos.row <- pos.row + y
                    drawsymbols pos)

Модуль создания анимации

Модуль содержит только один класс с одноименным названием. Первичный конструктор класса определяет скорость анимации и количество символов в прямоугольнике по рядам и столбцам. Класс Game обеспечивает непрерывную анимацию движения. Движок анимации построен на таймере, создающем события через определенные промежутки времени. Game владеет объектом класса Rectangle, описанным выше. Метод последнего Move direction непрерывно вызывается в событии таймера, таким образом создаётся анимационная иллюзия движения. Задавая различные интервалы срабатывания таймера можно изменять скорость движения символов.

После шага группы символов происходит проверка их координат в локальной функции checkPos (). В данном исходнике реализовано маятниковое движение "от стенки к стенке". Функция checkPos () получает актуальное положение символов и может использоваться для определения любого алгоритма реагирования касания помехи или цели.

Класс Game имеет открытые методы Right(), Left(), Down(), Up() для создания интерфейса взаимодействия с пользователем. Данные методы изменяют направление непрерывного движения соответственно их названию.

Программный код модуля Game:
module Game

open System
open System.Timers

// В конструкторе определяется интервал кадров анимации,
// количество строк и столбов в группе символов.
type Game(interval, numrows, numcols) =
    // Объект группы символов.
    let eraser = Eraser.Rectangle(numrows, numcols)
    // Таймер для создания анимации.
    let timer =
        new Timer(Interval = interval, Enabled = true)
    // Указатель направления движения.
    let mutable direction = 0
    // Получение актуальных координат всех анимированных символов. 
    let checkPos () =
        let eraserpos = eraser.Pos
        // Достигли правой стенки, движение обратно к левой.
        for i = 0 to eraser.Right.Length - 1 do
            if eraser.Right[ i ].col = Console.WindowWidth - 1 then
                direction <- 2

        for i = 0 to eraser.Left.Length - 1 do
            if eraser.Left[ i ].col = 0 then
                direction <- 1
        // Достигли верха переключаем на движение вниз.
        for i = 0 to eraser.Top.Length - 1 do
            if eraser.Top[ i ].row = 0 then
                direction <- 3

        for i = 0 to eraser.Bottom.Length - 1 do
            if eraser.Bottom[ i ].row = Console.WindowHeight then
                direction <- 4

    // Функция события таймера.
    let timerClick (source: Object) (e: System.Timers.ElapsedEventArgs) : unit =
        // Перемещение на шаг.
        eraser.Move direction
        // Получение актуальных координат символов.
        checkPos ()

        // Для надёжного скрытия курсора.
        // При изменении размеров окна курсор вновь появляется.
        Console.CursorVisible <- false

    // Добавление обработчика события таймера.
    do timer.Elapsed.AddHandler(timerClick)

    // Методы взаимодействия с пользователем.
    member x.Right() = direction <- 1
    member x.Left() = direction <- 2
    member x.Down() = direction <- 3
    member x.Up() = direction <- 4

Модуль запуска и закрытия программы

Модуль Program не имеет классов. Здесь выполняются предварительные настройки консольного окна. Далее создаётся объект класса Game с числовыми параметрами инициализации программы-игры. Затем запускается условно-бесконечный цикл прослушивания нажатых клавиш. Нажатия на клавиши-стрелки определяют направления движения символов, нажатие на пробел останавливает игру и закрывает окно программы.

Консольное приложение имеет неявную точку входа. В модуле нет функции main args с атрибутом [<EntryPoint>], что означает выполнение всех модулей программы по порядку. Такая особенность появилась в .NET6. Но при необходимости можно явно определить точку входа.

Программный код модуля Program:
open System
open System.Text

// --- Предварительные настройки консольного окна ---
// Вывод символов кодировки UTF-8.
Console.OutputEncoding <- Encoding.UTF8
// Скрытие мигающего курсора.
Console.CursorVisible <- false
// Установка цвета фона и символов.
Console.BackgroundColor <- ConsoleColor.Blue
Console.ForegroundColor <- ConsoleColor.Black
// Обновление окна.
Console.Clear()

// Объект с определёнными параметрами игры:
// интервал таймера, количество строк и столбцов.
let game = Game.Game(100, 5, 10)

let mutable exit = ConsoleKey.A

// Условно-бесконечный цикл игры.
while exit <> ConsoleKey.Spacebar do
    let action = Console.ReadKey(true)

    if action.Key = ConsoleKey.RightArrow then
        game.Right()

    if action.Key = ConsoleKey.LeftArrow then
        game.Left()

    if action.Key = ConsoleKey.DownArrow then
        game.Down()

    if action.Key = ConsoleKey.UpArrow then
        game.Up()

    exit <- action.Key

Исходник программы

Исходник написан в среде программирования MS Visual Studio 2022, платформа .NET6. На основе исходника можно создавать небольшие программы-игры для консольного окна. Функция определения актуальной позиции символов класса Game позволяет создавать разнообразные по логике игры.

В архивном файле исходник и файл программы для ознакомления.

Скачать исходник

  • Файл: Eraser-vs17.zip
  • Размер: 1102 Кбайт
  • Загрузки: 9