2 Copyright (c) 2006 Paolo Capriotti <p.capriotti@gmail.com>
3 (c) 2006 Maurizio Monge <maurizio.monge@kdemail.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
11 #include "icsconnection.h"
13 #include <QStringList>
17 #include <core/state.h>
18 #include <core/statefactory.h>
20 #include "components.h"
21 #include "icsconnection.h"
23 #include "positioninfo.h"
26 #include "pgnparser.h"
27 #include "icslistener.h"
29 using namespace boost
;
31 QRegExp
ICSConnection::pressReturn("^Press return to enter the server as \"\\S+\":");
33 // example: Creating: azsxdc (++++) Hispanico (1684) unrated crazyhouse 3 0
34 QRegExp
ICSConnection::creating("^Creating: (\\S+)\\s+\\((\\S*)\\)\\s+(\\S+)\\s+\\((\\S*)\\)"
35 "\\s+(\\S+)\\s+(\\S+)\\s+(\\d+)\\s+(\\d+)\\s*.*$");
37 // example: {Game 149 (azsxdc vs. Hispanico) Creating unrated crazyhouse match.}
38 // example: {Game 149 (azsxdc vs. Hispanico) Game aborted on move 1} *
39 QRegExp
ICSConnection::game("^\\{Game\\s+(\\d+)\\s+\\((\\S+)\\s+vs\\.\\s+(\\S+)\\)\\s+"
40 "(\\S+.*)\\}(?:\\s+(\\S+.*)|\\s*)$");
41 QRegExp
ICSConnection::unexamine("^You\\s+are\\s+no\\s+longer\\s+examining\\s+game\\s+(\\d+)");
42 QRegExp
ICSConnection::unobserve("^Removing\\s+game\\s+(\\d+)\\s+from\\s+observation\\s+list\\s*");
44 // example: 124 848 shanmark 1155 damopn [ br 2 12] 2:04 - 2:08 (39-39) W: 3
45 //QRegExp ICSConnection::gameInfo("^(\\d+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+"
46 // "\\[\\s+(\\S+)\\s+(\\d+)\\s+(\\d+)\\s+\\]");
49 // example: Game 124: shanmark ( 848) damopn (1155) rated blitz 2 12
50 // Game 170: AstralVision (2167) vladx (2197) rated standard 15 0
51 QRegExp
ICSConnection::observed_game("^Game\\s+(\\d+):\\s+(\\S+)\\s+\\(\\s*(\\S+)\\s*\\)\\s+"
52 "(\\S+)\\s+\\(\\s*(\\S+)\\s*\\)\\s+"
53 "(\\S+)\\s+(\\S+)\\s+(\\d+)\\s+(\\d+)");
55 QRegExp
ICSConnection::login("^login: $");
56 QRegExp
ICSConnection::password("^password: $");
57 QRegExp
ICSConnection::fics("^\\S+% ");
58 QRegExp
ICSConnection::beep("^\a");
60 //ex: Movelist for game 95:
61 QRegExp
ICSConnection::move_list_start("^Movelist\\s+for\\s+game\\s+(\\d+):");
62 //ex: npssnt (1586) vs. mikkk (1487) --- Sun Sep 3, 18:16 PDT 2006
63 QRegExp
ICSConnection::move_list_players("^(\\S+)\\s+\\((\\S*)\\)\\s+vs.\\s+(\\S+)\\s+\\((\\S*)\\)\\s*.*");
64 //ex: Unrated blitz match, initial time: 3 minutes, increment: 0 seconds.
65 QRegExp
ICSConnection::move_list_game("^(\\S+)\\s+(\\S+)\\s+.*initial\\s+time:\\s*(\\d+)"
66 "\\s+.*increment:\\s*(\\d+)\\s*.*");
67 //ex: Move npssnt mikkk
68 QRegExp
ICSConnection::move_list_ignore1("^Move\\s+(\\S+)\\s+(\\S+)");
69 //ex: ---- ---------------- ----------------
70 QRegExp
ICSConnection::move_list_ignore2("^( +|-+)+");
71 QRegExp
ICSConnection::move_list_terminator("^\\s*$");
73 QRegExp
ICSConnection::goForward("^Game (\\d+): (\\S+) goes forward (\\d+) moves?\\.");
74 QRegExp
ICSConnection::goBack("^Game (\\d+): (\\S+) backs up (\\d+) moves?\\.");
77 ICSConnection::ICSConnection()
79 , m_move_list_game_info(NULL
)
80 , m_move_list_position_info(NULL
)
81 , m_move_list_pool_info(NULL
) {
83 connect(this, SIGNAL(receivedLine(QString
, int)), this, SLOT(process(QString
)));
84 connect(this, SIGNAL(receivedText(QString
, int)), this, SLOT(processPartialLine(QString
)));
87 bool ICSConnection::test(const QRegExp
& pattern
, const QString
& str
) {
88 if (pattern
.indexIn(str
, m_processed_offset
, QRegExp::CaretAtOffset
) >= 0) {
89 m_processed_offset
= pattern
.matchedLength();
95 void ICSConnection::processPartialLine(QString str
) {
96 // kDebug() << "processing (partial) " << str;
97 if (test(fics
, str
)) {
98 // kDebug() << "matched prompt";
101 else if (test(beep
, str
)) {
104 else if (test(pressReturn
, str
)) {
105 registeredNickname();
107 else if (test(login
, str
)) {
110 else if (test(password
, str
)) {
114 // no match, but it could be a partial command
115 m_processed_offset
= 0;
119 void ICSConnection::process(QString str
) {
122 if (test(creating
, str
)) {
123 delete incomingGameInfo
;
124 incomingGameInfo
= new GameInfo(creating
, 1);
126 else if(test(observed_game
, str
)) {
128 delete incomingGameInfo
;
129 incomingGameInfo
= new GameInfo(Player(observed_game
.cap(2), observed_game
.cap(3).toInt()),
130 Player(observed_game
.cap(4), observed_game
.cap(5).toInt()),
131 observed_game
.cap(6), observed_game
.cap(7),
132 observed_game
.cap(8).toInt(), observed_game
.cap(9).toInt() );
133 int number
= observed_game
.cap(1).toInt();
134 incomingGameInfo
->setGameNumber(number
);
135 m_games
[number
] = ICSGameData(-1, incomingGameInfo
->type());
136 //kDebug() << "ok, obs " << number << " of type " << incomingGameInfo->type();
138 else if (test(game
, str
)) {
139 //kDebug() << "matched game. incomingGameInfo = " << incomingGameInfo;
140 if(game
.cap(4).startsWith("Creating") || game
.cap(4).startsWith("Continuing") ) {
141 if (!incomingGameInfo
) {
142 //this should really never happen, but anyway...
143 QStringList info
= game
.cap(4).split(' ');
145 incomingGameInfo
= new GameInfo(Player(game
.cap(2), 0),
146 Player(game
.cap(3), 0),
147 info
[1], info
[2], 0, 0 );
149 if (incomingGameInfo
) {
150 int number
= game
.cap(1).toInt();
151 incomingGameInfo
->setGameNumber(number
);
152 m_games
.insert(std::make_pair(number
, ICSGameData(-1, incomingGameInfo
->type())));
156 if (!incomingGameInfo
) {
157 int number
= game
.cap(1).toInt();
158 //kDebug() << "matching game " << number << " end";
159 m_games
.erase(number
);
160 QString what
= game
.cap(4);
161 QString result
= game
.cap(5);
162 endingGame(what
, result
);
166 else if (test(unexamine
, str
)) {
167 //kDebug() << "matching examined game end";
168 int gameNumber
= unexamine
.cap(1).toInt();
169 m_games
.erase(gameNumber
);
170 endingExaminedGame(gameNumber
);
172 else if (test(unobserve
, str
)) {
173 int gameNumber
= unobserve
.cap(1).toInt();
174 m_games
.erase(gameNumber
);
175 endingObservedGame(gameNumber
);
177 else if (test(move_list_start
, str
)) {
178 //kDebug() << "entering move list state";
179 m_move_list_game_num
= move_list_start
.cap(1).toInt();
180 state
= MoveListHeader
;
183 PositionInfo positionInfo
;
184 bool game_start
= positionInfo
.load(m_games
, str
);
185 if (positionInfo
.valid
) {
186 int gameNumber
= positionInfo
.gameNumber
;
187 GameList::const_iterator game_it
= m_games
.find(gameNumber
);
188 Q_ASSERT(game_it
!= m_games
.end());
190 bool incoming
= incomingGameInfo
&&
191 incomingGameInfo
->gameNumber() == gameNumber
;
193 if (game_start
|| incoming
) {
194 // delete extraneous game info
195 if (incomingGameInfo
&&
196 incomingGameInfo
->gameNumber() != gameNumber
) {
197 int n
= incomingGameInfo
->gameNumber();
200 delete incomingGameInfo
;
201 incomingGameInfo
= 0;
204 // no info on this game
205 if (!incomingGameInfo
) {
206 kWarning() << "unexpected style 12 for game" << gameNumber
;
207 incomingGameInfo
= new GameInfo(Player(positionInfo
.whitePlayer
, 0),
208 Player(positionInfo
.blackPlayer
, 0),
212 switch (positionInfo
.relation
) {
213 case PositionInfo::NotMyMove
:
214 case PositionInfo::MyMove
:
215 //kDebug() << "creating game";
216 creatingGame(incomingGameInfo
, positionInfo
);
218 case PositionInfo::Examining
:
219 //kDebug() << "creating examination";
220 creatingExaminedGame(incomingGameInfo
, positionInfo
);
222 case PositionInfo::ObservingPlayed
:
223 case PositionInfo::ObservingExamined
:
224 //kDebug() << "creating obs " << gameNumber << " " << incomingGameInfo->type();
225 creatingObservedGame(incomingGameInfo
, positionInfo
);
228 // unknown relation: ignoring
232 delete incomingGameInfo
;
233 incomingGameInfo
= 0;
236 if (shared_ptr
<ICSListener
> listener
= m_games
[positionInfo
.gameNumber
].listener
.lock())
237 listener
->notifyStyle12(positionInfo
, game_start
);
239 if (positionInfo
.relation
== PositionInfo::MyMove
) {
244 PoolInfo
pool_info(m_games
, str
);
245 if (pool_info
.m_valid
) {
247 if (pool_info
.m_added_piece
!= Piece()) {
248 if (shared_ptr
<ICSListener
> listener
= m_games
[pool_info
.m_game_num
].listener
.lock())
249 listener
->notifyPool(pool_info
);
257 if (test(move_list_players
, str
)){
258 //kDebug() << "move list players: " << str;
259 m_move_list_players
= move_list_players
.capturedTexts();
261 else if (test(move_list_game
, str
)){
262 //kDebug() << "move list game: " << str;
263 if (m_move_list_game_info
)
264 delete m_move_list_game_info
;
266 if (m_move_list_players
.size()>=5)
267 m_move_list_game_info
= new GameInfo(
268 Player(m_move_list_players
[1], m_move_list_players
[2].toInt()),
269 Player(m_move_list_players
[3], m_move_list_players
[4].toInt()),
270 move_list_game
.cap(1).toLower(), move_list_game
.cap(2),
271 move_list_game
.cap(3).toInt(), move_list_game
.cap(4).toInt()
274 m_move_list_game_info
= new GameInfo( Player("unknown",0), Player("unknown",0),
275 move_list_game
.cap(1).toLower(), move_list_game
.cap(2),
276 move_list_game
.cap(3).toInt(), move_list_game
.cap(4).toInt()
278 m_move_list_game_info
->setGameNumber(m_move_list_game_num
);
280 //NOTE: here is where an unknown variant will be "upgraded" to the correct variant
281 m_games
[m_move_list_game_num
].setType(move_list_game
.cap(2));
283 else if (test(move_list_terminator
, str
)) {
284 //kDebug() << "move list ign3: " << str;
286 else if (test(move_list_ignore1
, str
)){
287 //kDebug() << "move list ign1: " << str;
289 else if (test(move_list_ignore2
, str
)) {
290 //kDebug() << "move list ign2: " << str;
291 state
= MoveListMoves
;
295 pi
.load(m_games
, str
);
297 m_move_list_position_info
= new PositionInfo(pi
);
299 PoolInfo
pooli(m_games
, str
);
301 m_move_list_pool_info
= new PoolInfo(pooli
);
306 if (test(move_list_terminator
, str
)){
307 if (shared_ptr
<ICSListener
> listener
= m_games
[m_move_list_game_num
].listener
.lock()) {
309 if (m_move_list_position_info
)
310 p
= m_move_list_position_info
->position
;
312 std::map
<int, ICSGameData
>::const_iterator gi
= m_games
.find(m_move_list_game_num
);
313 if (gi
== m_games
.end()) {
314 kError() << "Received move list for unknown game " << m_move_list_game_num
;
317 Components
* components
= gi
->second
.components
;
318 p
= StatePtr(components
->createState());
324 if (m_move_list_pool_info
) {
326 //p->setPool(m_move_list_pool_info->m_pool);
329 PGN
pgn(m_move_list
);
331 kDebug() << "parse error on move list";
333 listener
->notifyMoveList(m_move_list_game_num
, p
, pgn
);
337 if (m_move_list_game_info
)
338 delete m_move_list_game_info
;
339 if (m_move_list_position_info
)
340 delete m_move_list_position_info
;
341 if (m_move_list_pool_info
)
342 delete m_move_list_pool_info
;
343 m_move_list_game_info
= NULL
;
344 m_move_list_position_info
= NULL
;
345 m_move_list_pool_info
= NULL
;
346 m_move_list
= QString();
354 m_processed_offset
= 0;
357 void ICSConnection::setListener(int gameNumber
, const weak_ptr
<ICSListener
>& listener
) {
358 m_games
[gameNumber
].listener
= listener
;
361 void ICSConnection::startup() {
362 sendText("alias $ @");
363 sendText("iset startpos 1");
364 sendText("iset ms 1");
365 sendText("iset lock 1");
366 sendText("set interface Tagua-0.10 (http://www.tagua-project.org)");
367 sendText("set style 12");