Separated view from model
parent
fe05bd7eaa
commit
f1042d351f
106
src/cam.py
106
src/cam.py
|
@ -10,14 +10,10 @@ tracing/echoing/multi-dimensional displays
|
|||
|
||||
@date: June 01, 2015
|
||||
"""
|
||||
import enum
|
||||
|
||||
import plane
|
||||
|
||||
import time
|
||||
import curses
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.animation as ani
|
||||
|
||||
import display
|
||||
|
||||
class CAM:
|
||||
"""
|
||||
|
@ -32,6 +28,13 @@ class CAM:
|
|||
updated. should be updated, Certain planes may or may not change every tick, but instead on every
|
||||
nth tick, allowing for more sophisticated views such as ECHOing and TRACing.
|
||||
"""
|
||||
class Show(enum.Enum):
|
||||
"""
|
||||
Display method.
|
||||
"""
|
||||
NONE = 0
|
||||
CONSOLE = 1
|
||||
WINDOW = 2
|
||||
|
||||
def __init__(self, cps=1, states=100, dimen=2):
|
||||
"""
|
||||
|
@ -49,7 +52,6 @@ class CAM:
|
|||
self.ticks = [(0, 1)]
|
||||
self.total = 0
|
||||
|
||||
|
||||
def tick(self, rules, *args):
|
||||
"""
|
||||
Modify all states in a given CAM "simultaneously".
|
||||
|
@ -64,83 +66,6 @@ class CAM:
|
|||
if self.total % j == 0:
|
||||
rules.apply_to(self.planes[i], *args)
|
||||
|
||||
|
||||
def start_plot(self, clock, rules, *args):
|
||||
"""
|
||||
Initiates main graphical loop.
|
||||
|
||||
The following function displays the graphical component (through use of matplotlib), and triggers the
|
||||
next tick for every "clock" milliseconds. This should only be called if the automata is 2 or 3 dimensional.
|
||||
"""
|
||||
fig, ax = plt.subplots()
|
||||
|
||||
ax.set_frame_on(False)
|
||||
ax.get_xaxis().set_visible(False)
|
||||
ax.get_yaxis().set_visible(False)
|
||||
|
||||
mshown = plt.matshow(self.master.bits(), fig.number, cmap='Greys')
|
||||
|
||||
def animate(frame):
|
||||
self.tick(rules, *args)
|
||||
mshown.set_array(self.master.bits())
|
||||
return [mshown]
|
||||
|
||||
ani.FuncAnimation(fig, animate, interval=clock)
|
||||
|
||||
plt.axis('off')
|
||||
plt.show()
|
||||
|
||||
def _console_run(self, stdscr, clock, rules, *args):
|
||||
"""
|
||||
The following displays all bits onto the console.
|
||||
|
||||
Since overflow of the window is most probable, we create a pad allowing the user to navigate
|
||||
the scene via the arrow keys. Note this does wrap around, so one can go left (for example) indefinitely.
|
||||
For multiple bit planes, curses.panels are used.
|
||||
"""
|
||||
y, x = 0, 0
|
||||
max_y, max_x = stdscr.getmaxyx()
|
||||
width, height = self.master.shape
|
||||
|
||||
pad = curses.newpad(width+1, height+1)
|
||||
pad.nodelay(1)
|
||||
pad.keypad(1)
|
||||
|
||||
while True:
|
||||
|
||||
# Allow navigating plane
|
||||
c = pad.getch()
|
||||
if c == curses.KEY_UP:
|
||||
y = (y + 1) % height
|
||||
elif c == curses.KEY_DOWN:
|
||||
y = (y - 1) % height
|
||||
elif c == curses.KEY_LEFT:
|
||||
x = (x + 1) % width
|
||||
elif c == curses.KEY_RIGHT:
|
||||
x = (x - 1) % width
|
||||
|
||||
# Cycle around grid
|
||||
grid = self.master.grid
|
||||
grid = np.append(grid[y:], grid[:y])
|
||||
|
||||
# Draw out to console
|
||||
line = 0
|
||||
for bits in grid.flat:
|
||||
pad.move(line, 0)
|
||||
pad.addstr((bits[x:] + bits[:x]).to01())
|
||||
line += 1
|
||||
pad.refresh(0, 0, 0, 0, max_y-1, max_x-1)
|
||||
time.sleep(clock / 1000)
|
||||
self.tick(rules, *args)
|
||||
|
||||
def start_console(self, clock, rules, *args):
|
||||
"""
|
||||
Initates main console loop.
|
||||
|
||||
Works similarly to start_plot but prints out to the console.
|
||||
"""
|
||||
curses.wrapper(self._console_run, clock, rules, *args)
|
||||
|
||||
def randomize(self):
|
||||
"""
|
||||
Convenience function to randomize individual planes.
|
||||
|
@ -149,4 +74,15 @@ class CAM:
|
|||
for plane in self.planes[1:]:
|
||||
plane.grid = self.master.grid
|
||||
|
||||
def start(self, show, **kwargs):
|
||||
"""
|
||||
Delegate how to initiate running the CAM.
|
||||
"""
|
||||
if show == CAM.Show.NONE:
|
||||
while True:
|
||||
self.tick(**kwargs)
|
||||
elif show == CAM.Show.CONSOLE:
|
||||
ConsoleDisplay(self, **kwargs).run()
|
||||
elif show == CAM.Show.WINDOW:
|
||||
WindowDisplay(self, **kwargs).run()
|
||||
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
"""
|
||||
Allow for displaying 2D/3D CAMs.
|
||||
|
||||
Two means of viewing the CAM are provided: either through the console via the curses
|
||||
library or through a GUI display via the matplotlib library. Note the console display
|
||||
only supports 2D CAMs while the GUI supports 2D/3D automata.
|
||||
|
||||
Both methods allow for the ability to display multiple cell planes at a time, with
|
||||
additional support for ECHOs and TRACing.
|
||||
|
||||
@date: June 16th, 2015
|
||||
"""
|
||||
import time
|
||||
import curses
|
||||
import curses.panel
|
||||
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.animation as ani
|
||||
|
||||
|
||||
class _Display:
|
||||
"""
|
||||
The base class for visualization of the CAM.
|
||||
"""
|
||||
|
||||
def __init__(self, cam, clock, rules, *args):
|
||||
"""
|
||||
Test for valid CAM and setup.
|
||||
"""
|
||||
self.cam = cam
|
||||
if not self._valid():
|
||||
raise ValueError("Invalid Dimension for Display")
|
||||
|
||||
self.clock = clock
|
||||
self.rules = rules
|
||||
self.tick_args = *args
|
||||
|
||||
def _valid(self):
|
||||
"""
|
||||
Ensures passed cam is supported for display type.
|
||||
"""
|
||||
return True
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Initiate the main display loop.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ConsoleDisplay(_Display):
|
||||
"""
|
||||
Displays CAM onto console via the curses library.
|
||||
|
||||
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
|
||||
at a time, so instead the use of a pad is supported, allowing navigation
|
||||
via the arrow keys.
|
||||
|
||||
Second, to provide support for color mapping and multiple planes, we use panels
|
||||
which allow for exactly this overlaying we are trying to simulate.
|
||||
"""
|
||||
|
||||
def __init__(self, cam, clock, rules, *args):
|
||||
"""
|
||||
Here we initialize the curses library, and begin construction of the necessary panels.
|
||||
"""
|
||||
super().__init__(cam, clock, rules, *args)
|
||||
|
||||
# Basic Curses Setup
|
||||
self.stdscr = curses.initscr()
|
||||
curses.noecho()
|
||||
curses.cbreak()
|
||||
stdscr.keypad(True)
|
||||
|
||||
# Specifies the offsets the grid are taken at
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
|
||||
# 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(1)
|
||||
self.pad.keypad(1)
|
||||
|
||||
# Construct the necessary planes
|
||||
self.panels = []
|
||||
for plane in self.cam.planes:
|
||||
self.panels.append(curses.panel.new_panel(self.pad))
|
||||
|
||||
def _valid(self):
|
||||
"""
|
||||
Ensures only 2D CAMs are accepted.
|
||||
"""
|
||||
return len(self.cam.master.shape) == 2
|
||||
|
||||
def _shift(self, ch):
|
||||
"""
|
||||
Move all panels over by specified amount.
|
||||
|
||||
Note we want functionality to wrap around, so we make
|
||||
sure to mod based on which direction we've gone. Directionality
|
||||
is determined by the passed character value.
|
||||
"""
|
||||
if ch == curses.KEY_UP:
|
||||
self.y = (self.y + 1) % height
|
||||
elif ch == curses.KEY_DOWN:
|
||||
self.y = (self.y - 1) % height
|
||||
elif ch == curses.KEY_LEFT:
|
||||
self.x = (self.x + 1) % width
|
||||
elif ch == curses.KEY_RIGHT:
|
||||
self.x = (self.x - 1) % width
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Commence actual loop.
|
||||
|
||||
The following draws out all panels, and, in the case of an exception
|
||||
(which could be user thrown by Ctrl-C), restores the terminal back
|
||||
to a usable state.
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
# Note the user can change the size of the terminal,
|
||||
# so we query for these values every time
|
||||
max_y, max_x = stdscr.getmaxyx()
|
||||
|
||||
# Navigate the plane
|
||||
# Note in the __init__ method, this was set to not block
|
||||
self._shift(pad.getch())
|
||||
|
||||
# Cycle around grid
|
||||
grid = self.cam.master.grid
|
||||
grid = np.append(grid[y:], grid[:y])
|
||||
|
||||
# Draw out to console
|
||||
line = 0
|
||||
for bits in grid.flat:
|
||||
pad.move(line, 0)
|
||||
pad.addstr((bits[x:] + bits[:x]).to01())
|
||||
line += 1
|
||||
|
||||
# Draw out to screen
|
||||
curses.panels.update_panels()
|
||||
pad.refresh(0, 0, 0, 0, max_y-1, max_x-1)
|
||||
|
||||
# Prepare for next loop
|
||||
time.sleep(self.clock / 1000)
|
||||
self.tick(self.rules, *self.tick_args)
|
||||
|
||||
except:
|
||||
curses.nocbreak()
|
||||
stdscr.keypad(False)
|
||||
curses.echo()
|
||||
curses.endwin()
|
||||
|
||||
|
||||
class WindowDisplay(_Display):
|
||||
"""
|
||||
Displays CAM onto window via the matplotlib library.
|
||||
|
||||
We use the AxesImage object in matplotlib and constantly animate
|
||||
the graph to display the automata. Unlike the curses library, this
|
||||
class also provides support for 3D display, though note this is
|
||||
much more intensive.
|
||||
"""
|
||||
def __init__(self, cam, clock, rules, *args):
|
||||
"""
|
||||
Initialize matplotlib objects.
|
||||
"""
|
||||
super().__init__(cam, clock, rules, *args)
|
||||
|
||||
# Keep local reference for convenience
|
||||
self.fig, self.ax = plt.subplots()
|
||||
|
||||
# Note we draw out planes in the reverse direction
|
||||
# for proper superimposition
|
||||
self.matrices = []
|
||||
for plane in self.cam.planes:
|
||||
mshown = plt.matshow(plane.bits(), fig.number, cmap='Greys')
|
||||
self.matrices.append(mshown)
|
||||
|
||||
def _valid(self):
|
||||
"""
|
||||
Ensures only 2D/3D CAMs are accepted.
|
||||
"""
|
||||
return 2 <= len(self.cam.master.shape) <= 3
|
||||
|
||||
def _animate(self, frame):
|
||||
"""
|
||||
Display the next state of the automaton.
|
||||
|
||||
Note that the framerate must be considered; one shouldn't just try to run
|
||||
the animation as fast as possible, as this callback will only be bottled up.
|
||||
The limiting factor is the tick method, which should be fast enough for reasonably
|
||||
sized CAMs (100x100 runs in <50 ms on my computer), but runs in quadratic time.
|
||||
"""
|
||||
self.cam.tick(self.rules, *self.tick_args)
|
||||
if len(self.cam.master.shape) == 2:
|
||||
self.mshown.set_array(self.cam.master.bits())
|
||||
return [self.mshown]
|
||||
else:
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Commence actual loop.
|
||||
|
||||
The following expands out each plane (from a bitarray to a matrix of bits)
|
||||
which are then displayed out via the animate function. We simply superimpose
|
||||
the necessary plots for the desired overlaying.
|
||||
"""
|
||||
if len(self.cam.master.shape) == 2:
|
||||
self.ax.set_frame_on(False)
|
||||
self.ax.get_xaxis().set_visible(False)
|
||||
self.ax.get_yaxis().set_visible(False)
|
||||
else:
|
||||
pass
|
||||
|
||||
ani.FuncAnimation(self.fig, self.animate, interval=self.clock)
|
||||
plt.axis('off')
|
||||
plt.show()
|
||||
|
|
@ -9,7 +9,6 @@ state at the ith index of the given row. This holds for 0 as well.
|
|||
For example, given a 100 x 100 CAM, we represent this underneath as a 1-D array of 100 integers, each of which's
|
||||
binary expansion will be 100 bits long (and padded with 0's if necessary).
|
||||
|
||||
@author: jrpotter
|
||||
@date: June 05, 2015
|
||||
"""
|
||||
import numpy as np
|
||||
|
|
Loading…
Reference in New Issue