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.
- data/.gitignore +5 -0
- data/Gemfile +5 -0
- data/README.md +118 -0
- data/Rakefile +1 -0
- data/alchemist-server.gemspec +29 -0
- data/bin/alchemist-repl +37 -0
- data/bin/alchemist-server +10 -0
- data/lib/alchemist-server.rb +151 -0
- data/lib/alchemist-server/avatar.rb +82 -0
- data/lib/alchemist-server/commands/appear.rb +13 -0
- data/lib/alchemist-server/commands/base.rb +31 -0
- data/lib/alchemist-server/commands/basics.rb +12 -0
- data/lib/alchemist-server/commands/compounds.rb +13 -0
- data/lib/alchemist-server/commands/create.rb +15 -0
- data/lib/alchemist-server/commands/describe.rb +18 -0
- data/lib/alchemist-server/commands/directions.rb +48 -0
- data/lib/alchemist-server/commands/element.rb +16 -0
- data/lib/alchemist-server/commands/forge.rb +18 -0
- data/lib/alchemist-server/commands/formulate.rb +20 -0
- data/lib/alchemist-server/commands/inventory.rb +12 -0
- data/lib/alchemist-server/commands/location.rb +13 -0
- data/lib/alchemist-server/commands/lock.rb +12 -0
- data/lib/alchemist-server/commands/look.rb +14 -0
- data/lib/alchemist-server/commands/message.rb +15 -0
- data/lib/alchemist-server/commands/put.rb +16 -0
- data/lib/alchemist-server/commands/read.rb +18 -0
- data/lib/alchemist-server/commands/take.rb +17 -0
- data/lib/alchemist-server/commands/who.rb +18 -0
- data/lib/alchemist-server/curses/geography_window.rb +63 -0
- data/lib/alchemist-server/curses/glyph_window.rb +89 -0
- data/lib/alchemist-server/curses/item_window.rb +59 -0
- data/lib/alchemist-server/curses/messages_window.rb +43 -0
- data/lib/alchemist-server/curses/prompt_window.rb +35 -0
- data/lib/alchemist-server/direction.rb +28 -0
- data/lib/alchemist-server/element.rb +14 -0
- data/lib/alchemist-server/event.rb +43 -0
- data/lib/alchemist-server/formula.rb +24 -0
- data/lib/alchemist-server/geography.rb +111 -0
- data/lib/alchemist-server/outcome.rb +12 -0
- data/lib/alchemist-server/record.rb +51 -0
- data/lib/alchemist-server/server_handler.rb +143 -0
- data/lib/alchemist-server/version.rb +8 -0
- data/lib/alchemist-server/world.rb +206 -0
- data/lib/alchemist-server/world_history.rb +45 -0
- data/script/alchemist-curses +307 -0
- metadata +148 -0
data/Gemfile
ADDED
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
|
data/bin/alchemist-repl
ADDED
@@ -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,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
|
+
|