Оглавление "Программирование для игр"

Игра в дартс неплохо развивает навыки устного счёта, но идея “подтянуть” смартфон для подсчёта остатка кажется неплохой идеей. Пусть, как пел Сыроежкин в фильме "Приключения Электроника" - вкалывают роботы. Смартфон - не робот, но тоже пусть отрабатывает вложения.

Задача. Создать приложение голосового помощника при игре в дартс, сообщающий остаток после голосовой команды.

На занятии Приложение ”Умный игральный кубик” мы создали приложение для Android с голосовым управлением для имитации игрального кубика. Используем его в качестве основы.

Распишем задачу по шагам.

В начале игры у нас есть 501 очко. Для простоты можно называть это начальный остаток. Голосовая команда будет состоять из префикса в биде буквы "o" и числа очков в текущем подходе: "o180", "о57" и т.п. После получения голосовой команды нужно выделить из данных команды число, вычесть его из остатка и сообщить новый остаток. Если остаток меньше нуля, то попытка не засчитывается из-за перебора. После окончания игры звучит фраза ”конец игры”.

Распишем алгоритм:

ВсегоОчков = 501

Функция ПриПолучениииКоманды

    Временный результат = Вызвать ПолучитьОстаток (Очки)

    Если ВременныйРезультат = 0 Тогда Сказать “конец игры”
    Если ВременныйРезультат > 0 Тогда ВсегоОчков = ВременныйРезультат и Сказать ВсегоОчков
    Если ВременныйРезультат < 0 Тогда Сказать “перебор”

КонецФункции

Функция ПолучитьОстаток (Очки)

    Вернуть Результат = ВсегоОчков – Очки

КонецФункции

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

Многие разработчики создают алгоритмы из головы и не любят “лишнюю писанину”. В случае простого приложения такой подход похож на поход в продуктовый магазин без списка того, что нужно купить. Купил лишнее, не то или что-то забыл купить – ничего страшного. Но в при разработке сложного приложения ситуация станет похожа на попытку построить дом без плана. Что настроит прораб в этом случае многим известно.

Существует несколько рекомендаций при написании кода, одна из которых звучит так:

старайтесь делать самодокументирующийся код и писать функции для выполнения только одного действия.

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

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

Полный код проекта для DroidScript показан ниже.

// значение громкости звонка и музыки
var oldRingVolume = null;
var oldMusicVolume = null;

// остаток
var totalPoints = 501; 

// при старте приложения
function OnStart() 
{
    // выключаем вывод отладочной информации
    app.SetDebugEnabled( false ); 
    
    // выключаем действие по умолчанию для системной кнопки Back
    app.EnableBackKey( false ); 
    
    lay = app.CreateLayout( "linear", "VCenter,FillXY" );
    btn = app.AddButton( lay, "Старт", 0.3 );
    btn.SetOnTouch( initGame );
    
    app.AddLayout( lay );
    
    // создаём объект синтезатора речи
    speech = app.CreateSpeechRec( "NoBeep,Parxtial" );
    
    // обработчик распознавания голосовой команды
    speech.SetOnResult( speech_OnResult ) 
    
    // обработчик ошибок
    speech.SetOnError( speech_OnError ) 

    // запоминаем значение громкости звонка и музыки
    oldRingVolume = app.GetVolume( 'ring' ); 
    oldMusicVolume = app.GetVolume( 'music' );
    
    // установка громкости
    app.SetVolume( 'ring', 0) ;
    app.SetVolume( 'music', 0.5);
    
    // выводим речь в поток для музыки
    app.TextToSpeech( "Для начала игры скажите старт", 1, 1.2, Listen, 'music' )
    
}

// инициализация игры
function initGame() 
{
    
    app.SetVolume( 'music', 0.5);
    app.TextToSpeech( "Игра началать", 1, 1.2, Listen, 'music' );
        
    totalPoints = 501;
    btn.SetEnabled = false;
}

// включаем прослушивание эфира
function Listen() 
{
    
    speech.Recognize();
}

function say( text ){ 
    
     app.SetVolume( 'music', 0.5 );
    
    app.TextToSpeech( text, 1,1.3, Listen, 'music' );
}

function checkPoints( points )
{
    
    if( points == 0 ){
        
        say( "конец игры" );
        
        btn.SetEnabled = true;
        
    } else if ( points < 0 ){
        
        say( "перебор" );
        
    } else {
        
        totalPoints = points;
        say( totalPoints );
    }
}

function speech_OnResult( results, partial )
{
    var cmd = results[0].toLowerCase()
    
    app.ShowPopup( cmd );
    
    if( cmd == "старт" ){
        
        initGame();
        
    } else if( cmd[0] == "о" || cmd[0] == "o" ){
        
        // получение числа из строки
        var value = parseInt( cmd.match( /\d+/ ) ); 
        
        if( isNaN(value) ) {
        
            say( "Повторите команду" );
            
        } else {
            
            var tmpPoints = Number.parseInt( totalPoints - value );
        
            checkPoints( tmpPoints );
        }
        
    } else {
        
        speech.Recognize()
    }
}

// обработка ошибок голосового ввода
function speech_OnError( error ) 
{
    if( !speech.IsListening() ){
        
        speech.Recognize()
    }    
}    

// деактивация приложения
function OnPause() 
{

    app.SetVolume( 'ring', oldRingVolume ) ;
    app.SetVolume( 'music', oldMusicVolume );
}    

// активация приложения
function OnResume() 
{

    app.SetVolume( 'ring', 0) ;
    app.SetVolume( 'music', 0.5);
}    

// выход из приложения по системной кнопке Back
function OnBack() 
{
    app.SetVolume( 'ring', oldRingVolume ) ;
    app.SetVolume( 'music', oldMusicVolume );

    app.Exit();
}

Для вывода речи используется поток "music" для воспроизведения мультимедиа, что позволило исключить из проекта таймер. Звуковые сигналы перед и после распознавания голоса выводятся через поток сингнала звонка, громкосчть которого убирается в 0. В базовой функциональности DroidScript отстствует обработчик события завершения работы приложения. Это не даёт возможность восстановить громкость звонка и мультимедиа на телефоне после закрытия приложения слайдом.

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