ミニゲーム?「迷いの森」
今朝は早くに目が覚めたので、珍しくちょっと勉強なんかしたりして、ちょっとだけ早く家を出ました。
おとといの暑さとは打って変わって、秋らしい風の冷たさを感じつつ、気分が良いので鼻歌など鳴らしながら駅への道を歩いていると、ふと思いつきました。
「だから4回なのか...」
このとき思いついたことを元に、ゲーム(というほどのものではないですが)を作ってみました。
タイトルは「迷いの森」。ふた昔くらい前のゲームではよくあった、ループゾーンを脱出するギミックを模したものです。
(クイズ:さて、この鼻歌は何の曲だったのでしょうか。ヒントはコードの中にあるよ!)
※念のため:このプログラムはシミュレートが目的です。通常はこういう場合Queue
Javaコード ※2進リテラルを使っているため、Java1.7でコンパイルする必要があります(コメントの16進リテラルに書き換えれば1.5でもコンパイルできます)
import static javax.swing.ScrollPaneConstants.*; import java.awt.*; import java.awt.event.*; import java.io.*; import javax.swing.*; import javax.swing.text.*; final class LoopTrap extends JFrame { // static final byte target = (byte)0b11_11_11_11; // 0xFF 北北北北 static final byte target = (byte)0b11_01_00_01; // 0xD1 北西南西 PrintWriter out; byte history; void onKeyPressed(KeyEvent e) { final int keyCode = e.getKeyCode(); Direction direction = Direction.asKeyCode(keyCode); out.println("入力: " + KeyEvent.getKeyText(keyCode)); updateHistory(direction); checkHistory(); } void updateHistory(Direction direction) { history = (byte)((history << 2) | direction.ordinal()); } void checkHistory() { out.print("履歴: " + getHistoryString()); out.println(" => " + (history == target ? "脱出した!" : "迷いの森にいる!")); } String getHistoryString() { StringBuilder buffer = new StringBuilder(); Direction[] directions = Direction.values(); for (int i = 3; i >= 0; i--) { int order = (history >> i * 2) & 0x03; buffer.append(directions[order].toCharacter()); } return buffer.toString(); } enum Direction { South('南'), West('西'), East('東'), North('北'); char c; Direction(char c) { this.c = c; } char toCharacter() { return c; } static Direction asKeyCode(int keyCode) { switch (keyCode) { case KeyEvent.VK_DOWN: return South; case KeyEvent.VK_LEFT: return West; case KeyEvent.VK_RIGHT: return East; case KeyEvent.VK_UP: return North; } throw new IllegalArgumentException(String.format("code=%d(%s)", keyCode, KeyEvent.getKeyText(keyCode))); } } // ここから下はGUIのためのコード LoopTrap() { setTitle("迷いの森"); setDefaultCloseOperation(EXIT_ON_CLOSE); final JTextPane text = new JTextPane() { @Override protected void processKeyEvent(KeyEvent e) { if (e.getID() != KeyEvent.KEY_PRESSED) return; try { onKeyPressed(e); } catch (IllegalArgumentException ex) { // out.println(ex); } setCaretPosition(getDocument().getEndPosition().getOffset() - 1); } }; text.setEditable(false); this.history = 0x00; this.out = new PrintWriter(new DocumentWriter(text.getDocument()), true); add(new JScrollPane(text, VERTICAL_SCROLLBAR_ALWAYS, HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER); } static final class DocumentWriter extends Writer { Document doc; DocumentWriter(Document doc) { this.doc = doc; } @Override public void write(char[] cbuf, int off, int len) throws IOException { int offset = doc.getEndPosition().getOffset(); try { doc.insertString(offset, String.valueOf(cbuf, off, len), null); } catch (BadLocationException ex) { throw new RuntimeException(ex); } } @Override public void flush() throws IOException { // ignore } @Override public void close() throws IOException { // ignore } } /** * @param args */ public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { // for 1.6- @Override public void run() { JFrame f = new LoopTrap(); f.setSize(300, 300); f.setLocationRelativeTo(null); f.setVisible(true); } }); } }
解説です。
これは、指定した順番に進まないと先に進めない「迷いの森」を脱出するゲームです。ランダムで順番を生成したり、制限時間を設けたりすれば、よりゲームらしくなるかもしれません。(当然、たいして面白くはならないでしょうが。)
まず、方角を2ビットで定義します。2ビットでは4通りしかできないので、どれかをデフォルトにします*1。
00 : 下・南 (デフォルト) 01 : 左・西 10 : 右・東 11 : 上・北
この値は、enumのDirection#ordinalと対応します。この順番にしたのは、テンキーと方向キーをマッピングすると、(n / 2 - 1)とできるためです。West('西'), North('北'), East('東'), South('南') にしてDirection.values[keyCode - 37]とするのもOK。
後者は定数の実装に依存したロジックなのでやめました。あとはお好みで。
この2ビットを1octet=8ビットに格納すると、4回分が保存できることになります。
履歴 1 0 0 1 0 1 1 1 ~~~~~ ~~~~~ ~~~~~ ~~~~~ 3回前 2回前 1回前 今回 10010111=東西西北
ゲーム自体のロジックは、入力→updateHistory→checkHistoryだけです。
updateHistoryでは、以下のようなビット演算により履歴の更新を行っています。
10010111 (=東西西北) SHIFT<<2 --------------------------------------------- 00000000 00000000 00000010 01011100 AND 00000000 00000000 00000000 11111100 --------------------------------------------- 00000000 00000000 00000000 01011100 OR 00000000 00000000 00000000 00000011 --------------------------------------------- 00000000 00000000 00000000 01011111 (=西西北北)
あとは、historyとtargetが同じかどうかをチェックすれば良いわけです。