alchemist-server 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|