ПРОФЕССИОНАЛЬНЫЙ ПОДХОД
(C) Збитнев В. А., 1992, 1993. (г. Новосибирск)
Применение статического генератора случайных чисел на примере
программы "ELITE"
Вероятно, уже все знают о том, что такое RND. Во многих игровых и учебных программах этот оператор используется для того, чтобы поведение компьютера в некоторых ситуациях было непредсказуемым. Эту роль прекрасно исполняет генератор случайных чисел на ZX-SPECTRUMe. Он относится к типу динамических, то есть таких, которые изменяют свое состояние независимо от того, когда вызывается этот генератор. Свое значение "спектрумовский" RND вычисляет, используя переменную SEED (23670 DEC), которая изменяется с течением времени.
Но так бывает не во всех компьютерах. Многие, наверное, знают работу RND на других версиях Бейсика для компьютеров типа "Ямаха" (MSX), IBM. Сколько бы раз Вы не вызывали бы RND в своей программе (например для рисования звездного неба), он выдает одни и те же числа после команды RUN, RANDOMIZE (в случае рисования звездного неба одна и та же картина будет на экране, если Вы остановите программу и снова запустите ее через RUN).
Такой вид RND-генератора называется статическим. Он плох для непредсказуемых действий, но он прекрасно может работать в качестве хранилища огромного объёма данных, не требующих в памяти места. Но самое главное - эта информация будет постоянной, сколько бы раз эти данные не были бы затребованы. Простейший пример такого RND -генератора на ассемблере
RND: LD |
A,(R1) |
LD |
D,A |
LD |
A,(R2) |
LD |
(R1),A |
ADD |
A,D |
LD |
D,A |
LD |
A,(R3) |
LD |
(R2),A |
ADD |
A,D |
RLCA |
|
LD |
(R3),A |
RET |
|
R1, R2, R3 - переменные
В регистре A будет содержаться случайное число (0-255). Но подобный генератор случайных чисел все время будет генерировать очередные случайные числа. Теперь создадим программу, подобную RANDOMIZE в Бейсике.
RAND: LD |
A,5 |
|
LD |
(R1), |
A |
LD |
A,90 |
|
LD |
(R2), |
A |
LD |
A,37 |
|
LD |
(R3), |
A |
RET |
|
|
Теперь, после вызова подпрограммы RAND некий указатель встаёт на начало некой таблицы, в которой хранится бесконечное количество байтов, которые и извлекаются из неё подпрограммой RND. Где это можно использовать? К примеру, в хорошо известной игре "ЖИЗНЬ" ("LIFE") нужна матрица со случайно расположенными в ней клетками. Вводить через DATA весь массив неудобно, медленно и не практично. Если заполнять массив через обыкновенный RND, то возникает проблема с тем, как снова воспроизвести картину, если это понадобится. Опять прибегаем к DATA. Но есть более практичный способ: задание массива через статический RND, который будет выдавать те же самые значения, что и в первый раз, стоит только вызвать RAND.
В игровых программах с большим количеством комнат таким путем можно запоминать все комнаты, как комбинацию трех (или другого числа байтов), в данном случае - 5, 90, 37. Стоит их только изменить и Вы имеете опять новую комнату. Программа "Demons" использует как раз такой способ, она чем-то напоминает "ЖИЗНЬ", но выглядит в 7 раз красивее и эффектнее. Попробуйте, не пожалеете!
Программа 1 Demons
Сброс генератора случайных чисел в исходное положение Заполнение экрана атрибутами через RND Старый массив Новый массив
768 байтов - размер области экранных атрибутов
30000
RAND
RNDSC
IX,22528
HL,31000
BC,768
;Если слева, справа, сверху или снизу может кто-то ;"съесть" этот атрибут, то пусть "съедает".
A,(HL)
A
7
(IX+1)
Z,SEAT
(IX-1)
Z,SEAT
(IX+32)
Z,SEAT
(IX-32)
Z,SEAT
LD
INC
AND
CP
JP
CP
JP
CP
JP
CP
JP
Продолжить обработку массива
CALL COPY RET
"Съедаем" текущий атрибут
Копирование нового массива в старый
HALT LD LD LD
LDIR RET LD LD
CALL
AND
LD
INC
DEC
LD
OR
JP
CALL RET
HL,31000 BC,768 RND1 7
(HL),A
HL
BC
A,B
C
NZ,FILL COPY
Заполняем новый массив байтами с числовым значением от 0 до 7
Копирование массива в старый массив
LD |
A,5 |
LD |
(R1),A |
LD |
A,90 |
LD |
(R2),A |
LD |
A,37 |
LD |
(R3),A |
LD |
HL,16384 |
LD |
DE,16385 |
LD |
BC,6144 |
LD |
(HL),255 |
LDIR |
|
RET |
|
LD |
A,(R1) |
LD |
D,A |
LD |
A,(R2) |
LD |
(R1),A |
ADD |
A,D |
LD |
D,A |
LD |
A,(R3) |
LD |
(R2),A |
ADD |
A,D |
RLCA |
|
LD |
(R3),A |
RET |
|
5, 90, 37 - байты, означающие параметры экрана
Генератор случайных чисел
R1 DEFB 0 ;Исходные значения параметров
R2 DEFB 0
R3 DEFB 0
Следующая область применения - формирование статичной (неменяющейся) информации. Возьмем к примеру игру "ELITE". Откуда компьютер с таким небольшим объемом памяти берет названия и координаты всех 256 звезд, в каждой из восьми галактик, да ещё и неизвестно сколько всего этих галактик. Где хранятся данные о ценах на каждой из планет, её техническом уровне, общественном строе и населении.
Разумеется, это работа статического RND. Если Вы наберете программу 2 на Ассемблере или 2.1 на БЕЙСИКе, то сможете получить данные о всех названиях планет и их координатах. Остальная информация каким-то образом хранится в 6 основных байтах и может быть образована одним из 4 RND, вызываемых последовательно для каждой звезды. Может быть, удастся расшифровать и остальные данные.
Итак, статический RND может хранить в себе информацию. Можно, к примеру, сделать слоги русскими, задействовать остальные байты для русских имен, для пола, возраста, адреса и пожалуйста: у Вас есть картотека на 10000 человек, работающих на космическом корабле. Статический RND может быть применим и для динамической информации. Примером может служить игра SENTINEL, в которой все ландшафты имеют свой номер, а задание их в памяти невозможно - слишком расточительно. Но в процессе игры Вы можете некоторым образом изменять эту информацию уже в матрице, созданной этим RND.
Программа 2.1
Заполняет массив данными о планете, начиная с адреса 40000 в следующем
формате:
| Имя звезды | X | Y |
| 8 байтов |1б.|1б.| = 10 байтов на планету
30000
RAND
B,0
;256 звёзд ;Считать имя в NAME
STARS PUSH BC
CALL LDNAME
CALL SAVNM ;Записать имя из NAME в массив, на который
POP BC ; указывает MASSV
DJNZ STARS RET
LDNAME LD HL,NAME ;Позиция в имени указывается переменной POS
LD (POS),HL
LD B,9 ;Очистили 9 байтов
CLEAR LD (HL),0 ;Имя будет равно 8 символам
INC HL
DJNZ CLEAR
LD A,(D0+1)
LD (D1),A ;Запишем координату Y
LD A,(D2+1)
LD (D3),A ;Координата X звезды
LD A,(D0) ;Условие, при котором 6-ой бит указывает на то,
AND 64 ; в слове нет последнего слога
PUSH AF
CALL LDSLG ;Три слога
CALL LDSLG
CALL LDSLG
POP AF
JR Z,RND2 ;Если нет последнего слога, то уход на холостой RND
LDSLG LD A,(D4+1) ;Условие отсутствия слога:
AND 31 ;номер слога = 0, т.е. слог "AL" - невозможен
JR Z,RND2
LD HL,SLOGS
ADD A,A ;Номер слога*2
LD E,A
LD D,0
LD HL,DE ;Находим позицию слога в SLOGS
LD A,(HL) ;Считываем первый символ слога
INC HL
PUSH HL
LD HL,(POS)
LD (HL),A ;Записываем первый символ по адресу POS
INC HL ;Следующий символ слога
EX DE,HL
POP HL
LD A,(HL) ;Если символ равен "?", то не записывать
CP 63 ;урезанный слог
JP Z,SAVPS
LD (DE),A ;Записываем 2-й символ слога
INC DE
SAVPS LD (POS),DE ; Сохраняем позицию
RND2 LD HL,(D0) ;Сама процедура RND
LD DE,(D2)
ADD HL,DE
EX DE,HL
LD HL,(D2)
LD (D0),HL
LD HL,(D4)
LD (D2),HL
ADD HL,DE
LD (D4),HL ; D0<—D2< — D4<—D0+D2+D4 RET
SAVENM LD HL,NAME ;Записываем имя в массив
LD DE,(MASSV)
LD BC,8 LDIR
LD A,(D1) ;Считываем Y
SRL A ;Преобразуем в нормальный вид
XOR 127
|
LD |
(DE),A |
|
INC |
DE |
|
LD |
A,(D3) |
|
LD |
(DE),A |
|
INC |
DE |
|
LD |
(MASSV),DE |
|
RET |
|
RAND |
LD |
HL,RNDDAT |
|
LD |
DE,D0 |
|
LD |
BC,6 |
LDIR |
LD |
HL,40000 |
|
LD |
(MASSV),HL |
|
RET |
|
D0 |
DEFW |
00 |
D1 |
DEFW |
00 |
D2 |
DEFW |
00 |
D3 |
DEFW |
00 |
D4 |
DEFW |
00 |
MASSV |
DEFW |
00 |
NAME |
DEFW |
|
SLOGS |
DEFM |
"ALLEXEGEZA |
|
DEFM |
"CEBISOUSES |
|
DEFM |
"ARMAINDIRE |
|
DEFM |
"A?ERATENBE |
|
DEFM |
"RALAVETIED |
|
DEFM |
"ORQUANTEIS |
|
DEFM |
"RION" |
RNDDAT |
DEFB |
74 |
|
DEFB |
90 |
|
DEFB |
72 |
|
DEFB |
2 |
|
DEFB |
83 |
|
DEFB |
183 |
;Записываем в массив
;Считываем X и записываем в массив
;Запоминаем позицию в массиве
;Копируем данные о галактике в массив RND
;61...66 байты в отгружаемом блоке состояния ELITE
Переменные
Программа 2.1
Печать имён планет всех галактик ELITE.
10 PAPER 0: INK 6: BORDER 0
20 CLS: PRINT "ELITE SPACE.....1-STAR NAMES.....2-PLOTS OF STARS
30 LET k$=INKEY$: IF k="1" THEN GO TO 60 40 IF k$="2" THEN GO TO 200 50 GO TO 30
Подпрограмма вывода названий.
60 LET n=0: GO SUB 1070: GO SUB 1010: REM установка генератора случайных чисел в исходное положение и установка стринга n$, содержащего слоги всех названий планет.
70 CLS: PRINT INK 5; "NO NAME"
80 FOR f=1 TO 20
90 LET r=d0-INT (d0/256)*256
100 GO SUB 2000: REM В строках 100...120 и 150 содержатся четыре вызова генератора для одной звезды.
110 GO SUB 2000
120 GO SUB 2000
130 IF r>127 THEN LET r=r-128
140 IF r-64<0 THEN GO SUB 1030: REM Если 6-ой бит равен нулю, то производится холостой вызов RND и слово становится короче на один слог.
150 GO SUB 2000
170 LET n=n+1: IF n=256 THEN STOP: REM Если число звёзд равно 256, то конец работы.
180 NEXT f: BEEP .1, 30: PAUSE 0: GO TO 70: REM Формируется следующее имя
Вывод позиций звёзд
200 CLS: PLOT 0,23: DRAW 255,0: DRAW 0,129
205 DRAW -255,0: DRAW 0,129
210 GO SUB 1010
220 FOR f=0 TO 256
230 LET y=INT (d0/256): LET y=INT (y/2)+24
240 LET x=INT (d2/256)
250 PLOT OVER 1;x,175-y: REM Вывод звезды
260 GO SUB 1030: GO SUB 1030
270 GO SUB 1030: GO SUB 1030
280 NEXT f: STOP
1000 REM Подпрограмма инициализации статического генератора* 1010 RESTORE: READ d0, d2, d4: RETURN
1020 REM *Статический генератор случайных чисел* 1030 LET s=d0+d2: IF s>65535 THEN LET s=s-65536
1040 LET s=s+d4: IF s>65535 THEN LET s=s-65536: REM s=d0+d2+d4 (двухбайтное логическое
сложение) 1050 LET d0=d2: LET d2=d4 1060 LET d4=s: RETURN
1065 REM *Генерация списка возможных слогов* 1070 LET n$="ALLEXEGEZACEBISOUSESARMAINDIREA?" 1080 LET n$=n$+"ERATENBERALAVETIEDORQUANTEISRION" 1090 RETURN
2000 REM *Выбор случайного слога*
2010 LET h=INT (d4/256): LET h=h-INT (h/32)*32
2020 IF h=0 THEN GO SUB 1030: RETURN
2030 LET h=h*2: PRINT n$(h+1)
2040 LET h=h+1
2050 IF n$(h+1)="?" THEN GO SUB 1030: RETURN 2060 PRINT n$(h+1): GO SUB 1030: RETURN
3000 DATA 74+90*256 3010 DATA 72+2*256 3020 DATA 83+183*256
3030 REM этот блок данных соответствует галактике с параметрами 74, 90, 72, 2, 83, 183 (байты 61...66 в блоке состояния программы ELITE)