"hex_move_upleft": "w",
"hex_move_upright": "e",
"hex_move_right": "d",
- "hex_move_downright": "x",
- "hex_move_downleft": "y",
- "hex_move_left": "a",
+ "hex_move_downright": "c",
+ "hex_move_downleft": "x",
+ "hex_move_left": "s",
"square_move_up": "w",
"square_move_left": "a",
"square_move_down": "s",
+# TODO: instead of sending tasks and thing types on request, send them on connection
+
def cmd_TASKS(game, connection_id):
tasks = []
game.io.send('TASKS ' + ','.join(game.tasks.keys()), connection_id)
cmd_TASKS.argtypes = ''
+def cmd_THING_TYPES(game, connection_id):
+ for t_t in game.thing_types.values():
+ game.io.send('THING_TYPE %s %s' % (t_t.get_type(), t_t.symbol_hint),
+ connection_id)
+cmd_THING_TYPES.argtypes = ''
+
def cmd_ALL(game, msg, connection_id):
def lower_msg_by_volume(msg, volume):
import random
if not connection_id in game.sessions:
raise GameError('need to be logged in for this')
- speaker = game.get_thing(game.sessions[connection_id], False)
+ speaker = game.get_thing(game.sessions[connection_id])
n_max = 255
map_size = game.map.size_i
dijkstra_map = [n_max for i in range(game.map.size_i)]
# x = 0
# print(' '.join(line_to_print))
for c_id in game.sessions:
- listener = game.get_thing(game.sessions[c_id], create_unfound=False)
+ listener = game.get_thing(game.sessions[c_id])
listener_vol = dijkstra_map[game.map.get_position_index(listener.position)]
volume = 1 / max(1, listener_vol)
lowered_msg = lower_msg_by_volume(msg, volume)
cmd_ALL.argtypes = 'string'
def cmd_LOGIN(game, nick, connection_id):
- for t in [t for t in game.things if t.type_ == 'player' and t.nickname == nick]:
+ for t in [t for t in game.things if t.type_ == 'Player' and t.nickname == nick]:
raise GameError('name already in use')
if connection_id in game.sessions:
raise GameError('cannot log in twice')
- t = game.thing_types['player'](game)
+ t = game.thing_types['Player'](game)
t.position = YX(game.map.size.y // 2, game.map.size.x // 2)
game.things += [t] # TODO refactor into Thing.__init__?
game.sessions[connection_id] = t.id_
cmd_LOGIN.argtypes = 'string'
def cmd_NICK(game, nick, connection_id):
- for t in [t for t in game.things if t.type_ == 'player' and t.nickname == nick]:
+ for t in [t for t in game.things if t.type_ == 'Player' and t.nickname == nick]:
raise GameError('name already in use')
if not connection_id in game.sessions:
raise GameError('can only rename when already logged in')
t_id = game.sessions[connection_id]
- t = game.get_thing(t_id, False)
+ t = game.get_thing(t_id)
old_nick = t.nickname
t.nickname = nick
game.io.send('CHAT ' + quote(old_nick + ' renamed themselves to ' + nick))
# raise GameError('can only query when logged in')
# t = game.get_thing(game.sessions[connection_id], False)
# source_nick = t.nickname
-# for t in [t for t in game.things if t.type_ == 'player' and t.nickname == target_nick]:
+# for t in [t for t in game.things if t.type_ == 'Player' and t.nickname == target_nick]:
# for c_id in game.sessions:
# if game.sessions[c_id] == t.id_:
# game.io.send('CHAT ' + quote(source_nick+ '->' + target_nick + ': ' + msg), c_id)
cmd_TURN.argtypes = 'int:nonneg'
def cmd_ANNOTATE(game, yx, msg, pw, connection_id):
- player = game.get_thing(game.sessions[connection_id], False)
+ player = game.get_thing(game.sessions[connection_id])
if player.fov_stencil[yx] != '.':
raise GameError('cannot annotate tile outside field of view')
if not game.can_do_tile_with_pw(yx, pw):
cmd_ANNOTATE.argtypes = 'yx_tuple:nonneg string string'
def cmd_PORTAL(game, yx, msg, pw, connection_id):
- player = game.get_thing(game.sessions[connection_id], False)
+ player = game.get_thing(game.sessions[connection_id])
if player.fov_stencil[yx] != '.':
raise GameError('cannot edit portal on tile outside field of view')
if not game.can_do_tile_with_pw(yx, pw):
cmd_GOD_PORTAL.argtypes = 'yx_tuple:nonneg string'
def cmd_GET_ANNOTATION(game, yx, connection_id):
- player = game.get_thing(game.sessions[connection_id], False)
+ player = game.get_thing(game.sessions[connection_id])
annotation = '(unknown)';
if player.fov_stencil[yx] == '.':
annotation = '(none)';
def cmd_MAP_CONTROL_PW(game, tile_class, password):
game.map_control_passwords[tile_class] = password
cmd_MAP_CONTROL_PW.argtypes = 'char string'
+
+def cmd_THING(game, yx, thing_type, thing_id):
+ if not thing_type in game.thing_types:
+ raise GameError('illegal thing type %s' % thing_type)
+ if yx.y < 0 or yx.x < 0 or yx.y >= game.map.size.y or yx.x >= game.map.size.x:
+ raise GameError('illegal position %s' % yx)
+ t_old = None
+ if thing_id > 0:
+ t_old = game.get_thing(thing_id)
+ t_new = game.thing_types[thing_type](game, id_=thing_id, position=yx)
+ if t_old:
+ game.things[game.things.index(t_old)] = t_new
+ else:
+ game.things += [t_new]
+ game.changed = True
+cmd_THING.argtypes = 'yx_tuple:nonneg string:thing_type int:nonneg'
from plomrogue.errors import GameError, PlayError
from plomrogue.io import GameIO
from plomrogue.misc import quote
-from plomrogue.things import Thing, ThingPlayer
from plomrogue.mapping import YX, MapGeometrySquare, Map
self.map_geometry = MapGeometrySquare(YX(24, 40))
self.commands = {}
- def get_thing(self, id_, create_unfound):
- # No default for create_unfound because every call to get_thing
- # should be accompanied by serious consideration whether to use it.
+ def get_thing(self, id_):
for thing in self.things:
if id_ == thing.id_:
return thing
- if create_unfound:
- t = self.thing_type(self, id_)
- self.things += [t]
- return t
return None
+ def _register_object(self, obj, obj_type_desc, prefix):
+ if not obj.__name__.startswith(prefix):
+ raise GameError('illegal %s object name: %s' % (obj_type_desc, obj.__name__))
+ obj_name = obj.__name__[len(prefix):]
+ d = getattr(self, obj_type_desc + 's')
+ d[obj_name] = obj
+
def register_command(self, command):
- prefix = 'cmd_'
- if not command.__name__.startswith(prefix):
- raise GameError('illegal command object name: %s' % command.__name__)
- command_name = command.__name__[len(prefix):]
- self.commands[command_name] = command
+ self._register_object(command, 'command', 'cmd_')
self.changed = True
self.io = GameIO(self, save_file)
self.tasks = {}
- self.thing_type = Thing
- self.thing_types = {'player': ThingPlayer}
+ self.thing_types = {}
self.sessions = {}
self.map = Map(self.map_geometry.size)
self.map_control = Map(self.map_geometry.size)
if not os.path.isfile(self.io.save_file):
raise GameError('save file path refers to non-file')
+ def register_thing_type(self, thing_type):
+ self._register_object(thing_type, 'thing_type', 'Thing_')
+
def register_task(self, task):
- prefix = 'Task_'
- if not task.__name__.startswith(prefix):
- raise GameError('illegal task object name: %s' % task.__name__)
- task_name = task.__name__[len(prefix):]
- self.tasks[task_name] = task
+ self._register_object(task, 'task', 'Task_')
def read_savefile(self):
if os.path.exists(self.io.save_file):
string.digits + string.ascii_letters + string.punctuation + ' ']
elif string_option_type == 'map_geometry':
return ['Hex', 'Square']
+ elif string_option_type == 'thing_type':
+ return self.thing_types.keys()
return None
def get_map_geometry_shape(self):
self.io.send('TURN ' + str(self.turn))
for c_id in self.sessions:
- player = self.get_thing(self.sessions[c_id], create_unfound = False)
+ player = self.get_thing(self.sessions[c_id])
visible_terrain = player.fov_stencil_map(self.map)
self.io.send('FOV %s' % quote(player.fov_stencil.terrain), c_id)
self.io.send('MAP %s %s %s' % (self.get_map_geometry_shape(),
self.io.send('MAP_CONTROL %s' % quote(visible_control), c_id)
for t in [t for t in self.things
if player.fov_stencil[t.position] == '.']:
- self.io.send('THING_POS %s %s' % (t.id_, t.position), c_id)
+ self.io.send('THING %s %s %s' % (t.position, t.type_, t.id_), c_id)
if hasattr(t, 'nickname'):
self.io.send('THING_NAME %s %s' % (t.id_,
quote(t.nickname)), c_id)
connection_id_found = True
break
if not connection_id_found:
- t = self.get_thing(self.sessions[connection_id], create_unfound=False)
+ t = self.get_thing(self.sessions[connection_id])
if hasattr(t, 'nickname'):
self.io.send('CHAT ' + quote(t.nickname + ' left the map.'))
self.things.remove(t)
def cmd_TASK_colon(task_name, game, *args, connection_id):
if connection_id not in game.sessions:
raise GameError('Not registered as player.')
- t = game.get_thing(game.sessions[connection_id], create_unfound=False)
+ t = game.get_thing(game.sessions[connection_id])
t.set_next_task(task_name, args)
def task_prefixed(command_name, task_prefix, task_command):
def new_thing_id(self):
if len(self.things) == 0:
- return 0
- # DANGEROUS – if anywhere we append a thing to the list of lower
- # ID than the highest-value ID, this might lead to re-using an
- # already active ID. This condition /should/ not be fulfilled
- # anywhere in the code, but if it does, trouble here is one of
- # the more obvious indicators that it does – that's why there's
- # no safeguard here against this.
- return self.things[-1].id_ + 1
+ return 1
+ return max([t.id_ for t in self.things]) + 1
def save(self):
for tile_class in self.map_control_passwords:
write(f, 'MAP_CONTROL_PW %s %s' % (tile_class,
self.map_control_passwords[tile_class]))
+ for t in [t for t in self.things if not t.type_ == 'Player']:
+ write(f, 'THING %s %s %s' % (t.position, t.type_, t.id_))
def new_world(self, map_geometry):
self.map_geometry = map_geometry
class ThingBase:
type_ = '?'
- def __init__(self, game, id_=None, position=(YX(0,0))):
+ def __init__(self, game, id_=0, position=(YX(0,0))):
self.game = game
- if id_ is None:
+ if id_ == 0:
self.id_ = self.game.new_thing_id()
else:
self.id_ = id_
def proceed(self):
pass
+ @property
+ def type_(self):
+ return self.__class__.get_type()
+
+ @classmethod
+ def get_type(cls):
+ return cls.__name__[len('Thing_'):]
+
+
+
+class Thing_Stone(Thing):
+ symbol_hint = 'o'
class ThingAnimate(Thing):
-class ThingPlayer(ThingAnimate):
- type_ = 'player'
+class Thing_Player(ThingAnimate):
+ symbol_hint = '@'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
from plomrogue.game import Game
from plomrogue.io_websocket import PlomWebSocketServer
from plomrogue.io_tcp import PlomTCPServer
-from plomrogue.commands import (cmd_ALL, cmd_LOGIN, cmd_NICK, cmd_PING,
+from plomrogue.commands import (cmd_ALL, cmd_LOGIN, cmd_NICK, cmd_PING, cmd_THING,
cmd_MAP, cmd_TURN, cmd_MAP_LINE, cmd_GET_ANNOTATION,
cmd_ANNOTATE, cmd_PORTAL, cmd_GET_GAMESTATE,
cmd_TASKS, cmd_MAP_CONTROL_LINE, cmd_MAP_CONTROL_PW,
- cmd_GOD_ANNOTATE, cmd_GOD_PORTAL)
+ cmd_GOD_ANNOTATE, cmd_GOD_PORTAL, cmd_THING_TYPES)
from plomrogue.tasks import (Task_WAIT, Task_MOVE, Task_WRITE,
Task_FLATTEN_SURROUNDINGS)
+from plomrogue.things import Thing_Player, Thing_Stone
import sys
if len(sys.argv) != 2:
game.register_command(cmd_GOD_PORTAL)
game.register_command(cmd_GET_GAMESTATE)
game.register_command(cmd_TASKS)
+game.register_command(cmd_THING_TYPES)
+game.register_command(cmd_THING)
game.register_task(Task_WAIT)
game.register_task(Task_MOVE)
game.register_task(Task_WRITE)
game.register_task(Task_FLATTEN_SURROUNDINGS)
+game.register_thing_type(Thing_Player)
+game.register_thing_type(Thing_Stone)
game.read_savefile()
game.io.start_loop()
game.io.start_server(8000, PlomWebSocketServer)
game.player_id = player_id
cmd_PLAYER_ID.argtypes = 'int:nonneg'
-def cmd_THING_POS(game, thing_id, position):
- t = game.get_thing(thing_id, True)
- t.position = position
-cmd_THING_POS.argtypes = 'int:nonneg yx_tuple:nonneg'
+def cmd_THING(game, yx, thing_type, thing_id):
+ t = game.get_thing(thing_id)
+ if not t:
+ t = ThingBase(game, thing_id)
+ game.things += [t]
+ t.position = yx
+ t.type_ = thing_type
+cmd_THING.argtypes = 'yx_tuple:nonneg string:thing_type int:nonneg'
def cmd_THING_NAME(game, thing_id, name):
- t = game.get_thing(thing_id, True)
- t.name = name
+ t = game.get_thing(thing_id)
+ if t:
+ t.name = name
cmd_THING_NAME.argtypes = 'int:nonneg string'
def cmd_MAP(game, geometry, size, content):
game.tui.switch_mode('play')
if game.tui.mode.shows_info:
game.tui.query_info()
- player = game.get_thing(game.player_id, False)
+ player = game.get_thing(game.player_id)
if player.position in game.portals:
game.tui.teleport_target_host = game.portals[player.position]
game.tui.switch_mode('teleport')
game.tasks = tasks_comma_separated.split(',')
cmd_TASKS.argtypes = 'string'
+def cmd_THING_TYPE(game, thing_type, symbol_hint):
+ game.thing_types[thing_type] = symbol_hint
+cmd_THING_TYPE.argtypes = 'string char'
+
def cmd_PONG(game):
pass
cmd_PONG.argtypes = ''
class Game(GameBase):
- thing_type = ThingBase
turn_complete = False
tasks = {}
+ thing_types = {}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.register_command(cmd_CHAT)
self.register_command(cmd_PLAYER_ID)
self.register_command(cmd_TURN)
- self.register_command(cmd_THING_POS)
+ self.register_command(cmd_THING)
+ self.register_command(cmd_THING_TYPE)
self.register_command(cmd_THING_NAME)
self.register_command(cmd_MAP)
self.register_command(cmd_MAP_CONTROL)
def get_string_options(self, string_option_type):
if string_option_type == 'map_geometry':
return ['Hex', 'Square']
+ elif string_option_type == 'thing_type':
+ return self.thing_types.keys()
return None
def get_command(self, command_name):
self.socket_thread = threading.Thread(target=self.socket.run)
self.socket_thread.start()
self.disconnected = False
+ self.game.thing_types = {}
self.socket.send('TASKS')
+ self.socket.send('THING_TYPES')
self.switch_mode('login')
except ConnectionRefusedError:
self.log_msg('@ server connect failure')
self.map_mode = 'terrain'
self.mode = getattr(self, 'mode_' + mode_name)
if self.mode.shows_info:
- player = self.game.get_thing(self.game.player_id, False)
+ player = self.game.get_thing(self.game.player_id)
self.explorer = YX(player.position.y, player.position.x)
if self.mode.name == 'waiting_for_server':
self.log_msg('@ waiting for server …')
info = 'TERRAIN: %s\n' % self.game.map_content[pos_i]
for t in self.game.things:
if t.position == self.explorer:
- info += 'PLAYER @: %s\n' % t.name
+ info += 'THING: %s' % t.type_
+ if hasattr(t, 'name'):
+ info += ' (name: %s)' % t.name
+ info += '\n'
if self.explorer in self.game.portals:
info += 'PORTAL: ' + self.game.portals[self.explorer] + '\n'
else:
map_lines_split += [list(map_content[start:end])]
if self.map_mode == 'terrain':
for t in self.game.things:
- map_lines_split[t.position.y][t.position.x] = '@'
+ symbol = self.game.thing_types[t.type_]
+ map_lines_split[t.position.y][t.position.x] = symbol
if self.mode.shows_info:
map_lines_split[self.explorer.y][self.explorer.x] = '?'
map_lines = []
map_lines += [' '.join(line)]
window_center = YX(int(self.size.y / 2),
int(self.window_width / 2))
- player = self.game.get_thing(self.game.player_id, False)
+ player = self.game.get_thing(self.game.player_id)
center = player.position
if self.mode.shows_info:
center = self.explorer
this.websocket = new WebSocket(this.url);
this.websocket.onopen = function(event) {
server.connected = true;
+ game.thing_types = {};
server.send(['TASKS']);
+ server.send(['THING_TYPES']);
tui.log_msg("@ server connected! :)");
tui.switch_mode(mode_login);
};
game.things = {};
game.portals = {};
game.turn = parseInt(tokens[1]);
- } else if (tokens[0] === 'THING_POS') {
- game.get_thing(tokens[1], true).position = parser.parse_yx(tokens[2]);
+ } else if (tokens[0] === 'THING') {
+ let t = game.get_thing(tokens[3], true);
+ t.position = parser.parse_yx(tokens[1]);
+ t.type_ = tokens[2];
} else if (tokens[0] === 'THING_NAME') {
- game.get_thing(tokens[1], true).name_ = tokens[2];
+ let t = game.get_thing(tokens[1], false);
+ if (t) {
+ t.name_ = tokens[2];
+ };
} else if (tokens[0] === 'TASKS') {
game.tasks = tokens[1].split(',')
+ } else if (tokens[0] === 'THING_TYPE') {
+ game.thing_types[tokens[1]] = tokens[2]
} else if (tokens[0] === 'MAP') {
game.map_geometry = tokens[1];
tui.init_keys();
if (this.map_mode == 'terrain') {
for (const thing_id in game.things) {
let t = game.things[thing_id];
- map_lines_split[t.position[0]][t.position[1]] = '@';
+ let symbol = game.thing_types[t.type_];
+ map_lines_split[t.position[0]][t.position[1]] = symbol;
};
}
if (tui.mode.shows_info) {
for (let t_id in game.things) {
let t = game.things[t_id];
if (t.position[0] == this.position[0] && t.position[1] == this.position[1]) {
- info += "PLAYER @";
+ info += "THING: " + t.type_;
if (t.name_) {
- info += ": " + t.name_;
+ info += " (name: " + t.name_ + ")";
}
info += "\n";
}