Curses with planes
parent
4ab82f792b
commit
ff7de18508
13
src/cam.py
13
src/cam.py
|
@ -44,10 +44,10 @@ class CAM:
|
||||||
will be cps * states^dimen
|
will be cps * states^dimen
|
||||||
@dimen: The dimensions of the cellular automata. For example, for an N-tuple array, the dimension is N.
|
@dimen: The dimensions of the cellular automata. For example, for an N-tuple array, the dimension is N.
|
||||||
"""
|
"""
|
||||||
plane_count = max(cps, 1)
|
pl_cnt = max(cps, 1)
|
||||||
grid_dimen = (states,) * dimen
|
grid_dimen = (states,) * dimen
|
||||||
|
|
||||||
self.planes = [plane.Plane(grid_dimen) for i in range(cps)]
|
self.planes = [plane.Plane(grid_dimen) for _ in range(pl_cnt)]
|
||||||
self.master = self.planes[0]
|
self.master = self.planes[0]
|
||||||
self.ticks = [(0, 1)]
|
self.ticks = [(0, 1)]
|
||||||
self.total = 0
|
self.total = 0
|
||||||
|
@ -64,6 +64,7 @@ class CAM:
|
||||||
self.total += 1
|
self.total += 1
|
||||||
for i, j in self.ticks:
|
for i, j in self.ticks:
|
||||||
if self.total % j == 0:
|
if self.total % j == 0:
|
||||||
|
self.planes[i].dirty = True
|
||||||
rules.apply_to(self.planes[i], *args)
|
rules.apply_to(self.planes[i], *args)
|
||||||
|
|
||||||
def randomize(self):
|
def randomize(self):
|
||||||
|
@ -71,8 +72,8 @@ class CAM:
|
||||||
Convenience function to randomize individual planes.
|
Convenience function to randomize individual planes.
|
||||||
"""
|
"""
|
||||||
self.master.randomize()
|
self.master.randomize()
|
||||||
for plane in self.planes[1:]:
|
for p in self.planes[1:]:
|
||||||
plane.grid = self.master.grid
|
p.grid = self.master.grid
|
||||||
|
|
||||||
def start(self, show, **kwargs):
|
def start(self, show, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -82,7 +83,7 @@ class CAM:
|
||||||
while True:
|
while True:
|
||||||
self.tick(**kwargs)
|
self.tick(**kwargs)
|
||||||
elif show == CAM.Show.CONSOLE:
|
elif show == CAM.Show.CONSOLE:
|
||||||
ConsoleDisplay(self, **kwargs).run()
|
display.ConsoleDisplay(self, **kwargs).run()
|
||||||
elif show == CAM.Show.WINDOW:
|
elif show == CAM.Show.WINDOW:
|
||||||
WindowDisplay(self, **kwargs).run()
|
display.WindowDisplay(self, **kwargs).run()
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,10 @@ additional support for ECHOs and TRACing.
|
||||||
|
|
||||||
@date: June 16th, 2015
|
@date: June 16th, 2015
|
||||||
"""
|
"""
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
import curses
|
import curses
|
||||||
import curses.panel
|
import itertools
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
@ -34,7 +35,7 @@ class _Display:
|
||||||
|
|
||||||
self.clock = clock
|
self.clock = clock
|
||||||
self.rules = rules
|
self.rules = rules
|
||||||
self.tick_args = *args
|
self.tick_args = args
|
||||||
|
|
||||||
def _valid(self):
|
def _valid(self):
|
||||||
"""
|
"""
|
||||||
|
@ -56,7 +57,7 @@ class ConsoleDisplay(_Display):
|
||||||
Note a couple concepts go hand in hand with displaying all the bits:
|
Note a couple concepts go hand in hand with displaying all the bits:
|
||||||
|
|
||||||
First, it is unlikely the entirety of a CAM can be displayed on the screen
|
First, it is unlikely the entirety of a CAM can be displayed on the screen
|
||||||
at a time, so instead the use of a pad is supported, allowing navigation
|
at a time, so instead we emulate the use of a pad, allowing navigation
|
||||||
via the arrow keys.
|
via the arrow keys.
|
||||||
|
|
||||||
Second, to provide support for color mapping and multiple planes, we use panels
|
Second, to provide support for color mapping and multiple planes, we use panels
|
||||||
|
@ -65,30 +66,30 @@ class ConsoleDisplay(_Display):
|
||||||
|
|
||||||
def __init__(self, cam, clock, rules, *args):
|
def __init__(self, cam, clock, rules, *args):
|
||||||
"""
|
"""
|
||||||
Here we initialize the curses library, and begin construction of the necessary panels.
|
Here we initialize the curses library, and begin construction of the necessary overlays.
|
||||||
"""
|
"""
|
||||||
super().__init__(cam, clock, rules, *args)
|
super().__init__(cam, clock, rules, *args)
|
||||||
|
|
||||||
# Basic Curses Setup
|
# Basic Curses Setup
|
||||||
self.stdscr = curses.initscr()
|
self.stdscr = curses.initscr()
|
||||||
|
self.stdscr.keypad(True)
|
||||||
|
self.stdscr.nodelay(True)
|
||||||
|
|
||||||
|
# Other setup
|
||||||
curses.noecho()
|
curses.noecho()
|
||||||
curses.cbreak()
|
curses.cbreak()
|
||||||
|
curses.start_color()
|
||||||
|
curses.use_default_colors()
|
||||||
|
|
||||||
# Specifies the offsets the grid are taken at
|
# Specifies the offsets the grid are taken at
|
||||||
self.x = 0
|
self.x, self.y = 0, 0
|
||||||
self.y = 0
|
self.width, self.height = self.cam.master.shape
|
||||||
|
|
||||||
# Note we actually don't use the stdscr, but keeping
|
|
||||||
# reference for future use
|
|
||||||
width, height = self.cam.master.shape
|
|
||||||
self.pad = curses.newpad(width+1, height+1)
|
|
||||||
self.pad.nodelay(True)
|
|
||||||
self.pad.keypad(True)
|
|
||||||
|
|
||||||
# Construct the necessary planes
|
# Construct the necessary planes
|
||||||
self.panels = []
|
self.overlays = []
|
||||||
for plane in self.cam.planes:
|
for pl in self.cam.planes:
|
||||||
self.panels.append(curses.panel.new_panel(self.pad))
|
pad = curses.newpad(self.width+1, self.height+1)
|
||||||
|
self.overlays.append(pad)
|
||||||
|
|
||||||
def _valid(self):
|
def _valid(self):
|
||||||
"""
|
"""
|
||||||
|
@ -105,13 +106,38 @@ class ConsoleDisplay(_Display):
|
||||||
is determined by the passed character value.
|
is determined by the passed character value.
|
||||||
"""
|
"""
|
||||||
if ch == curses.KEY_UP:
|
if ch == curses.KEY_UP:
|
||||||
self.y = (self.y + 1) % height
|
self.y = (self.y + 1) % self.height
|
||||||
elif ch == curses.KEY_DOWN:
|
elif ch == curses.KEY_DOWN:
|
||||||
self.y = (self.y - 1) % height
|
self.y = (self.y - 1) % self.height
|
||||||
elif ch == curses.KEY_LEFT:
|
elif ch == curses.KEY_LEFT:
|
||||||
self.x = (self.x + 1) % width
|
self.x = (self.x + 1) % self.width
|
||||||
elif ch == curses.KEY_RIGHT:
|
elif ch == curses.KEY_RIGHT:
|
||||||
self.x = (self.x - 1) % width
|
self.x = (self.x - 1) % self.width
|
||||||
|
|
||||||
|
def _draw_overlay(self, overlay, pl):
|
||||||
|
"""
|
||||||
|
Draw the grid onto the overlay.
|
||||||
|
|
||||||
|
Wherever a one occurs, we make sure to draw it. Otherwise, we create a transparent
|
||||||
|
spot so any overlays below can be seen.
|
||||||
|
"""
|
||||||
|
overlay.clear()
|
||||||
|
|
||||||
|
line = 0
|
||||||
|
grid = np.append(pl.grid[self.y:], pl.grid[:self.y])
|
||||||
|
for bits in grid.flat:
|
||||||
|
overlay.move(line, 0)
|
||||||
|
line += 1
|
||||||
|
|
||||||
|
# We go through and group the bits apart so
|
||||||
|
cycle = bits[self.x:] + bits[:self.x]
|
||||||
|
for k, g in itertools.groupby(cycle):
|
||||||
|
values = list(g)
|
||||||
|
if any(values):
|
||||||
|
overlay.addstr('+' * len(values))
|
||||||
|
else:
|
||||||
|
y, x = overlay.getyx()
|
||||||
|
overlay.move(y, x + len(values))
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
@ -121,40 +147,36 @@ class ConsoleDisplay(_Display):
|
||||||
(which could be user thrown by Ctrl-C), restores the terminal back
|
(which could be user thrown by Ctrl-C), restores the terminal back
|
||||||
to a usable state.
|
to a usable state.
|
||||||
"""
|
"""
|
||||||
while True:
|
|
||||||
try:
|
try:
|
||||||
|
while True:
|
||||||
|
|
||||||
# Note the user can change the size of the terminal,
|
# Note the user can change the size of the terminal,
|
||||||
# so we query for these values every time
|
# so we query for these values every time
|
||||||
max_y, max_x = stdscr.getmaxyx()
|
max_y, max_x = self.stdscr.getmaxyx()
|
||||||
|
|
||||||
# Navigate the plane
|
# Navigate the plane
|
||||||
# Note in the __init__ method, this was set to not block
|
# Note in the __init__ method, this was set to not block
|
||||||
self._shift(pad.getch())
|
self._shift(self.stdscr.getch())
|
||||||
|
|
||||||
# Cycle around grid
|
# Cycle around grid
|
||||||
grid = self.cam.master.grid
|
for i, pl in enumerate(self.cam.planes):
|
||||||
grid = np.append(grid[y:], grid[:y])
|
if pl.dirty:
|
||||||
|
pl.dirty = False
|
||||||
# Draw out to console
|
self._draw_overlay(self.overlays[i], pl)
|
||||||
line = 0
|
self.overlays[i].noutrefresh(0, 0, 0, 0, max_y-1, max_x-1)
|
||||||
for bits in grid.flat:
|
|
||||||
self.pad.move(line, 0)
|
|
||||||
self.pad.addstr((bits[x:] + bits[:x]).to01())
|
|
||||||
line += 1
|
|
||||||
|
|
||||||
# Draw out to screen
|
|
||||||
curses.panels.update_panels()
|
|
||||||
self.pad.refresh(0, 0, 0, 0, max_y-1, max_x-1)
|
|
||||||
|
|
||||||
# Prepare for next loop
|
# Prepare for next loop
|
||||||
|
curses.doupdate()
|
||||||
time.sleep(self.clock / 1000)
|
time.sleep(self.clock / 1000)
|
||||||
self.tick(self.rules, *self.tick_args)
|
self.cam.tick(self.rules, *self.tick_args)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
|
self.stdscr.keypad(False)
|
||||||
curses.nocbreak()
|
curses.nocbreak()
|
||||||
self.pad.keypad(False)
|
|
||||||
curses.echo()
|
curses.echo()
|
||||||
curses.endwin()
|
curses.endwin()
|
||||||
|
print("Exception: ", sys.exc_info()[0])
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
class WindowDisplay(_Display):
|
class WindowDisplay(_Display):
|
||||||
|
|
|
@ -45,6 +45,9 @@ class Plane:
|
||||||
for i in range(self.grid.size):
|
for i in range(self.grid.size):
|
||||||
self.grid.flat[i] = self.N * bitarray('0')
|
self.grid.flat[i] = self.N * bitarray('0')
|
||||||
|
|
||||||
|
# Check if a plane has been updated recently
|
||||||
|
self.dirty = False
|
||||||
|
|
||||||
|
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue