/* */ import java.applet.Applet; import java.awt.*; import java.awt.event.*; import java.util.Random; public class R6Ex6b extends Applet { PuzzleMe p = new PuzzleMe(); boolean finished = false; public void init() { addMouseListener(new MyMouseAdapter()); addMouseMotionListener(new MyMouseMotionAdapter()); } public void paint(Graphics g) { if (finished == false) // 上がった後なら描き直さない finished = p.draw(g); } public void update(Graphics g) { // 自動的に全画面を更新させない paint(g); } class MyMouseAdapter extends MouseAdapter { public void mouseReleased(MouseEvent evt) { if (p.released() == true) // 描き直す必要があるとき repaint(); } } class MyMouseMotionAdapter extends MouseMotionAdapter { public void mouseDragged(MouseEvent evt) { if (p.dragged(evt.getX(), evt.getY()) == true) repaint(); } } static class PuzzleMe { int[][] field = new int[4][4]; // フィールドを保持する配列 0が空きコマ int CMfromVx, CMfromVy; // 移動元の配列座標 int CMtoVx, CMtoVy; // 移動先の配列座標 int CMRx, CMRy; // 移動中ブロックの実座標 int CMOffsetX, CMOffsetY; // マウスポインタとブロック原点のオフセット int CMdiffX, CMdiffY; // 移動方向をX,Yの二つで表現 int CMdepth; // 動ける深度(= 可動個数-1) boolean MReleased; // マウスがリリースされたときtureにされるフラグ /* * コンストラクタは,乱数を用いて初期状態を決定した後, * 内部初期化メソッドを呼んで各フィールドを初期化する */ PuzzleMe() { Random rn = new Random(); int pin = rn.nextInt()%16; for (int i = 0; i < 16; i++) field[i/4][i%4] = (pin+=15)%16; init(); } /* * init() 内部初期化メソッド。「移動中」状態をクリアする */ private void init() { CMfromVx = -1; CMfromVy = -1; CMtoVx = -1; CMtoVy = -1; CMRx = 0; CMRy = 0; CMOffsetX = 0; CMOffsetY = 0; CMdiffX = 0; CMdiffY = 0; CMdepth = -1; MReleased = false; } /* * draw() 実際の描画すべてを担当する * 描画後,「上がり」をチェックする * 戻り値: 「上がり」のときtrue */ public boolean draw(Graphics g) { if (MReleased == true) { // マウスリリース後なら更新部分だけを描く // 背景を描く必要はない for (int i = 0; i <= CMdepth+1; i++) // 移動個数+空マス drawPiece(g, 80*(CMfromVx+i*CMdiffX), 80*(CMfromVy+i*CMdiffY), field[CMfromVx+i*CMdiffX][CMfromVy+i*CMdiffY]); init(); // 「移動中」状態をクリア if (checkFinish() == false) // 「上がり」をチェック return false; else { // 「上がり」の処理 Font fn = new Font("Helvetica", Font.BOLD, 40); g.setFont(fn); g.setColor(Color.green); g.fillRect(0,125,400,65); g.setColor(Color.red); g.drawString("Congratulations!",5,170); return true; } } else if (CMtoVx != -1) { // 移動中があれば(Vyについては省略)移動中だけを描く // 移動中は,背景+ブロックを描く必要がある g.setColor(Color.gray); for (int i = 0; i <= CMdepth+1; i++) // 移動個数+空マス g.fillRect(80*(CMfromVx+i*CMdiffX),80*(CMfromVy+i*CMdiffY), 80,80); for (int i = 0; i <= CMdepth; i++) drawPiece(g, CMRx+80*i*CMdiffX,CMRy+80*i*CMdiffY, field[CMfromVx+i*CMdiffX][CMfromVy+i*CMdiffY]); return false; } // 以下,すべての描画が必要な場合 g.setColor(Color.gray); // スクリーンを用意 g.fillRect(0,0,320,320); for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) drawPiece(g, 80*i,80*j, field[i][j]); return false; } /* * drawPiece() 1つ1つのブロックを描く * 描くブロックの実座標,コマ情報を受け取る */ private void drawPiece(Graphics g, int rx, int ry, int num) { if (num == 0) { // 空マスなら背景色で塗りつぶして終わり g.setColor(Color.gray); g.fillRect(rx,ry, 80,80); return; } Font fn = new Font("Helvetica", Font.BOLD, 50); g.setColor(Color.white); g.fillRoundRect(rx, ry, 80,80, 10,10); g.setColor(Color.lightGray); g.fillRect(rx+5,ry+5, 70,70); g.setColor(Color.black); g.setFont(fn); if (num < 10) // 描く数字が一桁のときは中央に数字 g.drawString(""+num, rx+26,ry+60); else // 二桁のとき位置変更 g.drawString(""+num,rx+13,ry+60); } /* * checkFinish() 「上がりのチェック」 * 戻り値: 「上がり」のときtrue */ private boolean checkFinish() { for (int i = 0; i < 16; i++) if (field[i%4][i/4] != (i+1)%16) return false; return true; } /* * dragged() マウスがドラッグされたときの処理 * ポインタの座標を受け取る * 戻り値: 描き直す必要があるときtrue */ public boolean dragged(int rx, int ry) { int vx = rx/80, vy = ry/80; if (rx < 0 || ry < 0 || vx >= field.length || vy >= field.length) // 範囲外なら何もしない // 「丸め」があるため,左と上は実座標で判定する return false; if (CMtoVx == -1) { // 現在「移動中」でなければ「移動中」にする CMdepth = getMovableDepth(vx, vy); if (CMdepth == -1) return false; // 移動可能でなければ何もしない CMfromVx = vx; CMfromVy = vy; // 移動元 CMtoVx = CMfromVx+CMdiffX; CMtoVy = CMfromVy+CMdiffY; CMRx = 80*vx; CMRy = 80*vy; CMOffsetX = rx - CMRx; CMOffsetY = ry - CMRy; return false; } else { // 「移動中」なら可動範囲を制限して再描画へ if (CMdiffX != 0) { // もし横方向に移動可なら CMRx = rx - CMOffsetX; if (CMRx < Math.min(80*CMfromVx, 80*CMtoVx)) CMRx = Math.min(80*CMfromVx, 80*CMtoVx); if (CMRx > Math.max(80*CMfromVx, 80*CMtoVx)) CMRx = Math.max(80*CMfromVx, 80*CMtoVx); } else { CMRy = ry - CMOffsetY; // 縦方向なら if (CMRy < Math.min(80*CMfromVy, 80*CMtoVy)) CMRy = Math.min(80*CMfromVy, 80*CMtoVy); if (CMRy > Math.max(80*CMfromVy, 80*CMtoVy)) CMRy = Math.max(80*CMfromVy, 80*CMtoVy); } return true; } } /* * released() マウスリリース時の処理 * ポインタの位置ではなく,ブロックの位置で処理を分ける * 戻り値: 再描画の必要があるときtrue */ public boolean released() { if (CMtoVx == -1) // 「移動中」でなかったら何もしない return false; if (!(80*CMfromVx == CMRx && 80*CMfromVy == CMRy)) // ぴったり元の座標に戻したときは元通り // 少しでも動いていればコマ情報を交換 exchange(); MReleased = true; // 「リリース後」のフラグをセット return true; } /* * getMovableDir() コマ[vx][vy]が移動できる方向を調べる * 戻り値: x正の方向から反時計回りに1〜4,移動不可なら0 */ private int getMovableDir(int vx, int vy) { if (field[vx][vy] == 0) return 0; if (vx > 0 && field[vx-1][vy] == 0) return 3; if (vx < field.length-1 && field[vx+1][vy] == 0) return 1; if (vy > 0 && field[vx][vy-1] == 0) return 2; if (vy < field.length-1 && field[vx][vy+1] == 0) return 4; return 0; } /* * getMovableDepth() 移動できる深さを得る */ private int getMovableDepth(int vx, int vy) { for (int d = 0; vx+d < field.length-1; d++) if (getMovableDir(vx+d, vy) == 1) { CMdiffX = +1; CMdiffY = 0; return d; } for (int d = 0; vy-d > 0; d++) if (getMovableDir(vx, vy-d) == 2) { CMdiffX = 0; CMdiffY = -1; return d; } for (int d = 0; vx-d > 0; d++) if (getMovableDir(vx-d, vy) == 3) { CMdiffX = -1; CMdiffY = 0; return d; } for (int d = 0; vy+d < field.length-1; d++) if (getMovableDir(vx, vy+d) == 4) { CMdiffX = 0; CMdiffY = +1; return d; } return -1; } /* * exchange() コマ情報を交換する * 現在の「移動中」の情報を使って「移動後」の状態にする */ private void exchange() { int temp[] = new int[field.length]; for (int i = 0; i <= CMdepth+1; i++) temp[i] = field[CMfromVx+i*CMdiffX][CMfromVy+i*CMdiffY]; for (int i = 1; i <= CMdepth+1; i++) field[CMfromVx+i*CMdiffX][CMfromVy+i*CMdiffY] = temp[i-1]; field[CMfromVx][CMfromVy] = 0; // 移動元は必ず空マスになる } } }