端末操作
今日は端末操作用のエスケープシーケンスを調べる機会があったので、その内の自分が良く使いそうな操作をcommon lispのパッケージとしてまとめておくことにする。
(defpackage ppterm (:use :common-lisp) (:export *colorset* color clear cursor)) (in-package :ppterm) (defvar *colorset* '(:black :red :green :brown :blue :purple :cyan :light-gray)) (defvar *fg-color-codes* (loop FOR i FROM 30 TO 37 COLLECT i)) (defvar *bg-color-codes* (loop FOR i FROM 40 TO 47 COLLECT i)) (defconstant +ESC-CHAR+ (code-char #o33)) ;; 文字の色(+その他)変更用のエスケープシーケンス (deftype colorset () `(member ,@*colorset*)) (defun color (string &key color back bold underline inverse) (declare ((or null colorset) color back) (boolean bold underline inverse)) (with-output-to-string (out) ;; エスケープシーケンス開始 (format out "~C[" +ESC-CHAR+) ;; 文字の色などを設定 (format out "~{~D~^;~}" (remove nil (list ;; 文字色 (when color #.`(ecase color ,@(mapcar (lambda (color code) `(,color ,code)) *colorset* *fg-color-codes*))) ;; 背景色 (when back #.`(ecase back ,@(mapcar (lambda (color code) `(,color ,code)) *colorset* *bg-color-codes*))) ;; 太字 (when bold 1) ;; 下線 (when underline 4) ;; 反転 (when inverse 7)))) ;; 文字列出力 (format out "m~A" string) ;; エスケープシーケンス終了 (format out "~C[0m" +ESC-CHAR+))) ;; カーソル位置設定用のエスケープシーケンス (deftype cursor-position () '(integer 0 #.most-positive-fixnum)) (defun cursor (&key x y up down left right) (declare ((or null cursor-position) x y up down left right)) (when (and x (null y)) (setf y 0)) (when (and y (null x)) (setf x 0)) (with-output-to-string (out) (when x (format out "~C[~D;~DH" +ESC-CHAR+ y x)) ;; カーソル位置を(x,y)で指定 (when up (format out "~C[~DA" +ESC-CHAR+ up)) ;; カーソルをup分上に移動 (when down (format out "~C[~DB" +ESC-CHAR+ down)) ;; カーソルをdown分下に移動 (when left (format out "~C[~DD" +ESC-CHAR+ left)) ;; カーソルをleft分右に移動 (when right (format out "~C[~DC" +ESC-CHAR+ right))));; カーソルをright分左に移動 ;; 画面クリア用のエスケープシーケンス ;; lineがtなら、一行のみを、nilなら画面全体をクリアする (defun clear (&optional line) (declare (boolean line)) (with-output-to-string (out) (format out "~C[~:[2J~;K~]" +ESC-CHAR+ line)))
使用例。
> (ppterm:color "赤い文字" :color :red)
-
- > "赤い文字"
少し長い例。
三目並べ 。※いろいろ適当
;; 盤を表示。ここでpptermを使用。 (defun print-board (board) (princ (ppterm:clear)) (loop FOR x FROM 0 TO 2 DO (loop FOR y FROM 0 TO 2 DO (princ (ppterm:cursor :x (* 2 (1+ x)) :y (1+ y))) (case (aref board x y) (:first (princ (ppterm:color "■" :color :red :bold t))) (:second (princ (ppterm:color "■" :color :blue :bold t))) (otherwise (princ (ppterm:color "□" :color :black))))))) ;; ゲームが終了したかどうかの判定 (defun finish? (board) (loop FOR player IN '(:first :second) DO (when (or (some (lambda (i) (or (loop FOR x FROM 0 TO 2 ALWAYS (eq (aref board x i) player)) ; 横一列 (loop FOR y FROM 0 TO 2 ALWAYS (eq (aref board i y) player)))) ; 縦一列 '(0 1 2)) (loop FOR x FROM 0 TO 2 FOR y FROM 0 TO 2 ALWAYS (eq (aref board x y) player)) ; 斜め1 (loop FOR x FROM 0 TO 2 FOR y FROM 2 DOWNTO 0 ALWAYS (eq (aref board x y) player))) ; 斜め2 (return-from finish? player))) (when (loop FOR i FROM 0 BELOW (array-total-size board) ; 全部のマスが埋まっているかどうか ALWAYS (row-major-aref board i)) (return-from finish? :draw))) ;; (defun game () (let ((board (make-array '(3 3) :initial-element nil))) (loop UNTIL (finish? board) FOR player = :first THEN (if (eq player :first) :second :first) DO (loop (print-board board) (format t "~&~:[後手~;先手~](x y): " (eq player :first)) (force-output) (let ((x (read)) (y (read))) (when (and (<= 0 x 2) (<= 0 y 2) (null (aref board x y))) (setf (aref board x y) player) (return))))) (print-board board) (format t "~&~A" (finish? board)) (force-output)) (values)) ;; 実行 > (game) ; ... ; ...