Skip to content

Instantly share code, notes, and snippets.

@amcaplan
Created November 18, 2020 12:54
Show Gist options
  • Save amcaplan/426a6bdf2f06c4fdb3d6d69136e35515 to your computer and use it in GitHub Desktop.
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
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
gem install get_process_mem
gem install curses
Copy link

ghost commented Nov 18, 2020

On windows you also have to gem install sys-proctable

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment