• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
Keine Tags

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

shogi-server source


Commit MetaInfo

Revision9c124efa7d68cb35fc3e837f2abae76ead13c9ed (tree)
Zeit2004-05-30 13:26:52
Autornabeken <nabeken@b8c6...>
Commiternabeken

Log Message

initial

Ändern Zusammenfassung

Diff

--- /dev/null
+++ b/shogi-server
@@ -0,0 +1,447 @@
1+#! /usr/bin/env ruby
2+## -*-Ruby-*- $RCSfile$ $Revision$ $Name$
3+
4+## Copyright (C) 2004 773@2ch
5+##
6+## This program is free software; you can redistribute it and/or modify
7+## it under the terms of the GNU General Public License as published by
8+## the Free Software Foundation; either version 2 of the License, or
9+## (at your option) any later version.
10+##
11+## This program is distributed in the hope that it will be useful,
12+## but WITHOUT ANY WARRANTY; without even the implied warranty of
13+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+## GNU General Public License for more details.
15+##
16+## You should have received a copy of the GNU General Public License
17+## along with this program; if not, write to the Free Software
18+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19+
20+DEFAULT_TIMEOUT = 10 # for single socket operation
21+Total_Time = 1500
22+Least_Time_Per_Move = 1
23+Watchdog_Time = 30 # time for ping
24+Agree_Time = 300 # time for AGREE
25+Login_Time = 300 # time for LOGIN
26+
27+Release = "$Name$".split[1].sub(/\A[^\d]*/, '').gsub(/_/, '.')
28+Release.concat("-") if (Release == "")
29+Revision = "$Revision$".gsub(/[^\.\d]/, '')
30+
31+STDOUT.sync = true
32+STDERR.sync = true
33+
34+require 'getoptlong'
35+require 'thread'
36+require 'timeout'
37+require 'socket'
38+require 'ping'
39+
40+TCPSocket.do_not_reverse_lookup = true
41+
42+class TCPSocket
43+ def gets_timeout(t = DEFAULT_TIMEOUT)
44+ begin
45+ timeout(t) do
46+ return self.gets
47+ end
48+ rescue TimeoutError
49+ return nil
50+ rescue
51+ return nil
52+ end
53+ end
54+ def gets_safe
55+ begin
56+ return self.gets
57+ rescue
58+ return nil
59+ end
60+ end
61+ def write_safe(str)
62+ begin
63+ return self.write(str)
64+ rescue
65+ return nil
66+ end
67+ end
68+end
69+
70+
71+class League
72+ def initialize
73+ @hash = Hash::new
74+ end
75+ attr_accessor :hash
76+
77+ def add(player)
78+ @hash[player.name] = player
79+ end
80+ def delete(player)
81+ @hash.delete(player.name)
82+ end
83+ def duplicated?(player)
84+ if (@hash[player.name])
85+ return true
86+ else
87+ return false
88+ end
89+ end
90+end
91+
92+
93+
94+
95+class Player
96+ def initialize(str, socket)
97+ @name = nil
98+ @password = nil
99+ @socket = socket
100+ @state = "connected" # wait_game -> game
101+
102+ @x1 = false # extention protocol
103+ @eol = "\m" # favorite eol code
104+ @game = nil
105+ @mytime = Total_Time
106+ @sente = nil
107+ @watchdog_thread = nil
108+
109+ login(str)
110+ end
111+
112+ attr_accessor :name, :password, :socket, :state
113+ attr_accessor :x1, :eol, :game, :mytime, :watchdog_thread
114+
115+
116+ def write_safe(str)
117+ @socket.write_safe(str + @eol)
118+ end
119+
120+ def login(str)
121+ str =~ /([\r\n]*)$/
122+ @eol = $1
123+ str.chomp!
124+ (login, @name, @password, ext) = str.split
125+ @x1 = true if (ext)
126+ end
127+
128+ def run
129+ if (@x1)
130+ log_message(sprintf("user %s run in x1 mode", @name))
131+ write_safe("## LOGIN in x1 mode")
132+ else
133+ log_message(sprintf("user %s run in CSA mode", @name))
134+ end
135+
136+ while (str = @socket.gets_safe)
137+ str.chomp!
138+ case str
139+ when /^%%HELP/
140+ write_help
141+ when /^%%GAME\s+(\S+)\s+([\+\-])/
142+ game_name = $1
143+ @state = "game_waiting"
144+ if ($2 == "+")
145+ @sente = true
146+ rival_sente = false
147+ else
148+ @sente = false
149+ rival_sente = true
150+ end
151+ rival = LEAGUE.get_player(game_name, rival_sente)
152+ if (rival)
153+ @state = "game"
154+ LEAGUE.start_game(game_name, self, rival)
155+ end
156+ when /^%%CHAT\s+(\S+)/
157+ message = $1
158+ LEAGUE.hash.each do |name, player|
159+ s = player.write_safe(sprintf("## [%s] %s", @name, message))
160+ player.status = "zombie" if (! s)
161+ end
162+ when /^%%WHO/
163+ LEAGUE.hash.each do |name, player|
164+ write_safe(sprintf("## %s %s", name, player.state))
165+ end
166+ when /^%%LOGOUT/
167+ break
168+ else
169+ write_safe(sprintf("## unknown command %s", str))
170+ end
171+ end
172+ end
173+end
174+
175+class Board
176+end
177+
178+class Game
179+ def initialize(event, sente, gote)
180+ @id = sprintf("%s-%s-%s-%s", event, sente.name, gote.name, Time::new.strftime("%Y%m%d%H%M%S"))
181+ @logfile = @id + ".csa"
182+ @sente = sente
183+ @gote = gote
184+ @sente.sg_flag = "+"
185+ @gote.sg_flag = "-"
186+ @board = Board::new
187+ @currnet_player = sente
188+ @next_player = gote
189+ @fh = nil
190+ printf("%s: new game %s %s %s\n", Time::new.to_s, @id, @sente.name, @gote.name)
191+ end
192+ def start
193+ begin
194+ @sente.watchdog_start(Watchdog_Time)
195+ @gote.watchdog_start(Watchdog_Time)
196+
197+ @fh = open(@logfile, "w")
198+ @fh.sync = true
199+
200+ @fh.printf("V2\n")
201+ @fh.printf("N+%s\n", @sente.name)
202+ @fh.printf("N-%s\n", @gote.name)
203+ @fh.printf("$EVENT:%s\n", @id)
204+ @sente.write(start_message("+"))
205+ @gote.write(start_message("-"))
206+ @sente.wait_agree(Agree_Time)
207+ @gote.wait_agree(Agree_Time)
208+
209+ @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
210+ @fh.print <<EOM
211+P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
212+P2 * -HI * * * * * -KA *
213+P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
214+P4 * * * * * * * * *
215+P5 * * * * * * * * *
216+P6 * * * * * * * * *
217+P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
218+P8 * +KA * * * * * +HI *
219+P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
220++
221+EOM
222+
223+ @sente.write(sprintf("START:%s\n", @id))
224+ @gote.write(sprintf("START:%s\n", @id))
225+ while(true)
226+ @currnet_player = @sente
227+ @next_player = @gote
228+ handle_one_move(@currnet_player, @next_player)
229+
230+ @currnet_player = @gote
231+ @next_player = @sente
232+ handle_one_move(@currnet_player, @next_player)
233+ end
234+ rescue ShogiWatchdogTimeout
235+ sg_flag_of_timeout = $!.message
236+ if (sg_flag_of_timeout == "+")
237+ loser = @sente
238+ winner = @gote
239+ else
240+ loser = @sente
241+ winner = @gote
242+ end
243+ printf("watchdog timeout by %s\n", loser.name)
244+ loser.write("#TIME_UP\n#LOSE\n")
245+ winner.write("#TIME_UP\n#WIN\n")
246+ rescue TimeoutError, ShogiTimeout
247+ printf("%s: end timeup by %s\n", Time::new.to_s, @currnet_player.name)
248+ @currnet_player.write("#TIME_UP\n#LOSE\n")
249+ @next_player.write("#TIME_UP\n#WIN\n")
250+ rescue ShogiReject
251+ sender = $!.message
252+ printf("%s: reject by %s\n", Time::new.to_s, sender)
253+ str = sprintf("REJECT:%s by %s\n", @id, sender)
254+ @sente.write(str)
255+ @gote.write(str)
256+ rescue ShogiIllegalMove
257+ printf("%s: end illegal move by %s\n", Time::new.to_s, @currnet_player.name)
258+ move = $!.message
259+ @fh.printf("%%ERROR\n")
260+ @currnet_player.write(sprintf("%s\n#ILLEGAL_MOVE\n#LOSE\n", move))
261+ @next_player.write(sprintf("%s\n#ILLEGAL_MOVE\n#WIN\n", move))
262+ rescue ShogiEnd
263+ printf("%s: end by %s\n", Time::new.to_s, @currnet_player.name)
264+ move = $!.message
265+ case move
266+ when "%TORYO"
267+ @currnet_player.write(sprintf("%s\n#RESIGN\n#LOSE\n", move))
268+ @next_player.write(sprintf("%s\n#RESIGN\n#WIN\n", move))
269+ when "%KACHI"
270+ @currnet_player.write(sprintf("%s\n#JISHOGI\n#WIN\n", move))
271+ @next_player.write(sprintf("%s\n#JISHOGI\n#LOSE\n", move))
272+ end
273+ end
274+ @fh.printf("'END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
275+ end
276+
277+ def handle_one_move(current_player, next_player)
278+ start_time = Time::new
279+ str = current_player.get_move
280+ @fh.printf("%s\n", str)
281+ end_time = Time::new
282+ time = (end_time - start_time).truncate
283+ time = Least_Time_Per_Move if (time < Least_Time_Per_Move)
284+ current_player.sub_time(time)
285+ raise ShogiEnd, str if (str =~ /\A%/)
286+ @sente.write(sprintf("%s,T%d\n", str, time))
287+ @gote.write(sprintf("%s,T%d\n", str, time))
288+ @fh.printf("T%s\n", time)
289+ end
290+
291+ def finish
292+ @sente.finish
293+ @gote.finish
294+ @fh.close
295+ printf("%s: end game %s %s %s\n", Time::new.to_s, @id, @sente.name, @gote.name)
296+ end
297+
298+ def start_message(sg_flag)
299+ str = <<EOM
300+Protocol_Mode:Server
301+Format:Shogi 1.0
302+Game_ID:#{@id}
303+Name+:#{@sente.name}
304+Name-:#{@gote.name}
305+Your_Turn:#{sg_flag}
306+Rematch_On_Draw:NO
307+To_Move:+
308+BEGIN Time
309+Time_Unit:1sec
310+Total_Time:#{Total_Time}
311+Least_Time_Per_Move:#{Least_Time_Per_Move}
312+END Time
313+BEGIN Position
314+Jishogi_Declaration:1.1
315+P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
316+P2 * -HI * * * * * -KA *
317+P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
318+P4 * * * * * * * * *
319+P5 * * * * * * * * *
320+P6 * * * * * * * * *
321+P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
322+P8 * +KA * * * * * +HI *
323+P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
324+P+
325+P-
326++
327+EOM
328+ return str
329+ end
330+end
331+
332+def usage
333+ print <<EOM
334+NAME
335+ shogi-server - server for CSA server protocol
336+
337+SYNOPSIS
338+ shogi-server event_name port_number
339+
340+DESCRIPTION
341+ server for CSA server protocol
342+
343+OPTIONS
344+ --pid-file file
345+ specify filename for logging process ID
346+
347+LICENSE
348+ this file is distributed under GPL version2 and might be compiled by Exerb
349+
350+SEE ALSO
351+
352+RELEASE
353+ #{Release}
354+
355+REVISION
356+ #{Revision}
357+EOM
358+end
359+
360+def log_message(str)
361+ printf("%s message: %s\n", Time::new.to_s, str)
362+end
363+
364+def log_warning(str)
365+ printf("%s message: %s\n", Time::new.to_s, str)
366+end
367+
368+def log_error(str)
369+ printf("%s error: %s\n", Time::new.to_s, str)
370+end
371+
372+
373+def parse_command_line
374+ options = Hash::new
375+ parser = GetoptLong.new
376+ parser.ordering = GetoptLong::REQUIRE_ORDER
377+ parser.set_options(
378+ ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
379+
380+ begin
381+ parser.each_option do |name, arg|
382+ options[name] = arg.dup
383+ end
384+ rescue
385+ usage
386+ raise parser.error_message
387+ end
388+ return options
389+end
390+
391+LEAGUE = League::new
392+
393+def good_login?(str)
394+ return false if (str !~ /^LOGIN /)
395+ tokens = str.split
396+ if ((tokens.length == 3) || (tokens.length == 4))
397+ ## ok
398+ else
399+ return false
400+ end
401+ return true
402+end
403+
404+def main
405+ $options = parse_command_line
406+ if (ARGV.length != 2)
407+ usage
408+ exit 2
409+ end
410+ event = ARGV.shift
411+ port = ARGV.shift
412+
413+ Thread.abort_on_exception = true
414+
415+ server = TCPserver.open(port)
416+ log_message("server started")
417+
418+ while true
419+ Thread::start(server.accept) do |client|
420+ client.sync = true
421+ while (str = client.gets_timeout(Login_Time))
422+ Thread::kill(Thread::current) if (! str) # disconnected
423+ str =~ /([\r\n]*)$/
424+ eol = $1
425+ if (good_login?(str))
426+ player = Player::new(str, client)
427+ if (LEAGUE.duplicated?(player))
428+ client.write_safe(sprintf("username %s is already connected", player.name))
429+ next
430+ end
431+ LEAGUE.add(player)
432+ break
433+ else
434+ client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol)
435+ end
436+ end # login loop
437+ log_message(sprintf("user %s login", player.name))
438+ player.run
439+ LEAGUE.delete(player)
440+ log_message(sprintf("user %s logout", player.name))
441+ end
442+ end
443+end
444+
445+if ($0 == __FILE__)
446+ main
447+end