package othello.app; import java.awt.*; import java.awt.event.*; import othello.*; /** * 盤面の描画と、人間が打つときの UI。 * * @version 1.00 */ class BoardCanvas extends Canvas { public static final Color boardColor = Color.green.darker(); private static final Color badSelectionColor = Color.red; private static final Color frameColor = Color.cyan; /* ウェイトの単位 [ms] */ public static final int waitUnit = 80; /* リサイズ時にアスペクト比を 1:1 に保つか否か */ private static final boolean keepSquareAspect = true; /* 描画用の各種座標値 */ private int width, height; private int boardX, boardY; private int boardWidth, boardHeight; private int cellWidth, cellHeight; /* オフスクリーンバッファ */ private Image offImage = null; private Graphics offG; /* 表示する盤面 */ private OthelloBoard board = null; /* 人間プレーヤー用 位置選択に関する 変数 */ private boolean positionSelecting = false; private Point position = null; private Object psLock = new Object(); private boolean stopRequested = false; private int frameX, frameY; public BoardCanvas() { addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { Dimension size = getSize(); width = size.width; height = size.height; cellWidth = (width - 1) / 8; cellHeight = (height - 1) / 8; if (keepSquareAspect) { cellWidth = Math.min(cellWidth, cellHeight); cellHeight = cellWidth; } boardWidth = cellWidth * 8 + 1; boardHeight = cellHeight * 8 + 1; boardX = (width - boardWidth) / 2; boardY = (height - boardHeight) / 2; offImage = createImage(width, height); if (offImage != null) offG = offImage.getGraphics(); if (offImage != null) drawBoard(offG); // この後に再描画されることは保証されてないようなので repaint(); } }); addMouseListener(new MouseAdapter() { public void mouseReleased(MouseEvent ev) { if (positionSelecting) { int x = (ev.getX() - boardX) / cellWidth; int y = (ev.getY() - boardY) / cellHeight; if (0 <= x && x < 8 && 0 <= y && y < 8) { synchronized (psLock) { position = new Point(x, y); psLock.notify(); } } } } }); addMouseMotionListener(new MouseMotionListener() { public void mouseMoved(MouseEvent ev) { int newX, newY; newX = (ev.getX() - boardX) / cellWidth; newY = (ev.getY() - boardY) / cellHeight; if (frameX != newX || frameY != newY) { if (positionSelecting) { drawSelectionFrame(frameX, frameY, null); drawSelectionFrame(newX, newY, frameColor); } frameX = newX; frameY = newY; } } public void mouseDragged(MouseEvent ev) { int newX, newY; newX = (ev.getX() - boardX) / cellWidth; newY = (ev.getY() - boardY) / cellHeight; if (positionSelecting && (frameX != newX || frameY != newY)) { if (positionSelecting) { drawSelectionFrame(frameX, frameY, null); drawSelectionFrame(newX, newY, frameColor); } frameX = newX; frameY = newY; } } }); } /* 指定されたマス目に選択用の枠を描く/消す */ private void drawSelectionFrame(int x, int y, Color color) { if (0 <= x && x < 8 && 0 <= y && y < 8) { x = boardX + x * cellWidth + 1; y = boardY + y * cellHeight + 1; Graphics g = getGraphics(); if (color != null) g.setColor(color); else /* 消去 */ g.setColor(boardColor); g.drawRect(x, y, cellWidth - 2, cellHeight - 2); g.drawRect(x + 1, y + 1, cellWidth - 4, cellHeight - 4); } } /* スレッドの同期よくわかんない。頭がこんがらがりそう。 どっか間違ってるかも。 */ /* 枠を表示してマス目を選択させる */ public Point selectPosition() throws InterruptedException { try { position = null; positionSelecting = true; stopRequested = false; drawSelectionFrame(frameX, frameY, frameColor); synchronized (psLock) { while (position == null) { psLock.wait(); if (stopRequested) throw new InterruptedException(); } } } finally { positionSelecting = false; drawSelectionFrame(frameX, frameY, null); } return position; } /* 位置が間違っていることを表示する */ public void warnBadPosition(Point pos) throws InterruptedException { drawSelectionFrame(pos.x, pos.y, badSelectionColor); Thread.sleep(waitUnit * 3); drawSelectionFrame(pos.x, pos.y, null); } /* マス目の選択をしていたらそれをやめる(selectPosition() から InterruptedException が投げられる) */ public void stopPositionSelecting() { if (positionSelecting) { synchronized (psLock) { stopRequested = true; // 本当は Thread#interrupt() が使えればいいんだけど // NN4.07(JDK1.1.5) では動かないようなので psLock.notify(); } } } public void setBoard(OthelloBoard board) { this.board = board; if (offImage != null) drawBoard(offG); repaint(); } public OthelloBoard getBoard() { return board; } /* 現在の盤面に、指定した手を打ち、それをアニメーションで表示する。 時間がかかるので、独立したスレッドから呼ばれることを仮定している。 */ public void put(int x, int y, int color) throws InterruptedException { int[] reverseCount = new int[8]; int dir, maxCount = 0; for (dir = OthelloBoard.DIR_START; dir <= OthelloBoard.DIR_END; dir++) { reverseCount[dir] = board.countReversibleToDirection(x, y, color, dir); if (maxCount < reverseCount[dir]) maxCount = reverseCount[dir]; } drawTurningPiece(offG, x, y, color, 0); repaint(); Thread.sleep(waitUnit * 2); for (int phase = 1; phase <= 5; phase++) { for (dir = OthelloBoard.DIR_START; dir <= OthelloBoard.DIR_END; dir++) { for (int count = reverseCount[dir]; count > 0; count--) { drawTurningPiece(offG, x + count * OthelloBoard.dirTableX[dir], y + count * OthelloBoard.dirTableY[dir], -color, phase); } } repaint(); Thread.sleep(waitUnit); } board.put(x, y, color); Thread.sleep(waitUnit * 2); } public void paint(Graphics g) { if (offImage != null) g.drawImage(offImage, 0, 0, this); } public void update(Graphics g) { paint(g); } /* 盤面を描く */ private void drawBoard(Graphics g) { /* 背景 */ g.setColor(boardColor); g.fillRect(0, 0, width, height); /* 罫線 */ g.setColor(Color.black); for (int i = 0; i <= 8; i++) { g.drawLine(boardX, boardY + i * cellHeight, boardX + boardWidth - 1, boardY + i * cellHeight); g.drawLine(boardX + i * cellWidth, boardY, boardX + i * cellWidth, boardY + boardHeight - 1); } /* コマ */ if (board != null) { for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { drawTurningPiece(g, x, y, board.get(x, y), 0); } } } } /* 回転中の or 普通のコマを描く */ private void drawTurningPiece(Graphics g, int x, int y, int color, int phase) { if (color == OthelloBoard.EMPTY) return; int w = cellWidth - 6, h = cellHeight - 6; x = boardX + x * cellWidth + 3; y = boardY + y * cellHeight + 3; g.setColor(boardColor); g.fillRect(x - 1, y - 1, w + 2, h + 2); if (phase >= 3) { color = -color; phase = 5 - phase; } if (phase > 0) { int newh = h / 3 * (3 - phase); y += (h - newh) / 2; h = newh; } g.setColor(color == OthelloBoard.WHITE ? Color.white : Color.black); g.fillOval(x, y, w, h); g.setColor(Color.black); g.drawOval(x, y, w, h); } }