Add savefile and modeline :3
This commit is contained in:
parent
e5e0998e2c
commit
7ec21fd308
1 changed files with 266 additions and 93 deletions
359
mewny.rb
359
mewny.rb
|
|
@ -7,6 +7,19 @@ require 'optparse'
|
|||
PUBLIC_HOST = '0.0.0.0'
|
||||
PRIVATE_HOST = '127.0.0.1'
|
||||
DEFAULT_CLIENT_HOST = '127.0.0.1'
|
||||
VERSION = {
|
||||
:SRV_TXT => '0.01',
|
||||
:CLN_TXT => '0.01',
|
||||
:CLN_ARO => '0.02',
|
||||
:SRV_CO8 => '0.30', # 8-color
|
||||
:CLN_CO8 => '0.30', # 8-color
|
||||
:SRV_GSZ => '0.40',
|
||||
|
||||
:FIL_TXT => '0.01',
|
||||
:FIL_CUP => '0.02',
|
||||
:FIL_CO8 => '0.30',
|
||||
}
|
||||
|
||||
options = {
|
||||
:port => 7570,
|
||||
:server => false,
|
||||
|
|
@ -14,6 +27,9 @@ options = {
|
|||
:server_host => PRIVATE_HOST,
|
||||
:rows => 24,
|
||||
:cols => 40,
|
||||
:file => nil,
|
||||
:file_lockout => false,
|
||||
:file_interval => 600,
|
||||
}
|
||||
OptionParser.new do |opt|
|
||||
opt.on('-pPORT', '--port PORT', Integer,
|
||||
|
|
@ -34,6 +50,11 @@ OptionParser.new do |opt|
|
|||
'(client only) Host to connect to (defaults to `localhost`)') do |o|
|
||||
options[:host] = o
|
||||
end
|
||||
opt.on('-tSECONDS', '--save-interval SECONDS', Float,
|
||||
'(server only) How long to wait between file writes in seconds. (defaults to 600 which is 10 minutes)'
|
||||
) do |o|
|
||||
options[:file_interval] = o
|
||||
end
|
||||
opt.on('-rROWS', '--rows ROWS', Integer,
|
||||
'(server only) The number of rows to have in the shared buffer'
|
||||
) do |o|
|
||||
|
|
@ -58,11 +79,49 @@ OptionParser.new do |opt|
|
|||
) do
|
||||
options[:server_host] = PRIVATE_HOST
|
||||
end
|
||||
opt.on('-f FILE', '--save-file FILE',
|
||||
'(server only) File to read initial state from and save state to.'
|
||||
) do |f|
|
||||
break if options[:file_lockout]
|
||||
options[:file_lockout] = true
|
||||
file_stuff = {
|
||||
:name => f,
|
||||
:text => '',
|
||||
:color => [],
|
||||
:cursor => 0,
|
||||
:writeme => false
|
||||
}
|
||||
unless File.exist? f
|
||||
file_stuff[:writeme] = true
|
||||
options[:file] = file_stuff
|
||||
options[:file_lockout] = false
|
||||
next
|
||||
end
|
||||
File.readlines(f, chomp: true).each do |msg|
|
||||
ver, msg = msg.chomp.split('|',2)
|
||||
next unless msg != nil
|
||||
case ver
|
||||
when VERSION[:FIL_TXT]
|
||||
file_stuff[:text] = msg
|
||||
when VERSION[:FIL_CO8]
|
||||
msg.each_char.with_index do |c, i|
|
||||
next unless c =~ /[0-7]/
|
||||
file_stuff[:color] << {
|
||||
:co8 => 7
|
||||
} until file_stuff[:color].length > i
|
||||
file_stuff[:color][i][:co8] = c.to_i.clamp(0,7)
|
||||
end
|
||||
when VERSION[:FIL_CUP]
|
||||
file_stuff[:cursor] = msg.to_i
|
||||
end
|
||||
end
|
||||
options[:file] = file_stuff
|
||||
end
|
||||
opt.on('-h', '--help', 'Get help with this command') do
|
||||
puts opt
|
||||
exit
|
||||
end
|
||||
end.parse!(into: options)
|
||||
end.parse!
|
||||
|
||||
HOST = options[:host]
|
||||
SRV_HOST = options[:server_host]
|
||||
|
|
@ -70,15 +129,22 @@ PORT = options[:port]
|
|||
|
||||
ROWS = options[:rows]
|
||||
COLS = options[:cols]
|
||||
VERSION = {
|
||||
:SRV_TXT => '0.01',
|
||||
:CLN_TXT => '0.01',
|
||||
:CLN_ARO => '0.02',
|
||||
:SRV_CO8 => '0.30', # 8-color
|
||||
:CLN_CO8 => '0.30', # 8-color
|
||||
}
|
||||
FILE_INTERVAL = options[:file_interval]
|
||||
|
||||
def do_server(file_opts)
|
||||
def write_current(wi, buffer, cobuf, file_opts)
|
||||
return if file_opts.nil?
|
||||
File.open(file_opts[:name], 'w') do |f|
|
||||
f.puts("#{VERSION[:FIL_TXT]}|#{clean_chars(buffer)}\n")
|
||||
f.write("#{VERSION[:FIL_CO8]}|")
|
||||
cobuf.each do |color|
|
||||
f.write(color[:co8].to_s)
|
||||
end
|
||||
f.puts ''
|
||||
f.puts("#{VERSION[:FIL_CUP]}|#{wi}")
|
||||
end
|
||||
end
|
||||
|
||||
def do_server
|
||||
server = TCPServer.new(SRV_HOST, PORT)
|
||||
sockets = [server]
|
||||
sockats = {
|
||||
|
|
@ -88,19 +154,33 @@ def do_server
|
|||
}
|
||||
}
|
||||
}
|
||||
if file_opts.nil? || file_opts[:writeme]
|
||||
pre = "#{ROWS}r#{COLS}c >>> "
|
||||
buffer = pre.ljust(ROWS*COLS)[0...ROWS*COLS]
|
||||
cobuf = Array.new(ROWS*COLS, {
|
||||
:co8 => 7
|
||||
})
|
||||
windex = pre.length # Wipe your buffer clean with your write index
|
||||
else
|
||||
buffer = file_opts[:text].ljust(ROWS*COLS)[0...ROWS*COLS]
|
||||
cobuf = file_opts[:color]
|
||||
if cobuf.length < ROWS*COLS
|
||||
cobuf += Array.new(ROWS*COLS - cobuf.length ) { {:co8 => 7} }
|
||||
else
|
||||
cobuf = cobuf.first(ROWS*COLS)
|
||||
end
|
||||
windex = file_opts[:cursor]
|
||||
end
|
||||
windex = windex%buffer.length
|
||||
if not file_opts.nil?
|
||||
write_current(windex, buffer, cobuf, file_opts)
|
||||
end
|
||||
|
||||
puts "PORT: #{PORT}"
|
||||
puts "GRID SIZE: #{ROWS}r#{COLS}c"
|
||||
if SRV_HOST == PUBLIC_HOST then puts "EXPOSED" end
|
||||
puts 'Listening...'
|
||||
|
||||
pre = "#{ROWS}r#{COLS}c >>> "
|
||||
buffer = pre.ljust(ROWS*COLS)[0...ROWS*COLS]
|
||||
cobuf = Array.new(ROWS*COLS, {
|
||||
:co8 => 7
|
||||
})
|
||||
windex = pre.length # Wipe your buffer clean with your write index
|
||||
windex = windex%buffer.length
|
||||
motions = []
|
||||
|
||||
def new_buf_cob_wi_out(buf, cob, wi, newtext, newcolors, motions: [])
|
||||
|
|
@ -157,88 +237,128 @@ def do_server
|
|||
[buf, cob, wi, out]
|
||||
end
|
||||
|
||||
loop do
|
||||
ready, = IO.select(sockets)
|
||||
newtext = ''
|
||||
newcolors = []
|
||||
|
||||
ready.each do |sck|
|
||||
if sck == server
|
||||
client = server.accept
|
||||
sockats[client] = {
|
||||
:color => {
|
||||
:co8 => 7
|
||||
begin
|
||||
old = {
|
||||
:windex => windex.dup,
|
||||
:buffer => buffer.dup,
|
||||
:cobuf => cobuf.dup,
|
||||
:lastsave => Time.now,
|
||||
}
|
||||
Thread.new do
|
||||
loop do
|
||||
time_since_next_save = Time.now - old[:lastsave]
|
||||
time_till_next_save = FILE_INTERVAL - time_since_next_save
|
||||
# To ensure thread doesn't bog down cpu too much.....
|
||||
sleep [
|
||||
[
|
||||
[1.0, # Wait a second
|
||||
time_till_next_save/2 # or half the remaining time
|
||||
].max, # Whichever's longer
|
||||
time_till_next_save].min, # Unless the remaining time is shorter than a second
|
||||
1.0].max # But don't wait longer than no time at all
|
||||
unless (old[:windex] == windex &&
|
||||
old[:buffer] == buffer &&
|
||||
old[:cobuf] == cobuf)
|
||||
next if file_opts.nil?
|
||||
next if time_since_next_save < FILE_INTERVAL
|
||||
old = {
|
||||
:lastsave => Time.now,
|
||||
:windex => windex.dup,
|
||||
:buffer => buffer.dup,
|
||||
:cobuf => cobuf.dup
|
||||
}
|
||||
}
|
||||
sockets << client
|
||||
puts "Connection: #{client.peeraddr}"
|
||||
client.puts new_buf_cob_wi_out(buffer,cobuf,0,buffer,cobuf)[3]
|
||||
if buffer.length > 1
|
||||
client.puts new_buf_cob_wi_out(
|
||||
buffer,cobuf,windex-1,
|
||||
buffer[windex-1],[cobuf[windex-1]])[3]
|
||||
write_current(windex, buffer, cobuf, file_opts)
|
||||
puts "[#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}] File autosaved."
|
||||
end
|
||||
next
|
||||
end
|
||||
begin
|
||||
msg = sck.gets
|
||||
unless msg
|
||||
puts "Disconnect: #{sck.peeraddr}"
|
||||
sck.close
|
||||
sockets.delete(sck)
|
||||
sockats.delete(sck)
|
||||
end
|
||||
loop do
|
||||
ready, = IO.select(sockets)
|
||||
newtext = ''
|
||||
newcolors = []
|
||||
|
||||
ready.each do |sck|
|
||||
if sck == server
|
||||
client = server.accept
|
||||
sockats[client] = {
|
||||
:color => {
|
||||
:co8 => 7
|
||||
}
|
||||
}
|
||||
sockets << client
|
||||
puts "Connection: #{client.peeraddr}"
|
||||
client.puts "#{VERSION[:SRV_GSZ]}|#{ROWS}|#{COLS}"
|
||||
client.puts new_buf_cob_wi_out(buffer,cobuf,0,buffer,cobuf)[3]
|
||||
if buffer.length > 1
|
||||
client.puts new_buf_cob_wi_out(
|
||||
buffer,cobuf,windex-1,
|
||||
buffer[windex-1],[cobuf[windex-1]])[3]
|
||||
end
|
||||
next
|
||||
end
|
||||
ver, msg = msg.chomp.split('|',2)
|
||||
next unless msg != nil
|
||||
case ver
|
||||
when VERSION[:CLN_TXT]
|
||||
newtext << clean_chars(msg)
|
||||
msg.length.times do
|
||||
newcolors << sockats[sck][:color].dup
|
||||
end
|
||||
when VERSION[:CLN_ARO]
|
||||
dir = 'l'
|
||||
case msg
|
||||
when 'A'
|
||||
dir = 'u'
|
||||
when 'B'
|
||||
dir = 'd'
|
||||
when 'C'
|
||||
dir = 'r'
|
||||
when 'D'
|
||||
dir = 'l'
|
||||
else
|
||||
begin
|
||||
msg = sck.gets
|
||||
unless msg
|
||||
puts "Disconnect: #{sck.peeraddr}"
|
||||
sck.close
|
||||
sockets.delete(sck)
|
||||
sockats.delete(sck)
|
||||
next
|
||||
end
|
||||
motions << {
|
||||
:nti => newtext.length - 1,
|
||||
:dir => dir,
|
||||
}
|
||||
when VERSION[:CLN_CO8]
|
||||
sockats[sck][:color][:co8] = msg.to_i.clamp(0,7)
|
||||
end
|
||||
rescue Errno::ECONNRESET, Errno::EPIPE
|
||||
puts "Disconnect (err): #{sck.peeraddr}"
|
||||
sck.close
|
||||
sockets.delete(sck)
|
||||
end
|
||||
buffer, cobuf, windex, out = new_buf_cob_wi_out(buffer,cobuf,windex,newtext,newcolors,motions:motions)
|
||||
motions = []
|
||||
clients = sockets - [server]
|
||||
clients.each do |c|
|
||||
begin
|
||||
c.puts out
|
||||
ver, msg = msg.chomp.split('|',2)
|
||||
next unless msg != nil
|
||||
case ver
|
||||
when VERSION[:CLN_TXT]
|
||||
newtext << clean_chars(msg)
|
||||
msg.length.times do
|
||||
newcolors << sockats[sck][:color].dup
|
||||
end
|
||||
when VERSION[:CLN_ARO]
|
||||
dir = 'l'
|
||||
case msg
|
||||
when 'A'
|
||||
dir = 'u'
|
||||
when 'B'
|
||||
dir = 'd'
|
||||
when 'C'
|
||||
dir = 'r'
|
||||
when 'D'
|
||||
dir = 'l'
|
||||
else
|
||||
next
|
||||
end
|
||||
motions << {
|
||||
:nti => newtext.length - 1,
|
||||
:dir => dir,
|
||||
}
|
||||
when VERSION[:CLN_CO8]
|
||||
sockats[sck][:color][:co8] = msg.to_i.clamp(0,7)
|
||||
end
|
||||
rescue Errno::ECONNRESET, Errno::EPIPE
|
||||
puts "Disconnect (err): #{c.peeraddr}"
|
||||
c.close
|
||||
sockets.delete(c)
|
||||
puts "Disconnect (err): #{sck.peeraddr}"
|
||||
sck.close
|
||||
sockets.delete(sck)
|
||||
end
|
||||
buffer, cobuf, windex, out = new_buf_cob_wi_out(buffer,cobuf,windex,newtext,newcolors,motions:motions)
|
||||
motions = []
|
||||
clients = sockets - [server]
|
||||
clients.each do |c|
|
||||
begin
|
||||
c.puts out
|
||||
rescue Errno::ECONNRESET, Errno::EPIPE
|
||||
puts "Disconnect (err): #{c.peeraddr}"
|
||||
c.close
|
||||
sockets.delete(c)
|
||||
end
|
||||
end
|
||||
#if out != ''
|
||||
# puts "#{windex}: (#{(windex/COLS)+1},#{(windex%COLS)+1})"
|
||||
#end
|
||||
end
|
||||
#if out != ''
|
||||
# puts "#{windex}: (#{(windex/COLS)+1},#{(windex%COLS)+1})"
|
||||
#end
|
||||
end
|
||||
ensure
|
||||
write_current(windex, buffer, cobuf, file_opts)
|
||||
puts "File saved on exit."
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -256,12 +376,61 @@ def clean_chars(chars)
|
|||
out
|
||||
end
|
||||
|
||||
|
||||
def do_client
|
||||
socket = TCPSocket.new(HOST, PORT)
|
||||
grid_rows = 24
|
||||
grid_cols = 40
|
||||
|
||||
puts "PORT: #{PORT}"
|
||||
puts "HOST: #{HOST}"
|
||||
|
||||
def print_mode(mode,gridrows)
|
||||
return if ENV["TERM"].downcase == "dumb"
|
||||
#print ("\x1b[s"+ # Save cursor
|
||||
# "\x1b[m"+ # Reset color
|
||||
# "\x1b[#{gridrows+1};1H"+# Cursor pos
|
||||
# "\x1b[2K"+ # Clear line
|
||||
# mode+ # Print mode
|
||||
# "\x1b[u") # Restore cursor
|
||||
print "\e[s\e[m\e[#{gridrows+1};1H\e[2K#{mode}\e[u"
|
||||
end
|
||||
|
||||
def parse_esckey(gridrows)
|
||||
begin
|
||||
char2 = STDIN.read_nonblock(1)
|
||||
return [:RAW, "\e#{char2}"] unless char2 == '['
|
||||
rescue
|
||||
return [:KEY_ESCAPE, '']
|
||||
end
|
||||
begin
|
||||
char3 = STDIN.read_nonblock(1)
|
||||
rescue
|
||||
return [:RAW, "\e#{char2}"]
|
||||
end
|
||||
case char3
|
||||
when 'A'
|
||||
return [:ARROW, 'A']
|
||||
when 'B'
|
||||
return [:ARROW, 'B']
|
||||
when 'C'
|
||||
return [:ARROW, 'C']
|
||||
when 'D'
|
||||
return [:ARROW, 'D']
|
||||
else
|
||||
loop do
|
||||
begin
|
||||
char3 << STDIN.read_nonblock(1)
|
||||
break unless char3[-1] =~ /[0-9:;\<=\>\?]/
|
||||
rescue
|
||||
break
|
||||
end
|
||||
end
|
||||
print_mode "#{clean_chars("\e"+char2+char3)} -- I'm unfamiliar with that key.",gridrows
|
||||
return [:RAW, "\e#{char2}#{char3}"]
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
print "\x1b[?1049h\x1b[2J\x1b[3J" # Open and clear alternate buffer
|
||||
print "\x1b[m" # Reset color
|
||||
|
|
@ -280,12 +449,16 @@ def do_client
|
|||
when VERSION[:SRV_CO8]
|
||||
next if ENV["TERM"].downcase == "dumb"
|
||||
print "\x1b[3#{msg.to_i.clamp(0,7)}m"
|
||||
when VERSION[:SRV_GSZ]
|
||||
grid_rows, grid_cols = msg.split('|',2).map { |p| p.to_i }
|
||||
print_mode ":3",grid_rows
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
socket.puts "#{VERSION[:CLN_CO8]}|#{rand(1..7)}"
|
||||
while (char = STDIN.noecho(&:getch))
|
||||
print_mode ":3",grid_rows
|
||||
exit if char == "\x04" # ^D
|
||||
if char == "\x0F" # (^O) Color change
|
||||
if /[0-7]/.match? (char2 = STDIN.noecho(&:getch).upcase)
|
||||
|
|
@ -295,12 +468,12 @@ def do_client
|
|||
char << char2
|
||||
end
|
||||
elsif char == "\x1b" # Detect arrows
|
||||
char2 = STDIN.noecho(&:getch)
|
||||
if /[A-D]/.match? (char3 = STDIN.noecho(&:getch).upcase)
|
||||
socket.puts "#{VERSION[:CLN_ARO]}|#{char3}"
|
||||
keytype, keytext = parse_esckey(grid_rows)
|
||||
if keytype == :ARROW
|
||||
socket.puts "#{VERSION[:CLN_ARO]}|#{keytext}"
|
||||
next
|
||||
else
|
||||
char << char2+char3
|
||||
elsif keytype != :KEY_ESCAPE
|
||||
char = keytext
|
||||
end
|
||||
elsif (char == "\x08" || # backspace
|
||||
char == "\x7F" ) # delete
|
||||
|
|
@ -318,7 +491,7 @@ def do_client
|
|||
end
|
||||
|
||||
if options[:server]
|
||||
do_server
|
||||
do_server(options[:file])
|
||||
else
|
||||
do_client
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue