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
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
+