alchemist-server 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +5 -0
  3. data/README.md +118 -0
  4. data/Rakefile +1 -0
  5. data/alchemist-server.gemspec +29 -0
  6. data/bin/alchemist-repl +37 -0
  7. data/bin/alchemist-server +10 -0
  8. data/lib/alchemist-server.rb +151 -0
  9. data/lib/alchemist-server/avatar.rb +82 -0
  10. data/lib/alchemist-server/commands/appear.rb +13 -0
  11. data/lib/alchemist-server/commands/base.rb +31 -0
  12. data/lib/alchemist-server/commands/basics.rb +12 -0
  13. data/lib/alchemist-server/commands/compounds.rb +13 -0
  14. data/lib/alchemist-server/commands/create.rb +15 -0
  15. data/lib/alchemist-server/commands/describe.rb +18 -0
  16. data/lib/alchemist-server/commands/directions.rb +48 -0
  17. data/lib/alchemist-server/commands/element.rb +16 -0
  18. data/lib/alchemist-server/commands/forge.rb +18 -0
  19. data/lib/alchemist-server/commands/formulate.rb +20 -0
  20. data/lib/alchemist-server/commands/inventory.rb +12 -0
  21. data/lib/alchemist-server/commands/location.rb +13 -0
  22. data/lib/alchemist-server/commands/lock.rb +12 -0
  23. data/lib/alchemist-server/commands/look.rb +14 -0
  24. data/lib/alchemist-server/commands/message.rb +15 -0
  25. data/lib/alchemist-server/commands/put.rb +16 -0
  26. data/lib/alchemist-server/commands/read.rb +18 -0
  27. data/lib/alchemist-server/commands/take.rb +17 -0
  28. data/lib/alchemist-server/commands/who.rb +18 -0
  29. data/lib/alchemist-server/curses/geography_window.rb +63 -0
  30. data/lib/alchemist-server/curses/glyph_window.rb +89 -0
  31. data/lib/alchemist-server/curses/item_window.rb +59 -0
  32. data/lib/alchemist-server/curses/messages_window.rb +43 -0
  33. data/lib/alchemist-server/curses/prompt_window.rb +35 -0
  34. data/lib/alchemist-server/direction.rb +28 -0
  35. data/lib/alchemist-server/element.rb +14 -0
  36. data/lib/alchemist-server/event.rb +43 -0
  37. data/lib/alchemist-server/formula.rb +24 -0
  38. data/lib/alchemist-server/geography.rb +111 -0
  39. data/lib/alchemist-server/outcome.rb +12 -0
  40. data/lib/alchemist-server/record.rb +51 -0
  41. data/lib/alchemist-server/server_handler.rb +143 -0
  42. data/lib/alchemist-server/version.rb +8 -0
  43. data/lib/alchemist-server/world.rb +206 -0
  44. data/lib/alchemist-server/world_history.rb +45 -0
  45. data/script/alchemist-curses +307 -0
  46. metadata +148 -0
@@ -0,0 +1,59 @@
1
+ module Alchemist
2
+ module Curses
3
+ class ItemWindow
4
+ include FFI::NCurses
5
+
6
+ def initialize(label, size, line, column)
7
+ @label = label
8
+ @width = label.length + size * 2
9
+ @win = newwin 1, @width, line, column
10
+ @items = ""
11
+ end
12
+
13
+ def update(items)
14
+ @items = items.force_encoding Encoding::UTF_8
15
+ draw
16
+ end
17
+
18
+ def draw
19
+ wclear @win
20
+ wprintw @win, "#{@label}#{@items.pad_to_unicode_monospace}"
21
+ wrefresh @win
22
+ end
23
+
24
+ def have_user_select
25
+ wmove @win, 0, @label.length
26
+ wrefresh @win
27
+
28
+ while (c = getch) != KEY_RETURN
29
+ case c
30
+ when KEY_LEFT
31
+ y,x = getyx @win
32
+ new_x = [x - 2, @label.length].max
33
+ wmove @win, 0, new_x
34
+ wrefresh @win
35
+
36
+ when KEY_RIGHT
37
+ y,x = getyx @win
38
+ new_x = [x + 2, @width - 1].min
39
+ wmove @win, 0, new_x
40
+ wrefresh @win
41
+
42
+ when KEY_ESCAPE
43
+ return nil
44
+ end
45
+ end
46
+
47
+ y,x = getyx @win
48
+
49
+ item_at x
50
+ end
51
+
52
+ def item_at(x)
53
+ inv_index = (x - @label.length) / 2
54
+ @items[inv_index]
55
+ end
56
+ end
57
+ end
58
+ end
59
+
@@ -0,0 +1,43 @@
1
+ module Alchemist
2
+ module Curses
3
+ class MessagesWindow
4
+ include FFI::NCurses
5
+
6
+ def initialize(height, width, line, col)
7
+ @height = height
8
+ @width = width
9
+
10
+ @offset = 0
11
+ @messages = []
12
+
13
+ @win = newwin height, width, line, col
14
+ end
15
+
16
+ def draw
17
+ wmove @win, 0, 0
18
+ wclear @win
19
+ wprintw @win, messages_to_show.join("\n")
20
+ wrefresh @win
21
+ end
22
+
23
+ def messages_to_show
24
+ @messages[@offset,@height] || []
25
+ end
26
+
27
+ def update(messages)
28
+ @messages = messages
29
+ draw
30
+ end
31
+
32
+ def scroll_up
33
+ @offset = [@offset-1, 0].max
34
+ draw
35
+ end
36
+
37
+ def scroll_down
38
+ @offset = [@offset+1, @messages.length - height].min
39
+ draw
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,35 @@
1
+ module Alchemist
2
+ module Curses
3
+ class PromptWindow
4
+ include FFI::NCurses
5
+
6
+ def initialize(line, col, width)
7
+ @win = newwin 1, width, line, col
8
+ end
9
+
10
+ def ask(label)
11
+ wclear @win
12
+ wmove @win, 0, 0
13
+ wprintw @win, label
14
+ wprintw @win, ': '
15
+ wrefresh @win
16
+
17
+ answer = ''
18
+
19
+ while (c = wgetch @win) != "\n".ord
20
+ if c > 0
21
+ answer << c
22
+ wprintw @win, (''<<c)
23
+ end
24
+
25
+ wrefresh @win
26
+ end
27
+
28
+ wclear @win
29
+
30
+ answer.strip
31
+ end
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,28 @@
1
+ module Alchemist
2
+ module Direction
3
+ module North
4
+ def self.move_from(x, y)
5
+ [x, y - 1]
6
+ end
7
+ end
8
+
9
+ module South
10
+ def self.move_from(x, y)
11
+ [x, y + 1]
12
+ end
13
+ end
14
+
15
+ module East
16
+ def self.move_from(x, y)
17
+ [x + 1, y]
18
+ end
19
+ end
20
+
21
+ module West
22
+ def self.move_from(x, y)
23
+ [x - 1, y]
24
+ end
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,14 @@
1
+ module Alchemist
2
+ class Element
3
+ include Record
4
+ record_attr :symbol, :name, :basic
5
+
6
+ def basic?
7
+ basic
8
+ end
9
+
10
+ def hex_code
11
+ "0x#{symbol.ord.to_s(16)}"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,43 @@
1
+ module Alchemist
2
+ class Event
3
+ attr_reader :command_string
4
+
5
+ def initialize(command_string, time)
6
+ @command_string = command_string
7
+ @time = time
8
+ end
9
+
10
+ def happen(history)
11
+ avatar_name, command, *args = @command_string.split /\s+/
12
+
13
+ if command_mod = COMMANDS.detect { |c| match_command? command, c }
14
+ command_mod.run avatar_name, history, *args
15
+ else
16
+ Outcome.new response: "error Unknown Command: #{command}"
17
+ end
18
+ end
19
+
20
+ def to_s
21
+ <<-str
22
+ #{@command_string}
23
+ #{@time}
24
+ str
25
+ end
26
+
27
+ protected
28
+
29
+ def match_command?(command_name, command)
30
+ command_name =~ /^#{command.pattern}/
31
+ end
32
+
33
+ def self.parse(string)
34
+ command_string, time, world_string = string.split("\n",3)
35
+ new command_string, Time.parse(time)
36
+ end
37
+
38
+ def self.genesis
39
+ new 'genesis', Time.now
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ module Alchemist
3
+ class Formula
4
+ attr_reader :elem_1, :elem_2, :result
5
+
6
+ def initialize(elem_1, elem_2, result)
7
+ @elem_1 = elem_1
8
+ @elem_2 = elem_2
9
+ @result = result
10
+ end
11
+
12
+ def to_s
13
+ "⨍ #{@elem_1}#{@elem_2}#{@result}"
14
+ end
15
+
16
+ def ==(other)
17
+ other.class == self.class &&
18
+ other.elem_1 == elem_1 &&
19
+ other.elem_2 == elem_2 &&
20
+ other.result == result
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,111 @@
1
+ module Alchemist
2
+ class Geography
3
+ def initialize(locations = {})
4
+ @locations = case locations
5
+ when Hamster::Hash then locations
6
+ else Hamster.hash locations
7
+ end
8
+ end
9
+
10
+ def at(x,y)
11
+ locations[Loc[x,y]]
12
+ end
13
+
14
+ def take(x,y)
15
+ Geography.new locations.delete(Loc[x,y])
16
+ end
17
+
18
+ def put(x,y,c)
19
+ loc = Loc[x,y]
20
+
21
+ if locations.key? loc
22
+ raise "#{locations[loc]} is already at #{x},#{y}"
23
+ end
24
+
25
+ Geography.new locations.put(loc, c[0..1])
26
+ end
27
+
28
+ def string_around(x,y,range)
29
+ string_within x - range,
30
+ y - range,
31
+ x + range,
32
+ y + range
33
+ end
34
+
35
+ def to_s
36
+ (min_x, min_y), (max_x, max_y) = dimensions
37
+ string_within min_x, min_y, max_x, max_y
38
+ end
39
+
40
+ def string_within(min_x, min_y, max_x, max_y)
41
+ if min_x && min_y && max_x && max_y
42
+ (min_y..max_y).map do |y|
43
+ (min_x..max_x).map do |x|
44
+ at(x, y) || ' '
45
+ end.join('')
46
+ end.join("\n")
47
+ else
48
+ ''
49
+ end
50
+ end
51
+
52
+
53
+ def dimensions
54
+ points = locations.keys
55
+ xs,ys = points.map(&:x), points.map(&:y)
56
+
57
+ [[xs.min, ys.min],[xs.max,ys.max]]
58
+ end
59
+
60
+ private
61
+
62
+ def locations
63
+ @locations
64
+ end
65
+
66
+ def offset(x,y)
67
+ w,h = dimensions
68
+ y*(w + 1) + x
69
+ end
70
+
71
+ class Loc
72
+ attr_reader :x, :y
73
+
74
+ def self.[](*args)
75
+ new *args
76
+ end
77
+
78
+ def initialize(x,y)
79
+ @x = x
80
+ @y = y
81
+ end
82
+
83
+ def hash
84
+ (@x.hash * 31) + @y.hash
85
+ end
86
+
87
+ def within?(min, max)
88
+ min.x <= x && x <= max.x &&
89
+ min.y <= y && y <= max.y
90
+ end
91
+
92
+ def distance(loc)
93
+ [
94
+ (loc.x - x).abs,
95
+ (loc.y - y).abs
96
+ ].max
97
+ end
98
+
99
+ def ==(other)
100
+ other.class == self.class &&
101
+ other.x == x &&
102
+ other.y == y
103
+ end
104
+
105
+ def eql?(other)
106
+ self == other
107
+ end
108
+ end
109
+ end
110
+ end
111
+
@@ -0,0 +1,12 @@
1
+ module Alchemist
2
+ class Outcome
3
+ include Record
4
+ include Enumerable
5
+
6
+ record_attr :response,
7
+ :new_world,
8
+ :new_history,
9
+ :nearby_avatar_command
10
+ end
11
+ end
12
+
@@ -0,0 +1,51 @@
1
+ module Alchemist
2
+ module Record
3
+ def self.included(mod)
4
+ mod.extend ClassMethods
5
+ end
6
+
7
+ def initialize(attrs = {})
8
+ @attrs = Record.hamsterize(attrs).slice(*attr_names)
9
+ end
10
+
11
+ def update(attr_changes)
12
+ self.class.new @attrs.merge(Record.hamsterize(attr_changes))
13
+ end
14
+
15
+ def read(attr)
16
+ @attrs[attr]
17
+ end
18
+
19
+ def attr_names
20
+ self.class.attr_names
21
+ end
22
+
23
+ def self.hamsterize(hash)
24
+ case hash
25
+ when Hamster::Hash then hash
26
+ else Hamster.hash hash
27
+ end
28
+ end
29
+
30
+ module ClassMethods
31
+ def attr_names
32
+ @attr_names ||= []
33
+ end
34
+
35
+ def attr_names=(attrs)
36
+ @attr_names = attrs
37
+ end
38
+
39
+ def record_attr(*attrs)
40
+ attrs.each do |attr|
41
+ class_eval %{
42
+ def #{attr}
43
+ read :#{attr}
44
+ end
45
+ }
46
+ end
47
+ self.attr_names += attrs
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,143 @@
1
+ module Alchemist
2
+ module ServerHandler
3
+ def self.new(world_file)
4
+ puts "Loading history...."
5
+ history = Alchemist::Server.load_history world_file
6
+
7
+ Module.new do
8
+ include Methods
9
+
10
+ @world_file = world_file
11
+ @history = history
12
+ @connections = []
13
+
14
+ class <<self
15
+ attr_reader :world_file
16
+ attr_accessor :history
17
+ attr_accessor :connections
18
+ end
19
+
20
+ def self.included(mod)
21
+ mod.instance_variable_set :@state, self
22
+ def mod.state; @state; end
23
+ end
24
+
25
+ def world_file
26
+ self.class.state.world_file
27
+ end
28
+
29
+ def history
30
+ self.class.state.history
31
+ end
32
+
33
+ def history=(h)
34
+ self.class.state.history = h
35
+ end
36
+
37
+ def connections
38
+ self.class.state.connections
39
+ end
40
+ end
41
+ end
42
+
43
+ module Methods
44
+ include ::EventMachine::Protocols::LineProtocol
45
+
46
+ attr_reader :name
47
+
48
+ def post_init
49
+ connections << self
50
+ send_line "Welcome alchemical friend. What is your name?"
51
+ end
52
+
53
+ def unbind
54
+ connections.delete self
55
+ end
56
+
57
+ def receive_line(line)
58
+ t = Benchmark.realtime do
59
+ response = process_line line
60
+
61
+ if response
62
+ send_line response
63
+ end
64
+ end
65
+
66
+ puts "#{Time.now} #{line.split(' ').first} #{(t*1000).round}"
67
+ end
68
+
69
+ def process_line(line)
70
+ if name
71
+ command = "#{name} #{line.chomp}"
72
+ outcome = Alchemist::Server.run_append command,
73
+ world_file,
74
+ history
75
+
76
+ self.history = outcome.new_history if outcome.new_history
77
+
78
+ if command = outcome.nearby_avatar_command
79
+ run_command_nearby command
80
+ end
81
+
82
+ outcome.response
83
+ else
84
+ possible_name = line.strip.split(' ').first
85
+
86
+ if possible_name && possible_name.length > 0
87
+ @name = possible_name
88
+ "hello #{name}"
89
+ else
90
+ "Please tell me your name."
91
+ end
92
+ end
93
+ rescue => e
94
+ show_error e
95
+ "error #{e}"
96
+ end
97
+
98
+ def run_command_nearby(command)
99
+ new_nearby = history.world.nearby_avatar_names name
100
+ old_nearby = begin
101
+ history.prior_world.try :nearby_avatar_names, name
102
+ rescue
103
+ # bail out if user wasn't in prior world
104
+ []
105
+ end
106
+
107
+ nearby = new_nearby | (old_nearby || [])
108
+
109
+ connections.select do |c|
110
+ begin
111
+ if nearby.include? c.name
112
+ c.run_unrecorded_command command
113
+ end
114
+ rescue => e
115
+ show_error e
116
+ end
117
+ end
118
+ end
119
+
120
+ def show_error(e)
121
+ $stderr.puts "#{e.class}: #{e.message}"
122
+ $stderr.puts e.backtrace
123
+ end
124
+
125
+ def run_unrecorded_command(command)
126
+ outcome = command.run name, history
127
+
128
+ if r = outcome.response
129
+ send_line r
130
+ end
131
+ end
132
+
133
+ def send_line(data)
134
+ send_data data
135
+
136
+ if data[-1] != "\n"
137
+ send_data "\n"
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+