152 lines
5.3 KiB
Python
152 lines
5.3 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
"""
|
||
|
This module implements a fire effect renderer.
|
||
|
"""
|
||
|
from __future__ import division
|
||
|
from __future__ import absolute_import
|
||
|
from __future__ import print_function
|
||
|
from __future__ import unicode_literals
|
||
|
from builtins import range
|
||
|
import copy
|
||
|
from random import randint, random
|
||
|
|
||
|
from asciimatics.renderers.base import DynamicRenderer
|
||
|
from asciimatics.screen import Screen
|
||
|
|
||
|
|
||
|
class Fire(DynamicRenderer):
|
||
|
"""
|
||
|
Renderer to create a fire effect based on a specified `emitter` that
|
||
|
defines the heat source.
|
||
|
|
||
|
The implementation here uses the same techniques described in
|
||
|
http://freespace.virgin.net/hugo.elias/models/m_fire.htm, although a
|
||
|
slightly different implementation.
|
||
|
"""
|
||
|
|
||
|
_COLOURS_16 = [
|
||
|
(Screen.COLOUR_RED, 0),
|
||
|
(Screen.COLOUR_RED, 0),
|
||
|
(Screen.COLOUR_RED, 0),
|
||
|
(Screen.COLOUR_RED, 0),
|
||
|
(Screen.COLOUR_RED, 0),
|
||
|
(Screen.COLOUR_RED, 0),
|
||
|
(Screen.COLOUR_RED, 0),
|
||
|
(Screen.COLOUR_RED, Screen.A_BOLD),
|
||
|
(Screen.COLOUR_RED, Screen.A_BOLD),
|
||
|
(Screen.COLOUR_RED, Screen.A_BOLD),
|
||
|
(Screen.COLOUR_RED, Screen.A_BOLD),
|
||
|
(Screen.COLOUR_YELLOW, Screen.A_BOLD),
|
||
|
(Screen.COLOUR_YELLOW, Screen.A_BOLD),
|
||
|
(Screen.COLOUR_YELLOW, Screen.A_BOLD),
|
||
|
(Screen.COLOUR_YELLOW, Screen.A_BOLD),
|
||
|
(Screen.COLOUR_WHITE, Screen.A_BOLD),
|
||
|
]
|
||
|
|
||
|
_COLOURS_256 = [
|
||
|
(0, 0),
|
||
|
(52, 0),
|
||
|
(88, 0),
|
||
|
(124, 0),
|
||
|
(160, 0),
|
||
|
(196, 0),
|
||
|
(202, 0),
|
||
|
(208, 0),
|
||
|
(214, 0),
|
||
|
(220, 0),
|
||
|
(226, 0),
|
||
|
(227, 0),
|
||
|
(228, 0),
|
||
|
(229, 0),
|
||
|
(230, 0),
|
||
|
(231, 0),
|
||
|
]
|
||
|
|
||
|
_CHARS = " ...::$$$&&&@@"
|
||
|
|
||
|
def __init__(self, height, width, emitter, intensity, spot, colours,
|
||
|
bg=False):
|
||
|
"""
|
||
|
:param height: Height of the box to contain the flames.
|
||
|
:param width: Width of the box to contain the flames.
|
||
|
:param emitter: Heat source for the flames. Any non-whitespace
|
||
|
character is treated as part of the heat source.
|
||
|
:param intensity: The strength of the flames. The bigger the number,
|
||
|
the hotter the fire. 0 <= intensity <= 1.0.
|
||
|
:param spot: Heat of each spot source. Must be an integer > 0.
|
||
|
:param colours: Number of colours the screen supports.
|
||
|
:param bg: (Optional) Whether to render background colours only.
|
||
|
"""
|
||
|
super(Fire, self).__init__(height, width)
|
||
|
self._emitter = emitter
|
||
|
self._intensity = intensity
|
||
|
self._spot_heat = spot
|
||
|
self._count = len([c for c in emitter if c not in " \n"])
|
||
|
line = [0 for _ in range(self._canvas.width)]
|
||
|
self._buffer = [copy.deepcopy(line) for _ in range(self._canvas.width * 2)]
|
||
|
self._colours = self._COLOURS_256 if colours >= 256 else \
|
||
|
self._COLOURS_16
|
||
|
self._bg_too = bg
|
||
|
|
||
|
# Figure out offset of emitter to centre at the bottom of the buffer
|
||
|
e_width = 0
|
||
|
e_height = 0
|
||
|
for line in self._emitter.split("\n"):
|
||
|
e_width = max(e_width, len(line))
|
||
|
e_height += 1
|
||
|
self._x = (width - e_width) // 2
|
||
|
self._y = height - e_height
|
||
|
|
||
|
def _render_now(self):
|
||
|
# First make the fire rise with convection
|
||
|
for y in range(len(self._buffer) - 1):
|
||
|
self._buffer[y] = self._buffer[y + 1]
|
||
|
self._buffer[len(self._buffer) - 1] = [0 for _ in range(self._canvas.width)]
|
||
|
|
||
|
# Seed new hot spots
|
||
|
x = self._x
|
||
|
y = self._y
|
||
|
for c in self._emitter:
|
||
|
if c not in " \n" and random() < self._intensity:
|
||
|
self._buffer[y][x] += randint(1, self._spot_heat)
|
||
|
if c == "\n":
|
||
|
x = self._x
|
||
|
y += 1
|
||
|
else:
|
||
|
x += 1
|
||
|
|
||
|
# Seed a few cooler spots
|
||
|
for _ in range(self._canvas.width // 2):
|
||
|
self._buffer[randint(0, self._canvas.height - 1)][
|
||
|
randint(0, self._canvas.width - 1)] -= 10
|
||
|
|
||
|
# Simulate cooling effect of the resulting environment.
|
||
|
for y in range(len(self._buffer)):
|
||
|
for x in range(self._canvas.width):
|
||
|
new_val = self._buffer[y][x]
|
||
|
if y < len(self._buffer) - 1:
|
||
|
new_val += self._buffer[y + 1][x]
|
||
|
if x > 0:
|
||
|
new_val += self._buffer[y][x - 1]
|
||
|
if x < self._canvas.width - 1:
|
||
|
new_val += self._buffer[y][x + 1]
|
||
|
self._buffer[y][x] = new_val // 4
|
||
|
|
||
|
# Now build the rendered text from the simulated flames.
|
||
|
self._clear()
|
||
|
for x in range(self._canvas.width):
|
||
|
for y in range(len(self._buffer)):
|
||
|
if self._buffer[y][x] > 0:
|
||
|
colour = self._colours[min(len(self._colours) - 1,
|
||
|
self._buffer[y][x])]
|
||
|
if self._bg_too:
|
||
|
char = " "
|
||
|
bg = colour[0]
|
||
|
else:
|
||
|
char = self._CHARS[min(len(self._CHARS) - 1,
|
||
|
self._buffer[y][x])]
|
||
|
bg = 0
|
||
|
self._write(char, x, y, colour[0], colour[1], bg)
|
||
|
|
||
|
return self._plain_image, self._colour_map
|