Last active
May 8, 2022 10:26
-
-
Save obelisk68/3a0118c8f3e0990546d928aa72f2aab3 to your computer and use it in GitHub Desktop.
エディタ付きライフゲーム(Ruby)
This file contains hidden or 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 '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