Gauche:NetWork:goban

Gauche:NetWork:goban

goban

クライアント・サーバのプログラムを書いてみる。 「UNIXネットワークプログラミング入門」(ISBN4-7741-1754-4)を読んでて、 最初の方の例が簡単そうだったので、とりあえず作ってみる。


1対1通信のサンプル

  1. サーバは適当なポートでクライアントからの接続を待つ
  2. クライアントがサーバのポートへ接続要求
  3. 接続が確立したらクライアントから a1,a2,...c2,c3 などの形式で位置入力
  4. 次はサーバから同じ形式で位置入力
  5. 以降は交互に升目を取り合う
  6. 別にサーバだからって思考ルーチンがあるわけじゃないし手入力だ
  7. 勝ち負けの判定も特にしない
  8. 初手はクライアントから

と、まぁ要するに接続して通信してってのがこのサンプルの課題部分。2004/12/29 04:43:14 PST

goban.scm

(define-module goban
  (export make-goban
          goban-show-plane
          goban-check
          goban-set!
          position->row&col
          my-turn peer-turn
          ))
(select-module goban)

;; return values row col
;; row,col: #f #f -> quit
;; row,col: #t  _ -> illegal
(define (position->row&col pos)
  (if (and (string? pos)
           (= (string-length pos) 2))
      (values (case (string-ref pos 0)
                ((#\a) 1)
                ((#\b) 2)
                ((#\c) 3)
                (else #t))
              (- (x->integer (string-ref pos 1))
                 (x->integer #\0)))
      (if (and (string? pos)
               (equal? #\q (string-ref pos 0)))
          (begin
            (format #t "input quit command!~%")
            (values #f #f))
          (values #t #f))))

(define (make-goban . args)
  (let-keywords* args ((my   :my   #\o)
                       (peer :peer #\x))
    (let1 plane (map string-incomplete->complete
                     '("+123+"
                       "a...|"
                       "b...|"
                       "c...|"
                       "+---+"))
      (lambda (cmd . arg)
        (case cmd
          ;; (goban 'show)
          ((show) (for-each print plane))
          ;; (goban 'check row col)
          ((check) (let ((row (car arg))
                         (col (cadr arg)))
                     (and (<= 1 row 3)
                          (<= 1 col 3)
                          (equal? #\. (string-ref (list-ref plane row) col)))))
          ;; (goban 'set stone row col)
          ((set) (let ((stone (car arg))
                       (row (cadr arg))
                       (col (caddr arg)))
                   (set! (string-ref (list-ref plane row) col) stone)))
          (else (error "ERROR: No such command for goban" cmd)))))))

(define (goban-show-plane goban) (goban 'show))
(define (goban-set! goban stone row col) (goban 'set stone row col))
(define (goban-check goban row col) (goban 'check row col))

(define (my-turn goban stone out)
  (format #t "input[a1-c3]> ")
  (flush)
  (let again ((pos (read-line)))
    (receive (row col) (position->row&col pos)
      (cond
       ((eq? #t row) (my-turn goban stone out))
       ((eq? #f row) (begin
                       (format out "~a ~a~%" row col) ;; #f #f
                       (flush)
                       (raise #f)))
       ((goban-check goban row col)
        (begin
          (format out "~a ~a~%" row col) ;; send command to server
          (goban-set! goban stone row col)
          (goban-show-plane goban)))
       (else (my-turn goban stone out))))))

(define (peer-turn goban stone in)
  (format #t "wait peer turn~%")
  (flush)
  (let ((row (read in))
        (col (read in)))
    (if (eq? #f row)
        (begin
          (format #t "receive quit!~%")
          (flush)
          (raise #f)))
    (goban-set! goban stone (x->integer row) (x->integer col))
    (goban-show-plane goban)))

(provide "goban")

;; Local variables:
;; mode: scheme
;; end:

server.scm

":";exec gosh -b $0 "$@"
;; server sample
;;
(use gauche.net)
(use goban)

(define (call-with-server-socket socket proc)
  (let* ((c (socket-accept socket)))
    (socket-close socket)
    (with-error-handler
      (lambda _ (format #t "ERROR: close socket.~%")
              (socket-close c))
      (lambda _
        (proc (socket-input-port c)
              (socket-output-port c))))))

(define (server in out)
  (let1 goban (make-goban)
    (let turn ()
      (peer-turn goban #\x in)
      (my-turn goban #\o out)
      (turn))))


(define (main args)
  (define port (if (null? (cdr args)) "8080"
                   (cadr args)))
  (with-error-handler
    (lambda _ (format #t "ERROR: receive error!~%")
            (exit 0))
    (lambda _
      (call-with-server-socket
       (make-server-socket 'inet port)
       server))))

;; Local variables:
;; mode: scheme
;; end:

client.scm

":";exec gosh -b $0 "$@"
;; client sample
;;
(use gauche.net)
(use goban)

(define (client in out)
  (let1 goban (make-goban)
    (let turn ()
      (my-turn goban #\o out)
      (peer-turn goban #\x in)
      (turn))))

(define (main args)
  (define port (if (null? (cdr args)) "8080"
                   (cadr args)))
  (with-error-handler
    (lambda _ (format #t "ERROR: receive error!~%")
            (exit 0))
    (lambda _
      (call-with-client-socket
          (make-client-socket 'inet "localhost" port)
        client))))

;; Local variables:
;; mode: scheme
;; end:

学習効果

この手の通信をデバッグしていると (format ...) とか (flush ...) とか どういったタイミングでどんな風に渡っているのか少しずつ分かる。
インタラクティブに format しちゃあ flush したり、 read だけじゃなく、 read-line なんかも使ってみたりすると、 入出力って面白いと思ったり。(最初はなんじゃこりゃ?って思ったけど)


Last modified : 2012/02/23 03:15:31 UTC