alchemist-server 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ tmp/*
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in alchemist-server.gemspec
4
+ gemspec
5
+
data/README.md ADDED
@@ -0,0 +1,118 @@
1
+ alchemist-server
2
+ ================
3
+
4
+ welcome!
5
+ --------
6
+
7
+ `alchemist` is a sandbox that uses unicode characters to represent objects in a persistent world that multiple users may interact with.
8
+
9
+ `alchemist` has few commands for interacting with the server. Here they are with some helpful tips for using them.
10
+
11
+ other than these commands below, all that needs doing is opening a TCP socket to the server with a unicode-supported client.
12
+
13
+ you can start a local server with `./bin/alchemist-server`
14
+
15
+ after you connect, you will be greeted with…
16
+
17
+ `Welcome, alchemical friend. What is your name?`
18
+
19
+ …after which you can enter your name and continue with commands below. (Parenthesized characters are optional.)
20
+
21
+ commands!
22
+ =========
23
+
24
+ app(ear)
25
+ --------
26
+ adds your avatar into the world! (this is important if you want to interact with people and other things.)
27
+
28
+ it also returns the any local avatars nearby with the following format:
29
+
30
+ avatars N
31
+ AVATARNAME X Y
32
+ AVATARNAME2 X Y
33
+ appeared
34
+
35
+
36
+ example:
37
+
38
+ > appear
39
+ avatars 2
40
+ qxjit -8 6
41
+ moob 0 0
42
+ appeared
43
+
44
+ basics
45
+ ------
46
+ returns a string that identifies the basic elements that exist in alchemist
47
+
48
+ example:
49
+
50
+ > basics
51
+ basics 火░〃〰
52
+
53
+ basics include:
54
+
55
+ name unicode
56
+ 火 fire \u706b
57
+ 〰 water \u3030
58
+ ░ earth \u2591
59
+ 〃 wind \u3003
60
+
61
+
62
+ compounds
63
+ ---------
64
+ returns a string that identifies the compound elements that (currently) exist in alchemist
65
+
66
+ example:
67
+
68
+ > compounds
69
+ compounds ʬㄒㄘ〝
70
+
71
+ create ABX
72
+ ----------
73
+ allows you to create new compounds in alchemist. they are created by providing two basic characters and a currently unused unicode character that will represent the compound in alchemist
74
+
75
+ example:
76
+
77
+ > create 火░ㄒ
78
+
79
+ !!! incomplete !!!
80
+
81
+ read
82
+ ----
83
+ returns the messages of all nearby avatars. this command is automatically called after a successful `message` call
84
+
85
+ > read
86
+ messages 10
87
+ Alchemistasaur:
88
+
89
+ Ryan:
90
+
91
+ moob:
92
+ "pants"
93
+
94
+
95
+
96
+ qxjit:
97
+
98
+ message|msg N MSG
99
+ -----------------
100
+
101
+ allows you to add messages to your avatar where N is 0-9 and MSG is a string to attach to that position
102
+
103
+ the server will respond as if you had called `read` immediately after
104
+
105
+ example:
106
+
107
+ > message 0 "hi"
108
+ messages 10
109
+ Alchemistasaur:
110
+
111
+ Ryan:
112
+
113
+ moob:
114
+ "pants"
115
+
116
+
117
+
118
+ qxjit:
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "alchemist-server/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "alchemist-server"
7
+ s.version = Alchemist::Server::VERSION
8
+ s.authors = ["David Vollbracht"]
9
+ s.email = ["david@flipstone.com"]
10
+ s.homepage = "https://github.com/flipstone/alchemist-server"
11
+ s.summary = %q{Server process for the Alchemist game}
12
+ s.description = %q{}
13
+
14
+ s.rubyforge_project = "alchemist-server"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ s.add_development_dependency "ffi-ncurses", ">= 0.4.0"
24
+
25
+ s.add_runtime_dependency "rb-readline"
26
+ s.add_runtime_dependency "hamster"
27
+ s.add_runtime_dependency "eventmachine", ">= 1.0"
28
+ s.add_runtime_dependency "alchemist-core", ">= 0.0.1"
29
+ end
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ require 'readline'
4
+
5
+ world_file = ARGV.first || "tmp/dev_world.alc"
6
+
7
+ puts "Using world file: #{world_file}"
8
+
9
+ loop do
10
+ line = Readline::readline('> ')
11
+ break if line.nil? || line == 'quit'
12
+ Readline::HISTORY.push(line)
13
+
14
+ rd, wr = IO.pipe
15
+
16
+ if fork
17
+ wr.close
18
+ result = rd.read
19
+ rd.close
20
+ Process.wait
21
+
22
+ puts result
23
+ else
24
+ rd.close
25
+ require 'alchemist-server'
26
+
27
+ result = Alchemist::Server.one_shot world_file, line
28
+
29
+ wr.write result.to_s.pad_to_unicode_monospace
30
+ wr.flush
31
+ wr.close
32
+ exit! 0
33
+ end
34
+ end
35
+
36
+ puts
37
+
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ require 'alchemist-server'
4
+
5
+ world_file = ARGV.first || "tmp/dev_world.alc"
6
+
7
+ EventMachine.run do
8
+ Alchemist::Server.run world_file
9
+ end
10
+
@@ -0,0 +1,151 @@
1
+ require "time"
2
+ require "hamster"
3
+ require "socket"
4
+ require "eventmachine"
5
+ require "benchmark"
6
+ require "em/protocols/line_protocol"
7
+
8
+ require "alchemist-core"
9
+ require "alchemist-server/version"
10
+ require "alchemist-server/record"
11
+
12
+ require "alchemist-server/avatar"
13
+ require "alchemist-server/direction"
14
+ require "alchemist-server/element"
15
+ require "alchemist-server/event"
16
+ require "alchemist-server/formula"
17
+ require "alchemist-server/geography"
18
+ require "alchemist-server/outcome"
19
+ require "alchemist-server/server_handler"
20
+ require "alchemist-server/world"
21
+ require "alchemist-server/world_history"
22
+
23
+ require "alchemist-server/commands/base"
24
+
25
+ require "alchemist-server/commands/appear"
26
+ require "alchemist-server/commands/basics"
27
+ require "alchemist-server/commands/compounds"
28
+ require "alchemist-server/commands/create"
29
+ require "alchemist-server/commands/describe"
30
+ require "alchemist-server/commands/directions"
31
+ require "alchemist-server/commands/element"
32
+ require "alchemist-server/commands/forge"
33
+ require "alchemist-server/commands/formulate"
34
+ require "alchemist-server/commands/inventory"
35
+ require "alchemist-server/commands/location"
36
+ require "alchemist-server/commands/lock"
37
+ require "alchemist-server/commands/look"
38
+ require "alchemist-server/commands/message"
39
+ require "alchemist-server/commands/put"
40
+ require "alchemist-server/commands/read"
41
+ require "alchemist-server/commands/take"
42
+ require "alchemist-server/commands/who"
43
+
44
+ class Object
45
+ def try(sym, *args)
46
+ send sym, *args
47
+ end
48
+ end
49
+
50
+ class NilClass
51
+ def try(*args)
52
+
53
+ end
54
+ end
55
+
56
+ module Alchemist
57
+ module Server
58
+ SERVER_PORT = 79 * 100
59
+
60
+ def self.run(world_file)
61
+ ::EventMachine.start_server "", SERVER_PORT, ServerHandler.new(world_file)
62
+ puts "Listening on #{SERVER_PORT}"
63
+ end
64
+
65
+ def self.one_shot(world_file, command_string_ascii)
66
+ command_string = command_string_ascii.force_encoding Encoding::UTF_8
67
+ avatar_name, command, *args = command_string.split /\s+/
68
+
69
+ if command_mod = COMMANDS.detect { |c| match_command? command, c }
70
+ run_command_module command_string, world_file
71
+ else
72
+ run_special_command world_file, command, *args
73
+ end
74
+ end
75
+
76
+ def self.run_command_module(command_string, world_file)
77
+ history = load_history world_file
78
+ outcome = run_append command_string, world_file, history
79
+ outcome.try :response
80
+ end
81
+
82
+ def self.run_append(command_string_ascii, world_file, history)
83
+ command_string = command_string_ascii.force_encoding Encoding::UTF_8
84
+ event = Event.new command_string, Time.now
85
+ outcome = event.happen history
86
+
87
+ if outcome.try :new_world
88
+ new_history = WorldHistory.new event, history
89
+
90
+ File.open(world_file,'a') do |f|
91
+ f.write event.to_s
92
+ f.write "\n"
93
+ end
94
+ end
95
+
96
+ outcome.update new_history: new_history
97
+ end
98
+
99
+ def self.run_special_command(world_file, command_string, *args)
100
+ case command_string
101
+ when /^show_raw_file$/
102
+ File.read(world_file)
103
+ when /^show_history$/
104
+ load_history(world_file).to_s
105
+ when /^dim(ensions)?$/
106
+ load_history(world_file).world.dimensions(*args).to_s
107
+ when /^geo(graphy)?$/
108
+ load_history(world_file).world.geography.to_s
109
+ else
110
+ "Unknown Command: #{command_string}"
111
+ end
112
+ end
113
+
114
+ def self.load_history(file_name)
115
+ if File.exist? file_name
116
+ WorldHistory.parse File.read(file_name)
117
+ else
118
+ WorldHistory.genesis
119
+ end
120
+ end
121
+
122
+ def self.match_command?(command_name, command)
123
+ command_name =~ /^#{command.pattern}/
124
+ end
125
+ end
126
+
127
+ COMMANDS = [
128
+ Commands::Appear,
129
+ Commands::Basics,
130
+ Commands::Compounds,
131
+ Commands::Create,
132
+ Commands::Describe,
133
+ Commands::North,
134
+ Commands::South,
135
+ Commands::East,
136
+ Commands::West,
137
+ Commands::Element,
138
+ Commands::Inventory,
139
+ Commands::Forge,
140
+ Commands::Formulate,
141
+ Commands::Location,
142
+ Commands::Lock,
143
+ Commands::Look,
144
+ Commands::Message,
145
+ Commands::Put,
146
+ Commands::Read,
147
+ Commands::Take,
148
+ Commands::Who,
149
+ ]
150
+ end
151
+
@@ -0,0 +1,82 @@
1
+ # encoding: utf-8
2
+ module Alchemist
3
+ class Avatar
4
+ INVENTORY_SIZE = 64
5
+
6
+ include Record
7
+ record_attr :name, :x, :y, :inventory, :messages
8
+
9
+ def move(direction)
10
+ new_x, new_y = direction.move_from(x, y)
11
+ update x: new_x, y: new_y
12
+ end
13
+
14
+ def location
15
+ Geography::Loc.new x, y
16
+ end
17
+
18
+ def near?(location, range)
19
+ location.distance(self.location) <= range
20
+ end
21
+
22
+ def add_to_inventory(additions)
23
+ a = update inventory: (inventory + additions).gsub(' ','')
24
+
25
+ if a.inventory.length > INVENTORY_SIZE
26
+ raise "You can only carry #{INVENTORY_SIZE} items"
27
+ end
28
+
29
+ a
30
+ end
31
+
32
+ def remove_from_inventory(*removals)
33
+ remover = removals.join('').split('').reduce(-> x { x }) do |r, c|
34
+ -> x { r[x].sub(c,'') }
35
+ end
36
+
37
+ update inventory: remover[inventory]
38
+ end
39
+
40
+ def put_message(key, message)
41
+ if key.to_s =~ /^[0-9]$/
42
+ update messages: messages.put(key.to_s.to_i, message)
43
+ else
44
+ raise "Only 0 through 9 may be used as message keys"
45
+ end
46
+ end
47
+
48
+ def message_list
49
+ messages.keys.sort.map { |k| messages[k] }
50
+ end
51
+
52
+ def has?(*items)
53
+ items.all? do |i|
54
+ inventory.include? i
55
+ end
56
+ end
57
+
58
+ def hash
59
+ name.hash
60
+ end
61
+
62
+ def ==(other)
63
+ other.class == self.class &&
64
+ other.name == name
65
+ end
66
+
67
+ def eql?(other)
68
+ self == other
69
+ end
70
+
71
+ def to_s
72
+ "⚲ #{name} #{x} #{y} #{inventory}"
73
+ end
74
+
75
+ def self.parse(string)
76
+ if string =~ /^⚲ (.*)$/
77
+ new *$1.split(" ")
78
+ end
79
+ end
80
+ end
81
+ end
82
+