gamedev Tutorials Программирование игр

Язык программирования Java для многих известен именно благодаря своим апплетам - особым программам, которые не могут нанести никакого вреда компьютеру на котором исполняются. Это огромное облегчение для пользователя - знать, что вот эта закачанная из Интернета программа гарантированно не является разносчиком вирусов.
Для обеспечения этих гарантий апплет сильно ограничен в своих правах - он, даже будучи запущенным с пользовательского компьютера, не может не только сохранять, но даже и читать файлы с диска этого же компьютера!
Хорошо, это гарантирует неприкосновенность и конфедициальность частных данных.
Но теперь ответьте, как такой программе сохранить свой регистрационный номер?

Почему именно shareware

выбрано в качестве примера решения этой неразрешимой задачи?

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

Лично я не удивлюсь, если однажды именно shareware станет единственной законной формой продажи программного обеспечения.

Это всё теория "в прекрасных стихах", а проза жизни такова, что пользователя так или иначе приходится (будем называть вещи своими именами) заставлять платить.

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

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

Решение невозможного

Апплет будет использоваться не сам по себе - он обычно исполняется в браузере, а ещё точнее: апплет включается в web-страницу - то есть обычный html-файл - и вот уже этот файл просматривается через браузер, заставляя апплет исполняться.

Понимаете, об апплете не следует думать, как о "вещи в себе" - он и запускающий его html-файл это единый программный комплекс.
А какими возможностями обладают html-документы?

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

Как готовят cookie

Приведу собственное решение. Оно заключается в своеобразной библиотеке cookie.js, которая легко подключается в любой html-документ строкой:
<script language="JavaScript" src="cookie.js"></script>
вставленной куда-нибудь в его начало, обычно между тегами .
Содержимое этой импровизированной библиотеки - пара функций, одна из которых сохраняет либо удаляет cookie (в зависимости от переданных ей параметров), а другая считывает cookie с подходящим именем. Вот их текст:
function saveCookie(szName, szValue, szDay){
var dtExpires=new Date()
var dtExpiryDate=""
if(szDay>0){//установить время хранения cookie в днях
	dtExpires.setTime(dtExpires.getTime()+szDay*24*60*60*1000)
}else{//установить стирание cookie
	dtExpires.setTime(dtExpires.getTime()-1)
}
dtExpiryDate=dtExpires.toGMTString()
document.cookie=szName + "=" + szValue + "; expires=" + dtExpiryDate
}

function loadCookie(szName){
var i=0
var nStartPosition=0
var nEndPosition=0
var szCookieString=document.cookie
while(i<=szCookieString.length){
	nStartPosition=i
	nEndPosition=nStartPosition + szName.length
	if(szCookieString.substring(nStartPosition, nEndPosition) == szName){
		nStartPosition=nEndPosition+1
		nEndPosition=document.cookie.indexOf(";", nStartPosition)
		if(nEndPosition < nStartPosition)
			nEndPosition = document.cookie.length
		return document.cookie.substring(nStartPosition, nEndPosition)
		break
	}
	i++
}
return ""
}

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

Если создаётся новый cookie с именем уже существующего - увы, старое содержимое будет безвозвратно затёрто без каких-либо предупреждений.
Cookie храниться в системе столько времени, сколько было определно при его создании (перезапись является созданием заново). Поэтому обдуманно отнеситесь к последнему входному параметру - ведь стоит установить уже минувшую дату и вместо создания Вы удалите запись!
Для удобства использования последний параметр вводится не в виде даты - а виде срока, иначе говоря числа дней. А дата вычисляется самой функцией. Причём если она меньше или равна 0 - то будет произведено удаление cookie.
Последней строкой в конце функции производится собственно вся работа: имя записи, затем её значение и, наконец, дата окончания хранения - все разделены точкой с запятой. Да, весь cookie - это одна запись, вдобавок число таких записей может быть ограничено системой - так что сохранить мы сможем не слишком много. Впрочем, для хранения регистрационных данных этих возможностей будет более чем достаточно.

С чем едят cookie

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

Если поле или метод апплета объявлены со спецификатором доступа public то оказывается сценарий JavaScript может обращаться к ним. Очевидно, что таким образом совсем нетрудно производить считывание информации.
Например, вот начало исходного текста апплета, содержащее объявления таких переменных:
import java.awt.*;
import java.applet.*;

public class Tarakan extends Applet implements Runnable{
	//регистрационные переменные считываются и сохраняются в cookie
	public String sName = null;
	public String sNik = null;
	public String sReg = null;
Теперь, в любом месте страницы, содержащий этот апплет, можно разместить фрагмент JavaScript, который будет использовать эти поля апплета. Нас наиболее интересует их сохранение в cookie. Неплохой идеей будет реализовать её через кнопку (предполая, что библиотека cookie.js включена в страницу ранее):
Для сохранения нажмите <input type="button"
value="Save"
onClick="saveCookie('TarakanKlugDevJugaRu',Tarakan.sName+'.'
+Tarakan.sNik+'.'+Tarakan.sReg+'.'+Tarakan.sLang+'.'+Tarakan.sLive+'.'
+Tarakan.sDinamit+'.'+Tarakan.sLevel+'.'+Tarakan.sLevelMaze+'.'
+Tarakan.sAllDoneMaze+'.'+Tarakan.sAllDoneDistance, 10*365)">
<! save it on 10 year>
Примечание: в реальном HTML-документе весь этот фрагмент должен быть написан в оджну строку - здесь разбивка на строки использована исключительно для удобочитаемости, но в реальности она могла бы вызвать ошибку периода исполнения на некоторых браузерах.

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

На мой взгляд оптимальным решением в данном случае оказывается генерация фрагмента html-текста "на лету" при помощи сценария JavaScript. Фокус в том, что все браузеры, встречая, в процессе считывания html, включённый в страницу сценарий, тут же начинают исполнять его. И, если этот сценарий содержит директивы по вписыванию html-текста - то в загруженной странице такой текст окажется на том месте, где был размещён сценарий.
Значит, если этот html-текст содержит тег <applet> - то апплет мы и увидим на готовой странице. Если же не полениться добавить к этому тегу и сопутствующие теги с необходимыми параметрами (в нашем случае это регистрационные данные) - то таким образом, задача передачи сохранённых данных апплету будет решена.

<script language="JavaScript">
<!--
var u="u"
var n="n"
var r="r"
var la="0"
var li="1"
var di="0"
var le="0"
var lm="0"
var am="0"
var ad="0"
var nStartPosition=0
var nEndPosition=0
var szTemp=loadCookie("TarakanKlugDevJugaRu")
if(szTemp!=""){//else no save
	nEndPosition = szTemp.indexOf(".", nStartPosition)
	u = szTemp.substring(nStartPosition, nEndPosition)
	nStartPosition = nEndPosition + 1
	nEndPosition = szTemp.indexOf(".", nStartPosition)
	n = szTemp.substring(nStartPosition, nEndPosition)
	nStartPosition = nEndPosition + 1
	nEndPosition = szTemp.indexOf(".", nStartPosition)
	r = szTemp.substring(nStartPosition, nEndPosition)
	nStartPosition = nEndPosition + 1
	nEndPosition = szTemp.indexOf(".", nStartPosition)
	la = szTemp.substring(nStartPosition, nEndPosition)
	nStartPosition = nEndPosition + 1
	nEndPosition = szTemp.indexOf(".", nStartPosition)
	li = szTemp.substring(nStartPosition, nEndPosition)
	nStartPosition = nEndPosition + 1
	nEndPosition = szTemp.indexOf(".", nStartPosition)
	di = szTemp.substring(nStartPosition, nEndPosition)
	nStartPosition = nEndPosition + 1
	nEndPosition = szTemp.indexOf(".", nStartPosition)
	le = szTemp.substring(nStartPosition, nEndPosition)
	nStartPosition = nEndPosition + 1
	nEndPosition = szTemp.indexOf(".", nStartPosition)
	lm = szTemp.substring(nStartPosition, nEndPosition)
	nStartPosition = nEndPosition + 1
	nEndPosition = szTemp.indexOf(".", nStartPosition)
	am = szTemp.substring(nStartPosition, nEndPosition)
	nStartPosition = nEndPosition + 1
	nEndPosition = szTemp.length
	ad = szTemp.substring(nStartPosition, nEndPosition)
}
document.write('<applet code=Tarakan.class name=Tarakan id=Tarakan width=440 height=440>')
document.write('<param name=user value="'+u+'">')
document.write('<param name=nik value="'+n+'">')
document.write('<param name=reg value="'+r+'">')
document.write('<param name=language value="'+la+'">')
document.write('<param name=live value="'+li+'">')
document.write('<param name=dinamit value="'+di+'">')
document.write('<param name=level value="'+le+'">')
document.write('<param name=levelmase value="'+lm+'">')
document.write('<param name=donemaze value="'+am+'">')
document.write('<param name=distance value="'+ad+'"></applet>')
}
//-->
</script>

Регистрационная форма

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

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

Java хорош компактностью - как исходного текста, так и конечного исполняемого кода. Существует история о том, как некий человек создал очень полезную по его мнению программу, умевшую показывать IP-адрес пользователя - но вот беда, он написал её на VB и получился монстр более мегабайта весом! Говорят, при известной ловкости, на C++ можно написать аналогичную утилиту размером всего в десятки килобайт. Это кажется превосходным результатом - но только до тех пор, пока вы не познакомитесь с Java.

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

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

Вот пример подобной регистрационной формы:
<HTML><meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<HEAD><TITLE>Tarakan registrator</TITLE>
<script language="JavaScript" src="cookie.js"></script>
</HEAD><BODY>
<h4 align=center>Регистрация</h4>
<form name="Person">
<table border=3 bgcolor=#cccccc align=center><tr><td>
<table border=0 bgcolor=#cccccc align=center>
<tr><th>Имя (пользователя):</th><td colspan=2><input type="text" name="user" value="" size="50"
        onChange="this.value=this.value"
        onFocus="this.select()"></td></tr>
<tr><th>Кличка (таракана):</th><td colspan=2><input type="text" name="nik" value="" size="50"
        onChange="this.value=this.value"
        onFocus="this.select()"></td>
<tr><th>Код (регистрации):</th><td><input type="text" name="reg" value="" size="7"
        onChange="this.value=this.value"
	onFocus="this.select()"></td>
<td><input type="button" value="Save"
onClick="saveCookie('TarakanKlugDevJugaRu', user.value+'.'+nik.value+'.'+reg.value+'.0.1.0.0.0.0.0', 10*365);">
<! сохранить на 10 лет>
</td></tr>
</table>
</td></tr></table>
</form>
</BODY></HTML>

Регистрационный код

Откуда возьмёт регистрационный код пользователь понятно - мы сообщим. А мы откуда его возьмём?

Нам нужен кодогенератор - и оказывается в Java уже имеется для этого всё необходимое.
Приведу пример реализации в виде апплета:
import java.awt.*;
import java.applet.*;

public class TarakanCode extends Applet{
	private TextField tfName;
	private TextField tfNik;
	private TextField tfCode;


	public void init(){
		this.setBackground(Color.lightGray);
		this.setForeground(Color.black);
		this.setLayout(new GridLayout(4, 2));
		Label labelName = new Label("Name (user)");
		tfName = new TextField(40);//для ввода имени, отчества, фамилии
		tfName.setEditable(true);
		Label labelNik = new Label("Nikname (tarakan)");
		tfNik = new TextField(40);//вряд ли кличка будет столь длинной
		tfNik.setEditable(true);
		Label labelCode = new Label("Code (result)");
		tfCode = new TextField(7);//здесь будет показан код
		tfCode.setEditable(false);//чтобы отличалось от полей ввода
// на самом деле предумышленно испортить сгенерированный код вводом в его поле нельзя
// потому что сейчас попытка ввода любого символа куда угодно вызывает пересчёт кода
		Label labelWarning0 = new Label("Все символы:");//предупреждения-рекомендации
		Label labelWarning1 = new Label("цифры или латынь!");//для оператора
		add(labelName);
		add(tfName);
		add(labelNik);
		add(tfNik);
		add(labelCode);
		add(tfCode);
		add(labelWarning0);
		add(labelWarning1);
	}//init


	public boolean handleEvent(Event event){
		if(event.id == Event.KEY_RELEASE && event.modifiers != Event.CTRL_MASK){
			//предположительно были введены новые значения в текст.поля
			//на всякий случай обсчитать все - хуже от этого не будет
			calcCode();
		}else return false;//вернуть что это сообщение не обработано
		return super.handleEvent(event);//этим простым трюком перекладывает отображение ввода на родителя
	}//handleEvent
	
	
	private void calcCode(){
		//здесь вычисляем код
		//...как именно? - каждый может решить это по-своему

		//показать результат
		tfCode.setText(new String(code));
	}//calcCode
	
}
В результате получаем полнофункциональное приложение для работы в любой оконной среде (Windows, Linux, etc) объёмом - пожалуйста, усядьтесь поудобнее - в 2 килобайта (а если алгоритм шифрования простой, то и меньше).
Если мне не изменяет память, то даже написанная целиком на ассемблере простейшая программа для Windows и то будет "потяжелее".

Примечание: в статье использованы фрагмены условно-бесплатной игры "Таракан"

© Rex
Игры и Java

PMG  8 февраля 2007 (c)  Rex