Commit 13cb4c85 authored by Bruno Kleinert's avatar Bruno Kleinert

Imported Upstream version 0.2.1

This diff is collapsed.
London Law ChangeLog
2005-07-05 Release 0.2.1.
Fixed a DNS resolution bug related to Twisted 2.0 upgrade.
2005-02-17 Release 0.2.0.
2005-02-15 Announce 0.2.0rc1.
Tooltip username labels for the map window are now more
visible on OS X.
Most wxStaticTextBox instances have been replaced with
simple labels.
2005-02-14 Server will permit clients to disconnect during gameplay
and then rejoin the game. Server saves game data to a
database, so it can be shut down and restarted without
loss of game state.
Clients receive pop-up notification when a player leaves,
rejoins, etc.
Clients can send chat messages "to all" or "to team".
2005-02-12 Completed rewrite of all networking code, using the
event-driven Twisted framework. Protocol is now line-based.
Server supports multiple simultaneous games. Thanks to
Conor Davis <conor at fastmail dot fm> for significant
contributions to server code.
Client game registration windows now use the more
suitable wxListCtrl.
2004-11-13 Release 0.1.0.
# arch-tag: DO_NOT_CHANGE_a0b9dc28-d83d-4271-a57a-a0e7e8df77f3
include ChangeLog README COPYING
recursive-include londonlaw/guiclient/images *.jpg *.png
recursive-include doc *
# arch-tag: DO_NOT_CHANGE_a7e5fd31-5694-442f-974f-89109844b602
Metadata-Version: 1.0
Name: londonlaw
Version: 0.2.1
Summary: networked multiplayer manhunting board game
Author: Paul J. Pelzl
License: GNU General Public License, Version 2
Description: London Law is a networked multiplayer adaptation of the classic
Scotland Yard board game. Mr. X must evade a number of detectives by
carefully concealing his movements across London. One of only a
handful of asymmetric board games (Mr. X and the detectives have
different goals and abilities).
Keywords: Scotland Yard board game multiplayer
Platform: *nix/X11
Platform: OS X
Platform: Win32
London Law README
Check the 'doc' directory for installation and usage instructions.
# arch-tag: DO_NOT_CHANGE_fa21d784-7566-4c16-8db3-a64edcd7ad26
Paul Pelzl <> Tue Sep 14 02:06:48 2004 13505.0
# London Law documentation makefile
all: manual.pdf manual.html post-build-cleanup manual.dvi
dvips -tletterSize -Ppdf -G0 manual.dvi
manual.dvi: manual.tex
latex manual.tex
latex manual.tex
manual.html: manual.tex
hevea manual.tex
post-build-cleanup: manual.pdf manual.html
rm -f *.aux *.log *.dvi *.ps
rm -f *.aux *.log *.dvi *.ps *.pdf *.html
# arch-tag: DO_NOT_CHANGE_7fb16864-342a-429b-83ed-6a12821406fe
London Law To-Do
* draw a more sensible graphic when a detective lands on Mr. X's
* fix layout glitch with choose boxes that have undergrounds
(looks like a wxWidgets sizer bug?)
* would be nice to have a "recent game history" display, maybe some text
blitted up in the corner of the map ("Red Detective moved to 171 with taxi",
* use some sort of animation to draw attention to player movement
(maybe just draw temporary lines on the map, Indiana-Jones style)
* come up with some way to draw alpha-blended shadows or highlights
around the player location markers (sure would be nice if wxWidgets
got true alpha support) to make them more visible
* splash screen (maybe)
* would be nice to have the capability to step through all turns as a
spectator after the game is over
* draw chat window text using the appropriate detective colors
* AI opponents (human player sends a "request AI" command to the
server, and server launches AI opponent as a separate process that
automatically joins the server and registers as Mr. X or a detective)
# arch-tag: DO_NOT_CHANGE_b30d9a28-9eb8-44e7-b4c4-6934ca205154
% London Law documentation
% End preamble
\title{London Law v0.2 User Manual}
\author{Paul J. Pelzl}
\date{February 15, 2005}
London Law is a networked multiplayer adaptation of the classic Scotland Yard
board game, published by Ravensburger in 1983. One player takes on the
role of Mr. X, who must evade Scotland Yard by carefully concealing his
path through London. Another one to five players control five detectives,
who must work together to locate Mr. X using the limited clues he leaves
behind. This game has the unusual quality of asymmetry--the two ``teams''
have different goals and different abilities.
London Law is written in Python, and makes use of the wxPython GUI library.
As a result, it should be quite cross-platform. It will certainly run on
GNU/Linux (my development platform), and ought to run on *BSD, Mac OS X,
Windows, and others.
Before installing London Law, you should have installed
%HEVEA \begin{rawhtml} <a href="">Python</a> \end{rawhtml}
2.3 or greater and
%HEVEA \begin{rawhtml} <a href="">wxPython</a> \end{rawhtml}
2.4.x or 2.5.x. (Mac users are advised to use wxPython 2.5.x, as its OS X support
is more mature.) In addition, you will need the
%HEVEA \begin{rawhtml} <a href="">Twisted</a> \end{rawhtml}
networking library. Most popular GNU/Linux and BSD distributions provide packages
for all of these components.
If you are using Windows, simply extract the London Law archive to a useful
location and disregard the remainder of this section. If you are using a
Unix-like operating system, read on for installation instructions. Note that
installation is optional; London Law may be executed straight out of the source tree from
the {\tt londonlaw} subdirectory.
I will assume you have received this program in the form of a source tarball,
e.g. ``londonlaw-x.x.x.tar.gz''. You have undoubtedly extracted this archive
already (e.g. using ``{\tt tar xvzf londonlaw-x.x.x.tar.gz}''). Enter the root of
the londonlaw installation directory, e.g. ``{\tt cd londonlaw-x.x.x}''; there should
be a file called ``'' in this directory. Become root before
trying to perform the installation. There are a couple of ways to install London Law:
\item {\bf Direct installation from source.} You can copy all the necessary
files to logical locations with the command ``{\tt python install}''.
If you wish to choose a different installation prefix, you can use ``{\tt
python install --prefix=PREFIX}'', where {\tt PREFIX} is your desired
installation root, for example ``/usr/local''. (If you choose this option,
your Python search path must include the corresponding directories.)
\item {\bf Installation via rpm.} If you use an rpm-based distribution you
can build an rpm package for London Law, which gives you the ability to remove the
program easily (not that you would ever want to do that). Create the rpm with
the command ``{\tt python bdist\_rpm}''. The package should be created
within the ``dist'' subdirectory, and you can install it using \\
``{\tt rpm -Uvh londonlaw-x.x.x-x.noarch.rpm}''.
If you have a Debian-based system, then you could use {\tt alien} to create a .deb
from this rpm.
\section*{Playing the game}
\subsection*{Setting Up}
London Law requires a game server, so start by executing ``{\tt london-server}'' on
a networked host; this command accepts an optional ``{\tt -pPORT}'' flag, which
causes the server to listen for connections on the specified port. Now you and your
gaming buddies can start your graphical clients by executing ``{\tt london-client}''.
Note: if you are using Windows, use the {\tt} and {\tt} files, which should be associated with Python. These are
located in the {\tt londonlaw} subdirectory. If you are using OS X, you must
launch the client with the special command ``{\tt pythonw london-client}'' in order
to permit the client to create graphics.
Upon launching the client, you will be presented with a connection window. Enter
the hostname or IP address of the machine hosting the game server, provide a username
and a password of your choice (do not forget the password!), and click ``Connect''.
Next you will be presented with a list of games. You can select a game and
join it, or click ``New Game'' to create your own. Once inside the game room,
click ``Choose Team'' if you want to select whether you will play as Mr. X or as the
detectives. When you are satisfied with your choice, click ``Vote to Start''.
When (1) there are players on both teams and (2) all players have voted to
start, the game will begin.
If you disconnect from the server during gameplay, whether intentionally or not, you
may reconnect and rejoin the game at any time (provided you are using the same
username and password). The server may also be shut down and restarted without loss of
game data.
There are 199 locations marked on the London map. These locations are connected by a
transportation network composed primarily of taxi, bus and underground lines (colored
yellow, green, and maroon, respectively). There are also a few river routes,
rendered as dashed black lines. All detectives begin with 10 taxi tickets, 8
bus tickets, and 4 underground tickets. Mr. X has a transportation advantage:
in addition to having unlimited taxi, bus, and underground tickets, he also has
2 pink ``double move'' tickets and 5 black tickets. The black tickets can be
used for any form of transportation, including the river routes; they are
useful for disguising Mr. X's movements. The current ticket counts are
displayed at the side of each player icon.
Mr. X and the detectives take turns moving throughout the map, spending tickets
as they go. Mr. X's location remains hidden for most of the game, and is
revealed only at turns 3, 8, 13, 18 and 24 (labeled with question marks in the
history window). The detectives are allowed to see what type of ticket Mr. X
uses at every turn. Mr. X's tickets and surfacing locations are displayed in
the history window at the left of the client screen. Mr. X's goal is to evade
the detectives until the end of the game; the detectives, of course, are trying
to track him down.
The game ends when one of the following occurs:
\item Mr. X evades the detectives for 24 turns. (Mr. X wins.)
\item One of the detectives lands on Mr. X's location, or vice versa. (Detectives win.)
\item All the detectives are stuck--they have run out of useful tickets, or are
unable to move without landing on another detective. (Mr. X wins.)
\item You can make a move either by clicking the ``Move'' button, or by
double-clicking on a map location. If you double-click on your desired
destination, this information will be automatically entered into the Move
\item You can drag the map around using the middle or right mouse buttons.
\item Double-clicking on a player's icon will center the map on that player's
\item The View menu may be helpful for getting a wider view of the map; there are
options (and hotkeys) for viewing the map in fullscreen, zooming in and
out, and hiding the history window.
London Law has been made available under the GNU General Public License (GPL),
version 2. You should have received a copy of the GPL along with this
program, in the file ``COPYING''.
I would like to express my thanks to:
\item Conor Davis, for significant contributions to the recent server rewrite
\item Robin Dunn, for his excellent wxPython bindings (which really make wxWidgets
programming a piece of cake)
\item The Twisted Matrix hackers, for a very well designed (if somewhat massive)
networking library
\item David Schaller, for reintroducing me to Scotland Yard
\section*{Contact info}
London Law author: Paul Pelzl {\tt <>} \\
London Law website: {\tt\~{}pelzlpj/londonlaw} \\
Feel free to contact me if you have bugs, feature requests\footnote{First have a look at the
included ``TODO'' list; I've got quite a few things in mind for future releases.}, patches, AI
opponents, etc. I would also welcome volunteers
interested in packaging London Law for various platforms.
London Law is developed with the aid of the excellent
Arch RCS\footnote{}.
%HEVEA \begin{rawhtml} <a href="">Arch RCS</a>. \end{rawhtml}
Interested developers are advised to track London Law development via my public repository: \\
\hspace*{2cm}{\tt $\backslash$ \\
\hspace*{3cm}\~{}pelzlpj/tla/2005} \\
London Law code may be found in branch {\tt londonlaw--main} .
Do you feel compelled to compensate me for writing London Law? As a {\em poor,
starving} graduate student, I will gratefully accept donations. Please see \\
{\tt\~{}pelzlpj/londonlaw/donate.html} for more information.
% arch-tag: DO_NOT_CHANGE_cfd1f7bd-124e-447a-a21f-89ca519c7235
The protocol will be line-oriented; all data will be in the form of strings,
terminated with CRLF. The LineOnlyReceiver protocol provided by Twisted is
ideal for this task.
All commands from client to server MAY be tagged with a unique identifier in
the form of a numeric string prefixed by an octothorpe, e.g. "#00001". The
line transmitted to the server then takes the form "#00001 <command>
<arguments>". If the server responds to this command, its response will be
tagged with the same identifier. If no tag is provided, the server will prefix
the response with "-". Command arguments may contain whitespace if they are
quoted (single or double quotes).
If the server provides a status message to the client, which is not a response
to some command, it should be prefixed with "*".
Server responses always take one of four forms:
S: "<tag> ok" , for an acknowledgment
S: "<tag> no <reason>" , for reply to a command which appears valid but is denied
S: "<tag> bad <reason>" , for reply to an illegal/ill-formatted command
S: "<tag> <data>" , for reply to a request for data
Server status messages take the form:
S: "* <message type> <data>"
Upon connecting to server, client transmits game protocol version and waits for
C: "#001 protocol 2.0"
S: "#001 ok"
Upon acceptance of protocol, the client must log in:
C: "#001 login \"my name\" \"my password\""
S: "#001 ok"
(A "no" reply would follow if the username is in use, or if the password is incorrect.)
Once the client is accepted, it may optionally request a list of all open games.
The server responds with an untagged message of the form
some string, GAME_STATUS is one of {"new", "in progress", "complete"},
and GAME_TYPE is "standard" (but will assume other values in future versions).
C: "#002 listgames"
S: "* gameinfo "my game" new standard 2
"* gameinfo "your game" "in progress" standard 2
"#002 ok"
The client may optionally choose to join a 'new' or 'in progress' game, which
it selects by name:
C: "#003 join \"my game"
S: "#003 ok"
The client may only join an 'in progress' game if it had been joined to that game
previously--otherwise the server will deny the client entry with a "no" response.
When joined to a 'new' or 'completed' game, the client may choose to leave the
C: "#004 leave"
S: "#004 ok"
When joined to a game, the client may request player information. The server will
provide the player information as untagged data in the following format:
"* playerinfo USERNAME TEAMNAME VOTESTART PAWNS", where TEAMNAME is one of {"Mr. X",
"Detectives"}, VOTESTART is one of {True, False} and PAWNS is a list of pawn names
such as "Red Yellow Green". The server will provide playerinfo updates as necessary
to keep the client synchronized.
C: "#005 listplayers"
S: "#005 ok"
S: "* playerinfo Joe \"Mr. X\" True X"
S: "* playerinfo Dan \"Detectives\" False \"Red Yellow Green Blue Black\""
When joined to a new game, the client may choose to select a team:
C: "#006 setteam \"Detectives\", or
"#006 setteam \"Mr. X\"
S: "#006 ok"
When joined to a game, the client may vote to begin the game:
C: "#007 votestart True"
S: "#007 ok"
When joined to a game, the client may send a chat message:
C: "#007 chatall \"my message\""
S: "#007 ok"
The server will broadcast chat messages to all clients:
S: "* chatall \"username\" \"message\""
Rather than joining an existing game, the client may choose to begin a new game:
C: "#008 newgame \"my game's name\" GAMETYPE"
S: "#008 ok"
If accepted, the client then is auto-joined to the new game room.
The game will begin when all players have chosen teams and voted to start. The
server informs all clients that play may proceed.
S: "* gamestart"
During gameplay, the client may at any time request pawn information. The server will
respond with untagged data of the form
PAWNNAME is one of {X, Red, Yellow, Green, Blue, Black}, LOCATION is the obvious integer
value, and the remaining arguments are the numbers of tokens the pawn possesses. The
special value "-1" is used to indicate a hidden location, and is also used to indicate
infinite tokens.
C: "#010 pawninfo"
S: "#010 ok"
S: "* pawninfo X Joe 64 -1 -1 -1 4 2"
S: "* pawninfo Red Dan 133 8 8 4 0 0"
S: "* pawninfo Yellow Dan 118 9 7 4 0 0"
S: "* pawninfo Green Dan 28 8 8 4 0 0"
S: "* pawninfo Blue Dan 183 8 8 4 0 0"
S: "* pawninfo Black Dan 135 9 8 3 0 0"
During gameplay, the client may at any time request to know whose turn it is:
C: "#011 whoseturn"
S: "#011 turn Red"
During gameplay, the client may at any time request to know the turn number:
C: "#012 turnnum"
S: "#012 5"
If it is the client's turn, the client may request to make a move:
C: "#013 move DESTINATION TICKET", or
S: "#013 ok"
The client may at any time request the partial game history. The server responds
with a series of untagged responses of the form
"* history 0 \"X X_INIT_LOC\" \"Red Red_INIT_LOC\" ... \"Black Black_INIT_LOC\""
C: "#014 history"
S "#014 ok"
S: "* history 0 \"X -1\" \"Red 132\" \"Yellow 141\" \"Green 29\" \"Blue 197\" \"Black 174\""
S: "* history 1 \"X -1 taxi\" \"Red 140 taxi\" \"Yellow 142 taxi\" \"Green 41 taxi\" \"Blue 196 taxi\" \"Black 161 taxi\""
S: ...
S: "* history end"
(Game history is just a chronological log of all moves. Use of double moves
is not explicitly noted, but must be extrapolated by the client.)
As game state changes occur, the server will inform the clients automatically.
(If the destination is obscured, the server will provide the number "-1".)
S: "* turn PAWNNAME"
S: "* turnnum NUM"
S: "* gameover WINNING_TEAM \"reason for win\"
S: "* playerleave USERNAME"
(Gameplay does not cease until all players have left the game. The
disconnected player can resume at any time, request player info and game
history, and continue. If all players leave the game before it is over,
the server ought to write game state to disk in the assumption that the
players will not be returning immediately.)
During gameplay, the client may send a chat message to all players or just to
its team:
C: "#015 chatall \"my message\"", or
"#015 chatteam \"my message\""
S: "#007 ok"
The server will broadcast chat messages only to the appropriate clients
(including the sender):
S: "* chatall \"nick\" \"message\"", or
S: "* chatteam \"nick\" \"message\""
# arch-tag: DO_NOT_CHANGE_ec83ed4f-17db-4323-895b-96d5855d0b52
MEDIAROOT = "guiclient"
This diff is collapsed.
# London Law -- a networked manhunting board game
# Copyright (C) 2003-2004, 2005 Paul Pelzl
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, Version 2, as
# published by the Free Software Foundation.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
LLAW_VERSION = "0.2.1"
LLAW_PORT = 7921
# arch-tag: DO_NOT_CHANGE_4feb10bd-0b69-4889-8569-2a2f163b0468
import re
def escape_chars(token):
return re.sub(r"""("|\\)""", r"\\\1", token)
def add_quotes(token):
if"\s", token):
return "".join(('"', token, '"'))
return token
def join_tokens(*tokens):
tokens = [escape_chars(token) for token in tokens]
tokens = [add_quotes(token) for token in tokens]
return " ".join(tokens)
def parse_bool(str):
str = str.lower()
if str == "true":
return True
elif str == "false":
return False
raise ValueError("invalid boolean value: %s" % str)
_nick_re = re.compile(r"^[^\n\t\"]+$")
def validate_nick(nick):
if len(nick) > 25:
raise ValueError("nick too long")
if not _nick_re.match(nick):
raise ValueError("illegal format for player nick")
return nick
# arch-tag: f95a921b-7e45-4d54-9273-ae670d7ac93f
# London Law -- a networked manhunting board game
# Copyright (C) 2003-2004, 2005 Paul Pelzl
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, Version 2, as
# published by the Free Software Foundation.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# This module contains a base class list control that does the following:
# * sort by column when clicking on headers (wxColumnSorterMixin)
# * auto-expands the width of the last column to fill available space
# (wxListCtrlAutoWidthMixin)
# * supports realtime addition and removal of items
# This base class will be used in both the game room browser and the
# team selection window.
# appears after first logging in to a server. Users may join exactly one game
# at a time, at which point a player registration window is spawned.
from twisted.python import log
from wxPython.wx import *
from wxPython.lib.mixins.listctrl import wxColumnSorterMixin, wxListCtrlAutoWidthMixin
from londonlaw.common.config import *
import os.path
# the AutoWidthMixin simply resizes the last column of of the
# ListCtrl to take up all remaining space.
class AutoWidthListCtrl(wxListCtrl, wxListCtrlAutoWidthMixin):
def __init__(self, parent, ID, pos = wxDefaultPosition,
size = wxDefaultSize, style = 0):
wxListCtrl.__init__(self, parent, ID, pos, size, style)
# 'headers' is a list of column headers.
# 'placeholder' should be a list of display data that is shown when
# the list is empty (same length as 'headers').
class AutoListCtrl(AutoWidthListCtrl, wxColumnSorterMixin):
def __init__(self, parent, ID, headers, placeholder = None):
AutoWidthListCtrl.__init__(self, parent, ID, wxDefaultPosition, wxDefaultSize,
self.headers = headers
# load in the tiny arrow images that wxColumnSorterMixin draws
# in the headers of sorted columns
# WARNING: this segfaults if imageList is a local variable.
# Maybe a wxPython bug... imageList falls out of scope and gets deleted prematurely?
self.imageList = wxImageList(16, 16, TRUE)
file1 = os.path.normpath(os.path.join(MEDIAROOT, "images/smalluparrow.png"))
file2 = os.path.normpath(os.path.join(MEDIAROOT, "images/smalldownarrow.png"))
image = wxImage(file1, wxBITMAP_TYPE_ANY)
image.SetMaskColour(255, 255, 255)
self.smallUpArrow = self.imageList.Add(wxBitmapFromImage(image))
image = wxImage(file2, wxBITMAP_TYPE_ANY)
image.SetMaskColour(255, 255, 255)
self.smallDnArrow = self.imageList.Add(wxBitmapFromImage(image))
self.SetImageList(self.imageList, wxIMAGE_LIST_SMALL)
self.placeholder = placeholder
# data from the server should be formatted as
# ("game name", "game type", "game status", "number of players")
if self.placeholder:
self.itemDataMap = {0 : self.placeholder}
self.itemDataMap = {}
# this must be called *after* the list has been created
wxColumnSorterMixin.__init__(self, len(self.headers))
def populateList(self):
info = wxListItem()
info.m_image = -1
info.m_format = wxLIST_FORMAT_CENTRE
for i in range(len(self.headers)):
info.m_text = self.headers[i]
self.InsertColumnInfo(i, info)
items = self.itemDataMap.items()
for i in range(len(items)):
key, data = items[i]
self.InsertStringItem(i, data[0])
for j in range(1, len(self.headers)):
self.SetStringItem(i, j, data[j])