---
tags: 1_basic
---
# DEMO. Ефекти 2. Порядок

Note:
План:
- цикл for
- counter,i
- процедури для організації коду
---
## Зав'язка
Гостросюжетний текст про те, як раптом комусь знадобилися скіли програміста.
---
1. Зигзаг
2. Два симетричні зигзаги
3. Вікно
4. Зигзаги "за" вікном
Слайди з контентом
---
Живий кодинг, експерименти, демо
Note:
Копія коду для тих, хто не був присутній на лекції і не дивився відео.
---
### DEMO.Effects2.A0.0 (обов'язкове завдання)
3 або 4 симетричні зигзаги
----
### DEMO.Effects2.A0.1 (обов'язкове завдання)
Намалювати трикутник заданого розміру.
----
### DEMO.Effects2.A0.2 (обов'язкове завдання)
Один з перших ефектів "вогню" з'явився в 1980 роках, і з тих пір неодноразово був використаний у демках та іграх. Цей ефект реалізовували на всіх екранах, включаючи допотопні MS-DOS.
Коли я вперше побачив його код в 9 класі, я не міг зрозуміти як це працює. Але виглядало дуже круто!
<details>
<summary>Насправді...</summary>
Щоб зрозуміти цей код, треба знати:
<ul>
<li>координатну площину
<li>двовимірні масиви, слайси масивів, операції над рядками масивів
<li>матриці та роботу за матрицями: додавання, транспонування, скалярне множення
<li>розуміти в якому порядку оновлюються дані в масивах та матрицях
<li>кольорові схеми RGB та HSL, чим відрізняються та коли друга зручніша
<li>механізм виводу пікселів на екран, дабл буфер
<li>середнє арифметичне
<li>додавання по модулю та коли його треба використовувати
<li>8-бітні палітри
<li>і це тільки теми, які не стосуються самого Python!
</ul>
</details>
<br />
Спробуй і ти запусти "вогонь"! Вибери режим, 1 або 2, а потім переглянь код. Чи зможеш зрозуміти як воно працює?
P.S. Цей та інші ефекти -- це прадіди "демосцени", мінімалістичних арт-перфомансів за допомогою програмування. В завданні [DEMO.Effects2.A3.0](#DEMO.Effects2.A3.0) є більше деталей.
```python=
import pygame
import random
import colorsys
def fps_text(clock, font):
"""
Повертає об'єкт Surface з текстом про кількість кадрів в секунду
(frames per second, FPS)
"""
fps = "FPS: " + str(int(clock.get_fps()))
return font.render(fps, 1, pygame.Color("violet"))
def color_HSL(h, s, l):
"""
Перетворює колір з системи HSL в систему RGB.
HSL дозволяє дуже просто робити палітру з "вогняними" кольорами
На вході - кольори у діапазоні 0-255
На виході - (r,g,b), також у діапазоні 0-255
"""
color = colorsys.hls_to_rgb(h/255, l/255, s/255)
return tuple( int(x*255) for x in color )
def fire_palette(color_shift=0, color_split=1):
"""
Палітра кольорів на 256 елементів.
Параметр color_shift дозволяє керувати домінантним кольором вогню
"""
return [
color_HSL((x / color_split + color_shift) % 256, 255, min(255, x*2))
for x in range(256)
]
def check_exit():
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
raise KeyboardInterrupt()
def fire_transform_numpy(arr):
"""
Ітерація "вогню" через матриці numpy
"""
height = arr.shape[0]
for y in range(height-1):
arr[y, 1:-1] = (
arr[y+1, 1:-1]
+ arr[y+1, :-2]
+ arr[y+1, 2: ]
+ arr[(y+2)%height, 1:-1]
) * 0.2499
def fire_transform_pixelarray(arr):
"""
Ітерація "вогню" через PixelArray об'єкт pygame
"""
w = arr.shape[0]
h = arr.shape[1]
for y in range(0, h-1):
for x in range(0, w):
arr[x, y] = int((
arr[(x - 1 + w) % w, (y + 1) % h]
+ arr[x % w , (y + 1) % h]
+ arr[(x + 1) % w , (y + 1) % h]
+ arr[x % w , (y + 2) % h]
) * 0.2499)
def non_optimized_demo(W, H):
screen = pygame.display.set_mode((W, H))
font = pygame.font.SysFont("Arial", 18)
clock = pygame.time.Clock()
# surface для вогню
fire_surface = pygame.Surface((W, H), pygame.HWPALETTE, 8)
fire_surface.set_palette(fire_palette(color_split=3))
fire_surface.fill(0)
while True:
screen.fill(0) # очистити екран
pxarray = pygame.PixelArray(fire_surface)
# Щоб зробити демонстрацію цікавішою, кожні 5 секунд будемо змінювати тип вогню
if pygame.time.get_ticks() // 1000 % 10 < 5:
# Нижній ряд - випадкові кольори з палітри
for x in range(0, W):
pxarray[x, H - 1] = random.randrange(0, 100)
fire_transform_pixelarray(pxarray)
pxarray.close()
# Намалювати вогонь на екран
screen.blit(fire_surface, (0,0))
# Намалювати текст FPS на екран
screen.blit(fps_text(clock, font), (10, 0))
check_exit()
clock.tick(200) # FPS, який ми хотіли би бачити
pygame.display.flip()
def optimized_demo(W, H):
import numpy as np
from pygame import surfarray
screen = pygame.display.set_mode((W, H))
font = pygame.font.SysFont("Arial", 18)
clock = pygame.time.Clock()
# матриця для вогню. Координати - спочатку y, потім x (!!!)
fire = np.zeros((H, W)).astype(int)
# surface для вогню
fire_surface = pygame.Surface((fire.shape[1], fire.shape[0]), pygame.HWPALETTE, 8)
fire_surface.set_palette(fire_palette(120))
while True:
screen.fill(0) # очистити екран
# Щоб зробити демонстрацію цікавішою, кожні 5 секунд будемо змінювати тип вогню
if pygame.time.get_ticks() // 1000 % 10 < 5:
# Нижній ряд - випадкові кольори з палітри
fire[-1] = np.random.randint(0, 150, (fire.shape[1],))
fire_transform_numpy(fire)
# Намалювати вогонь на екран. Не забуваємо про порядок координат!
surfarray.blit_array(fire_surface, np.transpose(fire))
screen.blit(fire_surface, (0,0))
# Намалювати текст FPS на екран
screen.blit(fps_text(clock, font), (10, 0))
check_exit()
clock.tick(200) # FPS, який ми хотіли би бачити
pygame.display.flip()
def main():
try:
variant = None
while variant not in ("1", "2"):
variant = input(
"Вибери варіант - (1) неоптимізоване демо, (2) оптимізоване демо: ")
pygame.init()
if variant == "1":
non_optimized_demo(80, 100)
else:
optimized_demo(300, 150)
except KeyboardInterrupt:
pass
finally:
pygame.quit()
main()
```
---
### Додаткові завдання
#### DEMO.Effects2.A1.0
https://edabit.com/challenges
#### DEMO.Effects2.A1.1
#### DEMO.Effects2.A1.2
#### DEMO.Effects2.A1.3
#### DEMO.Effects2.A1.4
---
### Додаткові завдання (складні!)
#### DEMO.Effects2.A2.0
#### DEMO.Effects2.A2.1
#### DEMO.Effects2.A2.2
---
### Real-world завдання, для експертів
#### DEMO.Effects2.A3.0
В 2004 році вийшла одна комп'ютерна інді мінігра, котра наробила трошки шуму. Називалася `.kkrieger`. Чули про таку?
{%youtube 9f5TYTRhC64%}
Її фішка була в процедурній генерації всіх текстур, звуків та анімацій. Сьогодні Unreal Engine за допомогою мільйонів трикутників в одному об'єкті досягає практично фотореалістичності, а установлений GTA V займає 65 Гб місця на диску! Це нереально великий об'єм даних, і це в 500 000 раз більше ніж мав розмір `.kkrieger`.
Розмір гри був 96Кб, і змагалась гра на конкурсі ігор розміром до 96Кб. Вона була настільки маленька, що люди думали -- це вірус. Ніхто не вірив, що шутер можна вмістити в такий маленький об'єм, доки самостійно її не запускали.
Корені гри походять від поняття **"демосцени"** -- коли програмісти демонструють своє кунг-фу, керування пікселями без грузних движків, без популярних фреймворків. Тільки ти і пікселі (і аудіо). Це -- арт, мистецтво для гіків.
{%youtube 9NBUM2-wkJE%}
- сама демка -- https://files.scene.org/view/demos/groups/farbrausch/kkrieger-beta.zip
- код гри [знаходиться на Github](https://github.com/farbrausch/fr_public/tree/master/werkkzeug3_kkrieger)
- один з авторів розповів, з чого складається гра -- https://fgiesen.wordpress.com/2012/02/13/debris-opening-the-box
Але завдання ось у чому: створити свою "демку", програму котра демонструє віртуозне володіння програмерськими штучками та математикою процедурної генерації.