r
/
fifth
1
Fork 0

Curses with planes

master
Joshua Potter 2015-06-17 08:47:01 -04:00
parent 4ab82f792b
commit ff7de18508
3 changed files with 75 additions and 49 deletions

View File

@ -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()

View File

@ -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):

View File

@ -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):
""" """