Skip to content

Instantly share code, notes, and snippets.

@obelisk68
Last active May 8, 2022 10:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save obelisk68/3a0118c8f3e0990546d928aa72f2aab3 to your computer and use it in GitHub Desktop.
Save obelisk68/3a0118c8f3e0990546d928aa72f2aab3 to your computer and use it in GitHub Desktop.
エディタ付きライフゲーム(Ruby)
require 'gtk2'
module LifeGame
class Field
MG = 4
Small, Large = [70, 50], [85, 60]
def initialize
@width, @height = Small
@size = :small
clear
@step = @stored_step = 0
@reserved = copy
@store = []
end
attr_reader :step, :size
attr_accessor :width, :height
def set_cell(x, y)
@field[y + MG][x + MG] = 1
end
def reset_cell(x, y)
@field[y + MG][x + MG] = 0
end
def get_cell(x, y)
@field[y + MG][x + MG]
end
def next
@before = copy
@store << @before
@store.shift if @store.size > 20
nxf = new_field()
each_cell do |x, y|
n = alive_cells(x, y)
if get_cell(x, y).zero?
nxf[y + MG][x + MG] = 1 if n == 3
else
nxf[y + MG][x + MG] = 1 if n == 2 or n == 3
end
end
@field = nxf
end
def alive_cells(x, y)
get_cell(x - 1, y - 1) + get_cell(x, y - 1) + get_cell(x + 1, y - 1) +
get_cell(x - 1, y) + get_cell(x + 1, y) +
get_cell(x - 1, y + 1) + get_cell(x, y + 1) + get_cell(x + 1, y + 1)
end
def each_cell
(@height + (MG - 1) * 2).times do |y|
(@width + (MG - 1) * 2).times {|x| yield(x - MG + 1, y - MG + 1)}
end
end
def new_field
Array.new(@height + MG * 2) {Array.new(@width + MG * 2, 0)}
end
def copy
copied = new_field()
each_cell do |x, y|
copied[y + MG][x + MG] = get_cell(x, y)
end
copied
end
def renewal?(x, y)
@before[y + MG][x + MG] != get_cell(x, y)
end
def preserve
@reserved = copy
@stored_step = @step
end
def restore
@field = @reserved
@step = @stored_step
end
def clear
@field = new_field()
end
def load(file_name)
size = nil
open(file_name, "r") do |io|
size = /^Size : \((.+)\)$/.match(io.gets.chomp)[1].to_sym
@field = []
@step = io.gets.chomp.scan(/\d+/)[0].to_i
io.each_line do |line|
@field << line.chomp.split(",").map(&:to_i)
end
end
change_window_size(size, false)
end
def save(file_name)
open(file_name, "w") do |io|
io.puts "Size : (#{@size.to_s})"
io.puts "Step : #{@step}"
@field.each {|x| io.puts x.map(&:to_s).join(",")}
end
end
def count
@step += 1
end
def step_reset
@step = 0
end
def back
return if @store.empty?
@field = @store.pop
@step -= 1
end
def change_window_size(size, f = true)
return false if @size == size
@size = size
@width, @height = (@size == :small) ? Small : Large
if f
field_convert(@size)
else
@step = @stored_step = 0
end
@reserved = copy
@store = []
true
end
def field_convert(size)
mg_w = (Large[0] - Small[0]) / 2
mg_h = (Large[1] - Small[1]) / 2
a = MG - 1
converted_field = new_field()
(Small[1] + a * 2).times do |y|
(Small[0] + a * 2).times do |x|
if size == :small
converted_field[y + 1][x + 1] = 1 if get_cell(x - a + mg_w, y - a + mg_h) == 1
else
converted_field[y + 1 + mg_h][x + 1 + mg_w] = 1 if get_cell(x - a, y - a) == 1
end
end
end
@field = converted_field
end
end
class FieldArea < Gtk::DrawingArea
CellSize = 11
Space = 1
L = CellSize + Space * 2
WaitChange = 50
def initialize(field)
super()
@f = field
@areaw, @areah = @f.width * L, @f.height * L
set_size_request(@areaw, @areah)
colormap = Gdk::Colormap.system
@color = {black: Gdk::Color.new(0, 0, 0),
green: Gdk::Color.new(0xc784, 0xffff, 0x52c4),
orange: Gdk::Color.new(0xffff, 0xbb85, 0xf7a),
grid_color: Gdk::Color.new(0x4b79, 0x4b79, 0x4b79)}
@color.each_value {|v| colormap.alloc_color(v, false, true)}
@cell_color = :green
@grid = false
@wait_time = 500
signal_connect("expose_event") do
@gc = Gdk::GC.new(window)
background_renewal
end
@time_goes = false
add_events(Gdk::Event::BUTTON_PRESS_MASK | Gdk::Event::BUTTON_MOTION_MASK)
event_to_xy = ->(e) {[e.x.to_i / L, e.y.to_i / L]}
signal_connect("button_press_event") do |w, e|
unless @time_goes
x, y = event_to_xy.(e)
@f.get_cell(x, y).zero? ? set_cell(x, y) : reset_cell(x, y)
end
end
signal_connect("motion_notify_event") do |w, e|
unless @time_goes
x, y = event_to_xy.(e)
set_cell(x, y) if e.state.button1_mask?
reset_cell(x, y) if e.state.button3_mask?
end
end
set_wait_time
end
attr_writer :time_goes, :label1, :label2
attr_reader :areaw, :areah
def set_color(color_name)
@gc.rgb_fg_color = @color[color_name]
end
def set_cell(x, y)
set_color(@cell_color)
@f.set_cell(x, y)
window.draw_rectangle(@gc, true, L * x + Space, L * y + Space, CellSize, CellSize)
end
def reset_cell(x, y)
set_color(:black)
@f.reset_cell(x, y)
window.draw_rectangle(@gc, true, L * x + Space, L * y + Space, CellSize, CellSize)
end
def go_on_one_step
@f.next
each_cell do |x, y|
renewal_one_place(x, y) if @f.renewal?(x, y)
end
@f.count
show_step
end
def renewal_one_place(x, y)
@f.get_cell(x, y).zero? ? reset_cell(x, y) : set_cell(x, y)
end
def preserve
@f.preserve
end
def restore
@f.restore
redraw
show_step
end
def show_step
@label1.set_text("Step : #{@f.step}")
end
def redraw
each_cell {|x, y| renewal_one_place(x, y)}
end
def each_cell
@f.height.times do |y|
@f.width.times {|x| yield(x, y)}
end
end
def clear
@f.clear
redraw
@f.step_reset
show_step
end
def scatter
co1 = co2 = 0
while co1 < 100 and co2 < 5000
x, y = rand(@f.width), rand(@f.height)
if @f.get_cell(x, y).zero?
set_cell(x, y)
co1 += 1
else
co2 += 1
end
end
end
def save_file(file_name)
@f.save(file_name)
end
def load_file(file_name)
is_changed = @f.load(file_name)
area_size_change if is_changed
redraw
show_step
return is_changed
end
def background_renewal
set_color(@grid ? :grid_color : :black)
window.draw_rectangle(@gc, true, 0, 0, @areaw, @areah)
redraw
end
def grid
@grid = !@grid
background_renewal
end
def set_cell_color(color)
@cell_color = color
redraw
end
def show_wait_time
@label2.set_text("#{@wait_time} ミリ秒")
end
def slower
@wait_time += WaitChange
set_wait_time
show_wait_time
end
def faster
@wait_time -= WaitChange
@wait_time = 50 if @wait_time < 50
set_wait_time
show_wait_time
end
def set_wait_time
Gtk.timeout_remove(@timer_id) if @timer_id
@timer_id = Gtk.timeout_add(@wait_time) do
go_on_one_step if @time_goes
true
end
end
def back
@f.back
redraw
show_step
end
def change_window_size(size)
if @f.change_window_size(size)
area_size_change
show_step
true
else
false
end
end
def area_size_change
@areaw, @areah = @f.width * L, @f.height * L
end
end
class SideBar < Gtk::VBox
MGN = 3
SideBar_W = 140
def initialize(field_area, main_window)
super()
set_size_request(SideBar_W, field_area.areah)
@farea = field_area
@mainw = main_window
@edit_mode = true
set_box1
set_box2
set_box3
set_box4
set_box5
set_box6
end
def set_box1
start_bt = Gtk::Button.new("開始")
stop_bt = Gtk::Button.new("停止")
step_bt = Gtk::Button.new("1ステップ進める")
back_bt = Gtk::Button.new("1ステップ戻る")
start_bt.signal_connect("clicked") do
@edit_mode = false
@farea.time_goes = true
end
stop_bt.signal_connect("clicked") do
@edit_mode = true
@farea.time_goes = false
end
step_bt.signal_connect("clicked") do
@farea.go_on_one_step if @edit_mode
end
back_bt.signal_connect("clicked") do
@farea.back if @edit_mode
end
l = Gtk::Label.new("")
@farea.label1 = l
pack([start_bt, stop_bt, step_bt, back_bt, l])
end
def set_box2
arrow1 = Gtk::Arrow.new(Gtk::Arrow::LEFT , Gtk::SHADOW_IN)
arrow2 = Gtk::Arrow.new(Gtk::Arrow::RIGHT, Gtk::SHADOW_IN)
l1 = Gtk::Label.new("ウェイト")
left_arrow_bt = Gtk::Button.new
right_arrow_bt = Gtk::Button.new
l2 = Gtk::Label.new("")
@farea.label2 = l2
@farea.show_wait_time
left_arrow_bt.signal_connect("clicked") do
@farea.slower
end
right_arrow_bt.signal_connect("clicked") do
@farea.faster
end
left_arrow_bt .add(arrow1)
right_arrow_bt.add(arrow2)
small_box = pack_small_box([left_arrow_bt, right_arrow_bt])
pack([l1, small_box, l2])
end
def set_box3
scatter_bt = Gtk::Button.new("ばらまく")
grid_bt = Gtk::Button.new("格子")
green_bt = Gtk::Button.new("緑色")
orange_bt = Gtk::Button.new("橙色")
clear_bt = Gtk::Button.new("全クリア")
scatter_bt.signal_connect("clicked") do
@farea.scatter if @edit_mode
end
grid_bt.signal_connect("clicked") do
@farea.grid if @edit_mode
end
green_bt.signal_connect("clicked") do
@farea.set_cell_color(:green) if @edit_mode
end
orange_bt.signal_connect("clicked") do
@farea.set_cell_color(:orange) if @edit_mode
end
clear_bt.signal_connect("clicked") do
@farea.clear if @edit_mode
end
small_box = pack_small_box([green_bt, orange_bt])
pack([scatter_bt, grid_bt, small_box, clear_bt])
end
def set_box4
preserve_bt = Gtk::Button.new("一時保存")
restore_bt = Gtk::Button.new("復帰")
preserve_bt.signal_connect("clicked") do
@farea.preserve if @edit_mode
end
restore_bt.signal_connect("clicked") do
@farea.restore if @edit_mode
end
pack([preserve_bt, restore_bt])
end
def set_box5
save_bt = Gtk::Button.new("ファイルに保存")
load_bt = Gtk::Button.new("ファイルの読み込み")
save_bt.signal_connect("clicked") do
if @edit_mode
file_name = select_file("Save File", Gtk::FileChooser::ACTION_SAVE)
@farea.save_file(file_name) if file_name
end
end
load_bt.signal_connect("clicked") do
if @edit_mode
file_name = select_file("Load File", Gtk::FileChooser::ACTION_OPEN)
main_window_change(@farea.load_file(file_name)) if file_name
end
end
pack([save_bt, load_bt])
end
def set_box6
large_bt = Gtk::Button.new("大")
small_bt = Gtk::Button.new("小")
close_bt = Gtk::Button.new("終了")
large_bt.signal_connect("clicked") do
change_window_size(:large) if @edit_mode
end
small_bt.signal_connect("clicked") do
change_window_size(:small) if @edit_mode
end
close_bt.signal_connect("clicked") do
Gtk.main_quit if @edit_mode
end
small_box = pack_small_box([large_bt, small_bt])
box = Gtk::VBox.new
pack_start(box, false, false, 0)
pack_box(box, [small_box, close_bt])
end
def pack_small_box(widgets)
small_box = Gtk::HBox.new
widgets.each {|wi| small_box.pack_start(wi, true, true, 1)}
small_box
end
def pack(widgets)
box = Gtk::VBox.new
pack_start(box, false, false, 0)
pack_box(box, widgets)
box.pack_start(Gtk::HSeparator.new, false, false, MGN)
end
def pack_box(box, widgets)
widgets.each {|wi| box.pack_start(wi, false, false, MGN)}
end
def change_window_size(size)
main_window_change(@farea.change_window_size(size))
end
def main_window_change(is_changed)
@mainw.set_size_request(@farea.areaw + SideBar_W, @farea.areah) if is_changed
end
def select_file(title, mode)
file_name = nil
dialog = Gtk::FileChooserDialog.new(title,
nil, mode, nil,
[Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL],
[Gtk::Stock::OPEN , Gtk::Dialog::RESPONSE_ACCEPT])
if dialog.run == Gtk::Dialog::RESPONSE_ACCEPT
file_name = dialog.filename
end
dialog.destroy
file_name
end
end
class MainWindow < Gtk::Window
def initialize
super("Life Game")
set_resizable(false)
field = Field.new
field_area = FieldArea.new(field)
side_bar = SideBar.new(field_area, self)
box = Gtk::HBox.new
add(box)
box.pack_start(field_area, true , true, 0)
box.pack_start(side_bar , false, true, 0)
signal_connect("destroy") {Gtk.main_quit}
show_all
end
end
def self.start
MainWindow.new
Gtk.main
end
end
LifeGame.start
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment