Главная » Статьи » Играем Blitz » Общее

Использование формул логики

Сначала - о двоичном исчислением, для тех кто с ним не знаком. В обычной, десятичной системе счисления, которую мы используем, для записи чисел используются десять знаков - 0123456789. Любое число можно разложить на разряды следующим образом: a * 10n + b * 10n-1 + ... + x * 102 + y * 10 + z. Например: 956 = 9 * 102 + 5 * 101 + 6 * 100. Число 10 называется ОСНОВАНИЕМ системы. Двоичная система счисления имеет основание 2, то есть любое чило может быть представлено так: a * 2n + b * 2n-1 + ... + x * 22 + y * 2 + z, например: 1011 = 1*23 + 0 * 22 + 1 * 21 + 1 * 20. Легко посчитать, что в десятичной системе счисления это число равно 8 + 2 + 1 = 11. Для записи чисел в двоичной системе счисления используются два знака - 0 и 1.

Классические формулы логики оперируют переменными только с двумя значениями - ИСТИНА (1) и ЛОЖЬ (0):

A B NOT A A AND B A OR B A XOR B
0 0 1 0 0 0
0 1 1 0 1 1
1 0 0 0 1 1
1 1 0 1 1 0

Логические операции над числами (которые имеют 32 бита) можно представить, так:

 

A) 0...01010110 0...01010110 0...01010110
B) 0...00100111 0...00100111 0...00100111
 


  0...00000110 0...01110111 0...01110001
  A AND B A OR B A XOR B


Как Вы видите, операция производится над битами, находящимися в одинаковых столбцах, а результат записывается в новое число в бит того же столбца. Операция NOT действует иным образом: результат равен 1, если число (а не бит) равно 0, если же число отлично от 0, то результат равен 0. Для представления числа в двоичном виде в Blitz существует функция BIN$. Например, BIN$(51) дает строку "000000000000000000000000000000110011". Преобразовывать числа из двоичной системы в десятичную поможет идентификатор % (%110011=51).

Умножение и деление

Команды "a SHL n" и "a SHR n" осуществляют побитовый сдвиг в числе a на n бит. SHR - вправо, SHL - влево. Скажем, 43(101011) SHR 1 = 21(10101), 5(101) SHL 3 = 40(101000). Как видите, SHR уничтожает значения крайних битов, а SHL дополняет число нулями. Нижеследующие эквиваленты очень полезны, т. к. логические операции гораздо быстрее арифметических, пытайтесь по возможности максимально широко использовать логические.

  • a * 2n = a SHL n
  • FLOOR(a / 2n) = a SHR n

Зацикливание

Очень часто в программе требуется "зациклить" какую-нибудь переменную. Например, игрок с координатами (X, Y) находится на карте NxN клеток, где каждая клетка имеет координаты (0...n-1, 0...n-1). Он может перемещаться вверх / вниз / вправо / влево, соответвственно меняются координаты (Y = Y - 1 / Y = Y + 1 / X = X + 1 / X = X - 1). Необходимо сделать так, чтобы при выходе за пределы карты, он появлялся с другой стороны, то есть, если координата X равна 0, а игрок делает шаг влево, то она становится равной не -1, а n-1 (противоположный край карты). Все бы ничего, да на карте есть локация с сапогами-скороходами, позволяющими перемещаться в любую сторону на случайное расстояние, которое может быть в несколько раз больше n. Соответственно, надо учитывать, что игрок может "промотать" за один ход несколько кругов по карте, в том числе и уменьшая координату. Самый простой (и самый медленный) способ выглядит так:

;Функция уменьшает или увеличивает переменную на n, пока она не окажется в

;пределах (0...n - 1)
Function LoopNum(x, n)
Repeat
 If v < n Then
  v = v + n
 ElseIf v >= n Then
  v = v - n
 Else
  Return v
 End If
Forever

End Function

Хорошо, если это только один игрок, а если это подконтрольные компьютеру существа, которых тысячи? Еще пара способов, более скоростных:

;Этот способ использует остаток от деления на n. Число "5" означает максимальное

;количество кругов, которое переменная может "промотать" назад.
x = (x + n * 5) Mod n
;Более аккуратный способ - вычитание излишка / прибавление недостатка

x = x - Floor(x / n) * n

Но самый быстрый способ можно применить, если n = 2m (m >= 1), тогда:

x = x And (n-1)

Вот и все! Но давайте разберемся, как это работает. Число 2m в двоичной системе счисления выглядит так: 1000...0, где нулей ровно m. А если от него отнять единицу, то получим число 1111...1, в котором будет m единиц. Операция AND отсекает ненужные биты, образующиеся при выходе переменной за рамки (0...n - 1). Например, n AND (n - 1) = 0, а -1(11111...11111 - 32 единицы) AND (n - 1) = n - 1, что, собственно, и требовалось.

Упаковка данных

А вот проблема, возникающая при работе с большими картами во многих типах программ: приходится к каждой клетке большой карты привязывать несколько параметров. Ну, например, создается мега-аркада с огромной картой, 1000 на 1000 клеток. Каждая клетка содержит следующую информацию: номер тайла (изображения клетки) (0-999), проходимость (0 - да, 1 - нет), 2 дополнительных изображения (следы крови, сажа) в диапазоне от 0 до 59, уровень заполненности водой (0-15), предмет (0-11). Если идти стандартным путем, то для хранения всей этой информации понадобится 6 массивов, и вся карта будет занимать 28MB. Может показаться, что трата памяти сейчас практически не проблема, но если карта представляет огромный мир и в несколько десятков раз больше? Сколько будут занимать и загружаться сохраненные игры? Есть способ свести данные в один массив, то есть уменьшить их объем в 7 раз в данном случае. Для начала, попробуем определить, сколько бит занимает каждая из составляющих:

  • Номер тайла: 10 бит (29(512) < 1000 < 210(1024))
  • Проходимость: 1 бит
  • Дополнительные изображения: 2 х 6 бит (25(32) < 60 < 26(64))
  • Уровень воды: 4 бит (16=24)
  • Предмет: 4 бит (23(8) < 12 < 24(16))
  • Итого: 31 бит > 32 бит

Теперь разобъем биты переменной на части:

Данные:   Тайл Пр. Доп. из. 1 Доп. из. 2 Вода Предмет
Число: 0 0000000000 0 000000 000000 0000 0000
Положение: 31 21-30 20 14-19 9-13 4-8 0-3

Теперь можно работать с массивом:

;Если все составляющие известны, то формируется массив следующим образом: 

; каждая переменная сдвигается на свою позицию, и все они складываются 
map(x,y)=tile Shl 21+movethru Shl 20+i1 Shl 14+i2 Shl 9+water Shl 4+obj
;Для замены одной из составляющих, нужно сначала обнулить ее в массиве 
;(обратите внимание, что в двоичном числе нули стоят на позициях, занимаемых 
; переменной), а затем добавить, предварительно сдвинув: 
map(x,y)=map(x,y) And %01111111111100001111111111111111+i1 Shl 14
;Чтение переменной проще: сначала сдвигаем число назад, чтобы она оказалась 
; справа, а затем отсекаем левую часть (количество единиц в двоичном числе 
; равно количеству бит, занимаемых переменной): 
i2=map(x,y) Shr 14 And %1111
;А вот так можно легко поменять значение флага (однобитной переменной) на 
; противоположное (если использовать команду OR, то флаг будет включен): 
map(x,y)=map(x,y) Xor %00000000000100000000000000000000

;Двоичные числа можно сразу преобразовывать в десятичные

В программе реализации игры "Жизнь" такая упаковка данных еще и ускоряет работу.

Упрощение формул

Вот, например одна из часто возникающих задач: переменная A принимает определенные значения (1, 3, 9, 10, 15), переменная B, в зависимости от того, какое значение приняла первая, должна принять значение из другого набора (2, 6, 3, 5, 11). К примеру, если A=1, то B=2, если A=3, то B=6. Самый простой и наглядный способ - это использовать оператор SELECT:

Select A

 Case 1: B=2
 Case 3: B=6
 Case 9: B=3
 Case 10: B=5
 Case 15: B=11

End Select

Еще один способ - более медленный и менее наглядный, но компактен и позволяет присваивать значения строковым переменным:

B=MID$(" 02..06.......0305........11",A*2,2)

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

B=(A=1)*2+(A=3)*9+(A=9)*3+(A=10)*5+(A=15)*11
Категория: Общее | Добавил: AD77Root (31 Июля 2015) | Автор: Матвей Меркулов E
Просмотров: 94 | Рейтинг: 0.0/0
Всего комментариев: 0
class="uForm uComForm">
avatar