Created
November 18, 2020 12:54
-
-
Save amcaplan/426a6bdf2f06c4fdb3d6d69136e35515 to your computer and use it in GitHub Desktop.
RubyConf 2020 demo code: Conway's Game of Life with and without caching intermediates
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'get_process_mem' | |
require 'curses' | |
initial_map = <<~MAP | |
............................ | |
............................ | |
.....O......O......O......O. | |
......O......O......O......O | |
....OOO....OOO....OOO....OOO | |
............................ | |
............................ | |
............................ | |
.....O......O......O......O. | |
......O......O......O......O | |
....OOO....OOO....OOO....OOO | |
............................ | |
............................ | |
............................ | |
.....O......O......O......O. | |
......O......O......O......O | |
....OOO....OOO....OOO....OOO | |
............................ | |
MAP | |
WIDTH = initial_map.each_line.first.chomp.size | |
HEIGHT = initial_map.lines.size | |
INITIAL_STATES = {} | |
initial_map.each_line.with_index do |line, y| | |
line.each_char.with_index do |char, x| | |
INITIAL_STATES[[x, y]] = 1 if char == 'O' | |
end | |
end | |
initial_sleep = 5 | |
life_hash = Hash.new do |h, (x, y, step)| | |
h[[x, y, step]] = | |
if step == 0 | |
INITIAL_STATES[[x, y]] || 0 | |
else | |
neighbor_diffs = [[-1, -1], [-1, 0], [-1, 1], | |
[ 0, -1], [ 0, 1], | |
[ 1, -1], [ 1, 0], [ 1, 1]] | |
live_neighbors = neighbor_diffs.sum { |x_diff, y_diff| | |
h[[(x + x_diff) % WIDTH, (y + y_diff) % HEIGHT, step - 1]] | |
} | |
if h[[x, y, step - 1]] == 1 | |
[2, 3].include?(live_neighbors) ? 1 : 0 | |
else | |
live_neighbors == 3 ? 1 : 0 | |
end | |
end | |
end | |
def life_func(x, y, step) | |
if step == 0 | |
INITIAL_STATES[[x, y]] || 0 | |
else | |
neighbor_diffs = [[-1, -1], [-1, 0], [-1, 1], | |
[ 0, -1], [ 0, 1], | |
[ 1, -1], [ 1, 0], [ 1, 1]] | |
live_neighbors = neighbor_diffs.sum { |x_diff, y_diff| | |
life_func((x + x_diff) % WIDTH, (y + y_diff) % HEIGHT, step - 1) | |
} | |
if life_func(x, y, step - 1) == 1 | |
[2, 3].include?(live_neighbors) ? 1 : 0 | |
else | |
live_neighbors == 3 ? 1 : 0 | |
end | |
end | |
end | |
state = { | |
hash_implementation: { | |
string: nil, | |
last_updated: Time.now | |
}, | |
function_implementation: { | |
string: nil, | |
last_updated: Time.now | |
} | |
} | |
thread1 = Thread.new do | |
step = 0 | |
loop do | |
state[:function_implementation][:string] = (0...HEIGHT).map { |y| | |
(0...WIDTH).map { |x| | |
life_func(x, y, step) == 1 ? 'O' : '⋅' | |
}.join | |
}.join("\n") << "\n#{step + 1} steps completed (function)" | |
state[:function_implementation][:last_updated] = Time.now | |
sleep initial_sleep if step == 0 | |
sleep 0.05 | |
step += 1 | |
end | |
end | |
thread2 = Thread.new do | |
step = 0 | |
loop do | |
state[:hash_implementation][:string] = (0...HEIGHT).map { |y| | |
(0...WIDTH).map { |x| | |
life_hash[[x, y, step]] == 1 ? 'O' : '⋅' | |
}.join | |
}.join("\n") << "\n#{step + 1} steps completed (hash)" | |
state[:hash_implementation][:last_updated] = Time.now | |
sleep initial_sleep if step == 0 | |
sleep 0.05 | |
step += 1 | |
end | |
end | |
Curses.init_screen | |
main_window = Curses::Window.new(25, 80, 0, 0) | |
subwindow_width = [WIDTH + 2, 31].max | |
windows = { | |
hash_implementation: { | |
last_updated: Time.now - 1, | |
window: main_window.subwin(HEIGHT + 3, subwindow_width, 1, 0) | |
}, | |
function_implementation: { | |
last_updated: Time.now - 1, | |
window: main_window.subwin(HEIGHT + 3, subwindow_width, 1, subwindow_width + 1) | |
} | |
} | |
timer_window = main_window.subwin(1, 80, 0, 0) | |
def render_frame(timer_window, windows, state, init: nil) | |
timer_window.clear | |
timer_window.setpos(1,1) | |
timer_window << "#{'%.2f' % (Time.now - init).round(2)} seconds elapsed /" if init | |
mem = GetProcessMem.new | |
timer_window << " Memory used : #{mem.mb.round(0)} MB" | |
timer_window.refresh | |
windows.each do |implementation, window_data| | |
window = window_data[:window] | |
if state[implementation][:last_updated] > window_data[:last_updated] | |
window.clear | |
window_data[:last_updated] = Time.now | |
state[implementation][:string].each_line.with_index do |line, index| | |
window.setpos(index + 1, 1) | |
window << line | |
end | |
window.box('|', '-') | |
window.refresh | |
end | |
end | |
end | |
sleep 0.01 until state.each_value.all? { |val| val[:string] } | |
render_frame(timer_window, windows, state) | |
sleep initial_sleep | |
init = Time.now | |
while Time.now < init + 60 | |
render_frame(timer_window, windows, state, init: init) | |
sleep 0.05 | |
end | |
Curses.close_screen |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
gem install get_process_mem | |
gem install curses |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
On windows you also have to
gem install sys-proctable