challenge ラングトンのアリの描画

ラングトンのアリを描画してください。ラングトンのアリは、以下のような動きをする、セル・オートマトンです。(Wikipediaより引用)
- 黒いマスにアリがいた場合、90°右に方向転換し、そのマスの色を反転させ、1マス前進する。
- 白いマスにアリがいた場合、90°左に方向転換し、そのマスの色を反転させ、1マス前進する。
詳しくはWikipedia等で調べるか、参考ページに拙作のデモがありますのでご覧下さい。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="ja">
<head>
       <meta http-equiv="content-type" content="text/html;charset=utf-8">
       <meta http-equiv="content-script-type" content="text/javascript">
       <meta http-equiv="content-style-type" content="text/css">
       <title>ラングトンの蟻</title>
<style type="text/css">
#canvas{
       border: 1px solid #999;
       width: 300px;
       height: 300px;
}
#canvas div{
       width: 3px;
       height: 3px;
       float: left;
}
</style>
<script>
var earth = [];
var WORLD_SIZE = 100;
var lang_ant;

function Ant(){}
Ant.prototype = {
    age : 0,
    ageDisplay : undefined,
    id : undefined,
    speed: 200,
    direction: [0,-1],//向き。x y軸。最初は上に動く
    position: [60,40],//初期位置
    world: [],
    start: function(){
        var self = this;
        this.id = setInterval(function(){self.move()}, 1000/this.speed);
    },
    move: function(){
        this.ageDisplay.innerHTML = ++this.age;
        this.moveNextCell();
        var cell = this.getCellInfo();
        var color = cell.getAndToggleColor();
        this.setNextDirection(color);
    },
    moveNextCell: function(){
        this.position[0] += this.direction[0];
        this.position[1] += this.direction[1];
        if(this.position[0] < 0 || this.position[1] < 0 ||
           this.position[0] >= WORLD_SIZE || this.position[1] >= WORLD_SIZE){
            clearInterval(this.id);
            this.die();
        }
    },
    getCellInfo: function(){
        var idx = this.position[0] + this.position[1] * WORLD_SIZE;
        return this.world[idx];
    },
    setNextDirection: function(bool){//colorがfalse(白)なら右へ、true(黒)なら左へ転回
        if(bool){//黒
            var tmp = this.direction[0];
            this.direction[0] = this.direction[1];
            this.direction[1] = -tmp;
        }
        else{//白
            var tmp = this.direction[0];
            this.direction[0] = -this.direction[1];
            this.direction[1] = tmp;
        }
    },
    die: function(){
        alert('Langton\'s ant is dead.');
        throw true;
    }
};

function Cell(elm){
    this.elm = elm;
}
Cell.prototype = {
    elm: undefined,
    color: false, //colorは2値なのでbooleanで表す
    colorList: ['#FFF','#000'],
    getAndToggleColor: function(){
        this.color = !this.color;
        var i = this.color ? 1 : 0;
        this.elm.style.backgroundColor = this.colorList[i];
        return !this.color;
    }
}
window.onload = function(){
    var canvas = document.getElementById('canvas');
    var div = '<div></div>';
    var inner_canvas = "";
    for(var i=0; i< WORLD_SIZE*WORLD_SIZE; i++){
        inner_canvas += div;
    }
    canvas.innerHTML = inner_canvas;
    
    var cells = canvas.childNodes;
    for(var i=0; i<cells.length; i++){//世界の誕生
        earth[i] = new Cell(cells[i]);
    }
    lang_ant = new Ant();//蟻の誕生
    lang_ant.world = earth;//地球に降り立つ
    lang_ant.ageDisplay = document.getElementById('step');
    
    document.getElementById('run').disabled = false;
}
</script>
</head>
<body>
<p><input type="button" value="run" onclick="lang_ant.start();this.disabled=true;" id="run" disabled="disabled"> <span id="step"></span>
 <input type="button" value="stop &amp; refresh" onclick="location.reload();"></p>
<div id="canvas"></div>

Posted feedbacks - Flatten

Nested Hidden

C++でWTL使いました。GDIで描画、Windowsのタイマーメッセージ (WM_TIMER) を使って回数を進めるようにしています。高速に結果を見たかったので、1度のタイマイベントで複数回進めるようにしています。

ありを進める部分はなんの工夫もないですが。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#define WINVER 0x0400
#define _WIN32_WINDOWS 0
#define _WIN32_WINNT 0
#define _WIN32_IE 0x0300 // IE3以上を指定しないとWTLがコンパイルエラーを起こす。

#define WIN32_LEAN_AND_MEAN
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _WTL_NO_AUTOMATIC_NAMESPACE

#include <cassert>
#include <sstream>

#include <tchar.h> // _tWinMain

#include <windows.h>
#include <atlbase.h> // ATL共通ヘッダ
#include <atlwin.h> // CWindowImplほか
#include <atlcrack.h> // MSG_*
#include <atlapp.h> // <atlmisc.h>
#include <atlmisc.h> // CPoint

const int ID_TIMER = 1;

enum Direction {Up, Right, Down, Left};

class MainWindow :
    public ATL::CWindowImpl<MainWindow, ATL::CWindow, ATL::CFrameWinTraits>
{
public:
    MainWindow() : field(), x(100), y(100), direction(Up), step() {} // このxとyの値が初期位置

    // ウィンドウクラス名を登録
    DECLARE_WND_CLASS(TEXT("Langton's ant"));

    // メッセージマップ
    BEGIN_MSG_MAP(MainWindow)
        MSG_WM_TIMER(OnTimer)
        MSG_WM_PAINT(OnPaint)
        MSG_WM_CREATE(OnCreate)
        MSG_WM_DESTROY(OnDestroy)
    END_MSG_MAP()
private:
    void OnTimer(UINT /*idEvent*/)
    {
        for (int i = 0; i < 16; ++i) // 1度のタイマイベントで複数回進める。
        {
            NextStep();
            ++step;
        }
        std::basic_ostringstream<TCHAR> oss;
        oss << TEXT("Langton's ant - step: ") << step;
        SetWindowText(oss.str().c_str());
        Invalidate(FALSE);
    }

    static const int TILE_WIDTH = 2, TILE_HEIGHT = 2;
    static const int X = 200, Y = 200;

    void OnPaint(HDC)
    {
        WTL::CPaintDC dc(m_hWnd);
        for (int i = 0; i < Y; ++i)
        {
            for (int j = 0; j < X; ++j)
            {
                dc.FillSolidRect(j * TILE_WIDTH, i * TILE_HEIGHT,
                    TILE_WIDTH, TILE_HEIGHT,
                    field[i][j] ? RGB(255, 255, 255)
                                : RGB(0, 0, 0));
            }
        }
    }

    LRESULT OnCreate(CREATESTRUCT const* pcs)
    {
        RECT rc = {0, 0, TILE_WIDTH * X, TILE_HEIGHT * Y};
        AdjustWindowRectEx(&rc, pcs->style, FALSE, pcs->dwExStyle);
        SetWindowPos(0, &rc, SWP_NOMOVE | SWP_NOZORDER);
        SetTimer(ID_TIMER, 10, 0);
        return 0;
    }

    void OnDestroy()
    {
        PostQuitMessage(0);
    }

    void NextStep()
    {
        if (field[y][x]) // trueが白、falseが黒としている
        {
            if (direction == Up)
            {
                direction = Left;
            }
            else
            {
                --direction;
            }
        }
        else
        {
            if (direction == Left)
            {
                direction = Up;
            }
            else
            {
                ++direction;
            }
        }
        field[y][x] = !field[y][x];
        switch (direction)
        {
        case Up:
            y--;
            if (y == -1)
            {
                y = Y - 1;
            }
            break;
        case Right:
            x++;
            if (x == X)
            {
                x = 0;
            }
            break;
        case Down:
            y++;
            if (y == Y)
            {
                y = 0;
            }
            break;
        case Left:
            x--;
            if (x == -1)
            {
                x = X - 1;
            }
            break;
        default:
            assert(0);
        }
    }

    bool field[Y][X];
    int y;
    int x;
    int direction; //Direction(列挙)型では++と--できない(できるようにするのが面倒)。
    int step;
};

int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int cmdShow)
{
    MainWindow wnd;
    wnd.Create(NULL, ATL::CWindow::rcDefault, TEXT("Langton's ant"),
        WS_CAPTION | WS_SYSMENU | WS_DLGFRAME | WS_MINIMIZEBOX
        | WS_OVERLAPPED | WS_SYSMENU, WS_EX_APPWINDOW);
    wnd.ShowWindow(cmdShow);
    wnd.UpdateWindow();

    WTL::CMessageLoop msgLoop;
    return msgLoop.Run();
}

C#でビットマップにごりごり描画します。 フレームスキップして速度を稼いでいます。

今回一番はまったのは、Color.FromArgb(255, 0, 0, 0) が Color.Black とイコールではないってこと。ColorはARGB以外の情報を持っているので、ToArgb()で比較するように、とMSDNにある。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace LangtonsAnt {
    class Ant {
        Point pos;
        int direction;
        public int X { get { return pos.X; } }
        public int Y { get { return pos.Y; } }

        public Ant(int x, int y) {
            pos.X = x;
            pos.Y = y;
            direction = 0;
        }
        public void Advance(bool IsBlack) {
            if (IsBlack) {
                direction = (direction + 1) % 4;
            } else {
                direction = (direction + 3) % 4;
            }
            switch (direction) {
                case 0: pos.Y--; break; //北
                case 1: pos.X++; break; //東
                case 2: pos.Y++; break; //南
                case 3: pos.X--; break; //西
            }
        }
    }
    class Form1 : Form {
        Bitmap bitmap;                  //世界=ビットマップ
        List<Ant> ants;                 //アリたち
        Timer timer;                    //タイマー
        long step_count;                //ステップ数
        Color Black = Color.FromArgb(255, 0, 0, 0);
        Color White = Color.FromArgb(255, 255, 255, 255);
        public Form1() {
            //マップの初期化
            this.Width = 300; this.Height = 200;
            bitmap = new Bitmap(300, 200, Graphics.FromHwnd(this.Handle));
            //100,100を中心に黒点を打つ
            Random r = new Random();
            for (int i = 0 ; i < 100 ; i++) {
                int x = r.Next(40) + bitmap.Width / 2 - 20;
                int y = r.Next(40) + bitmap.Height / 2 - 20;
                bitmap.SetPixel(x, y, Black);
            }
            //アリの初期化
            ants = new List<Ant>();
            ants.Add(new Ant(r.Next(20) + bitmap.Width / 2 - 10, r.Next(20) + bitmap.Height / 2 - 10));
            ants.Add(new Ant(r.Next(20) + bitmap.Width / 2 - 10, r.Next(20) + bitmap.Height / 2 - 10));
            ants.Add(new Ant(r.Next(20) + bitmap.Width / 2 - 10, r.Next(20) + bitmap.Height / 2 - 10));
            //タイマー初期化
            step_count = 0;
            timer = new Timer();
            timer.Interval = 300;       //300msに1度再描画
            timer.Tick += new EventHandler(timer_Tick);
            timer.Start();
        }
        void timer_Tick(object sender, EventArgs e) {
            //高速化のため1描画につき150周回す
            for (int step = 0 ; step < 150 ; step++) {
                foreach (Ant ant in ants) {
                    int x = (ant.X + bitmap.Width) % bitmap.Width;
                    if (x < 0) x += bitmap.Width;
                    int y = (ant.Y + bitmap.Height) % bitmap.Height;
                    if (y < 0) y += bitmap.Height;
                    Color color = bitmap.GetPixel(x, y);
                    if (color == Black) {
                        ant.Advance(true);
                        bitmap.SetPixel(x, y, White);
                    } else {
                        ant.Advance(false);
                        bitmap.SetPixel(x, y, Black);
                    }
                }
            }
            Refresh();
            step_count += 150;
            Text = step_count.ToString();
        }
        protected override void OnPaint(PaintEventArgs e) {
            base.OnPaint(e);
            e.Graphics.DrawImage(bitmap, ClientRectangle);
        }
        protected override void OnClosed(EventArgs e) {
            timer.Stop(); timer = null;
            base.OnClosed(e);
        }
    }
    static class Program {
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

ブラウザ向けに結果を出力します。

パラメータはフィールドの「幅」と「高さ」で、以下のように使用します。
(アリはフィールドの中心に落ちます)

lang_ant(300, 300);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
function lang_ant($w, $h)
{
    $world = array_fill(0, $h, array_fill(0, $w, 1));
    $x = (int)($w/2);
    $y = (int)($h/2);
    $dct = 0; /* 0:N 1:E 2:S 3:W */
    $stp_ant = array('$x++;', '$y++;', '$x--;', '$y--;');
    $chg_dct = array('$dct = (((++$dct)%4)+2)%4;', '$dct = (++$dct)%4;');
    $print_world = array('0', '&nbsp;');

    for ( $i = 0; $i < 20000; $i++) {
        eval($chg_dct[$world[$y][$x]]);
        eval($stp_ant[$dct]);
        if ($x<0 || $w<=$x || $y<0 || $h<=$y) {
            break;
        }
        $world[$y][$x] = (++$world[$y][$x])%2;
    }
    foreach ( $world as $cels) {
        foreach ($cels as $cel) {
            echo $print_world[$cel];
        }
        echo "<br />";
    }
}
?>

Squeak Smalltalk で。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Display fillWhite; restoreAfter: [
    | pen black white |
    pen := Pen new.
    black := pen color: Color black; color.
    white := Color white.
    [Sensor leftShiftDown] whileFalse: [
        (Display colorAt: pen location) caseOf: {
            [black] -> [pen color: white; turn: -90].
            [white] -> [pen color: black; turn: 90]} otherwise: [].
        pen down; go: 0; up; go: 1.
        pen instVarNamed: #direction put: pen direction \\ 360]
]

JPanelに直接描画してみました。 今時のJVMだと結構早い描画ができると思います。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Sample276 extends JFrame {
    private final LangtonsAntPanel mainPanel_;
    private final JButton startButton_ = new JButton(new AbstractAction("start") {
        public void actionPerformed(ActionEvent e) {
            timer_.start();
            changeMode(true);
        }
    });
    private final JButton stopButton_ = new JButton(new AbstractAction("stop") {
        public void actionPerformed(ActionEvent e) {
            timer_.stop();
            changeMode(false);
        }
    });
    private final JButton resetButton_ = new JButton(new AbstractAction("reset") {
        public void actionPerformed(ActionEvent e) {
            timer_.stop();
            changeMode(false);

            mainPanel_.reset();
            mainPanel_.repaint();
        }
    });
    private final JButton endButton_ = new JButton(new AbstractAction("end") {
        public void actionPerformed(ActionEvent e) {
            Sample276.this.dispose();
        }
    });

    private final Timer timer_;

    public Sample276(int size) {
        super("Langton's ant.");
        this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        mainPanel_ = new LangtonsAntPanel(size);

        JPanel panel = new JPanel(new BorderLayout(3, 3));
        panel.add(mainPanel_, BorderLayout.CENTER);
        panel.add(createButtonPanel(), BorderLayout.SOUTH);
        this.add(panel);
        this.pack();

        timer_ = new Timer(5, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                boolean res = mainPanel_.tick();
                mainPanel_.repaint();
                if (!res) {
                    timer_.stop();
                    changeMode(false);
                    JOptionPane.showMessageDialog(Sample276.this, "Langton's ant is dead.");
                }
            }
        });
    }

    private JComponent createButtonPanel() {
        JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 3, 3));
        panel.add(startButton_);
        panel.add(stopButton_);
        panel.add(resetButton_);
        panel.add(endButton_);
        return panel;
    }

    private void changeMode(boolean isMoving) {
        startButton_.setEnabled(!isMoving);
        stopButton_.setEnabled(isMoving);
    }


    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                try{
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch(Exception e) {
                    throw new InternalError(e.toString());
                }
                Sample276 frame = new Sample276(100);
                frame.setVisible(true);
            };
        });
    }
}

class LangtonsAntPanel extends JPanel {
    public enum Direction {
        N(0, -1),
        E(1, 0),
        S(0, 1),
        W(-1, 0);

        public final int dx;
        public final int dy;
        private Direction(int x, int y) {
            dx = x;
            dy = y;
        }

        public Direction turn(boolean isRight) {
            int dd = this.ordinal() + (isRight? 1: 3);
            return Direction.values()[dd % 4];
        }
    }

    public class Ant {
        private int x_;
        private int y_;
        private Direction d_ = Direction.N;
        private int counter_ = 0;

        public Ant(int x, int y) {
            x_ = x;
            y_ = y;
        }

        public int getX() {
            return x_;
        }
        public int getY() {
            return y_;
        }

        public int getCounter() {
            return counter_;
        }

        public void next(boolean isBlack) {
            d_ = d_.turn(isBlack);
            x_ += d_.dx;
            y_ += d_.dy;
            counter_++;
        }
    }

    private static final int ANT_SIZE = 3;

    private final int size_;
    private final boolean[][] land_;
    private Ant ant_;

    public LangtonsAntPanel(int size) {
        super(true);

        size_ = size;
        land_ = new boolean[size][];
        for (int index = 0; index < land_.length; index++) {
            land_[index] = new boolean[size];
        }
        ant_ = new Ant(size / 2, size / 2);

        setBackground(Color.WHITE);
        setForeground(Color.BLACK);
        setSize(size * ANT_SIZE, size * ANT_SIZE);
        setPreferredSize(getSize());
    }

    public boolean tick() {
        int y = ant_.getY();
        int x = ant_.getX();
        boolean b = land_[y][x];
        land_[y][x] = !b;
        ant_.next(b);
        
        y = ant_.getY();
        if (y < 0 || size_ <= y) {
            return false;
        }
        x = ant_.getX();
        if (x < 0 || size_ <= x) {
            return false;
        }
        return true;
    }

    public void reset() {
        for (int y = 0; y < land_.length; y++) {
            for (int x = 0; x < land_[y].length; x++) {
                land_[y][x] = false;
            }
        }
        ant_ = new Ant(size_ / 2, size_ / 2);
    }

    @Override
    protected void paintComponent(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        g2d.setBackground(Color.WHITE);
        g2d.setColor(Color.BLACK);

        g2d.clearRect(0, 0, this.getWidth(), this.getHeight());
        for (int y = 0; y < land_.length; y++) {
            for (int x = 0; x < land_[y].length; x++) {
                if (land_[y][x]) {
                    g2d.fillRoundRect(x * ANT_SIZE, y * ANT_SIZE, ANT_SIZE, ANT_SIZE, 1, 1);
                }
            }
        }
        g2d.drawString(String.format("turn: %d", ant_.getCounter()), 2, 12);
    }
}

アリ1匹につき1スレッドで。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;

namespace doukaku276
{
    class Program
    {
        const int ANT_NUM = 10;
        const int FIELD_WIDTH = 100;
        const int FIELD_HEIGHT = 100;
        static Random rnd = new Random();

        static void Main(string[] args)
        {
            new FormMain().ShowDialog();
        }

        class Ant
        {
            int x, y;
            int direction;//0:↑ 1:→ 2:↓ 3:←
            Color color;
            public Ant()
            {
                x = rnd.Next(FIELD_WIDTH);
                y = rnd.Next(FIELD_HEIGHT);
                direction = rnd.Next(4);
                color = Color.FromArgb(rnd.Next(254), rnd.Next(255), rnd.Next(255));
            }
            public void step(Bitmap bmp)
            {
                bool b = bmp.GetPixel(x, y).R != 255; // 黒? R=255を白とする
                direction = (direction + (b ? 1 : 3)) % 4; // 回転
                bmp.SetPixel(x, y, b ? Color.White : color); // 色反転
                // 前進
                switch (direction)
                {
                case 0: y--; break;
                case 1: x++; break;
                case 2: y++; break;
                case 3: x--; break;
                }
                if (x < 0) x += FIELD_WIDTH;
                if (x >= FIELD_WIDTH) x -= FIELD_WIDTH;
                if (y < 0) y += FIELD_HEIGHT;
                if (y >= FIELD_HEIGHT) y -= FIELD_HEIGHT;
            }
        }
        class FormMain : Form
        {
            Bitmap bmp = new Bitmap(FIELD_WIDTH, FIELD_HEIGHT);
            List<Thread> ants = new List<Thread>();
            public FormMain()
            {
                lock (bmp)
                {
                    for (int y = 0; y < FIELD_HEIGHT; y++)
                        for (int x = 0; x < FIELD_WIDTH; x++)
                            bmp.SetPixel(x, y, Color.White);
                }
                this.Size = new Size(FIELD_WIDTH * 3 + 100, FIELD_HEIGHT * 3 + 100);
                this.CenterToScreen();
                this.DoubleBuffered = true;
            }
            void update()
            {
                Ant ant = new Ant();
                while (true)
                {
                    lock (bmp)
                    {
                        ant.step(bmp);
                    }
                    this.Invalidate();
                    Thread.Sleep(1);
                }
            }
            protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
            {
                foreach(var t in ants) t.Abort();
            }
            protected override void OnPaint(PaintEventArgs e)
            {
                //base.OnPaint(e);
                Graphics g = e.Graphics;
                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
                lock (bmp)
                {
                    g.DrawImage(bmp, 30, 30, FIELD_WIDTH * 3, FIELD_HEIGHT * 3);
                }
                g.DrawString(string.Format("ants:{0}", ants.Count), this.Font, Brushes.Black, 40, FIELD_HEIGHT * 3);
            }
            protected override void OnClick(EventArgs e)
            {
                var thread = new Thread(new ThreadStart(update));
                thread.Start();
                ants.Add(thread);
            }
        }
    }
}

Perl/Tk でごりごり書いてみました。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use strict;
use warnings;

use Tk;
use Tk::MsgBox;
use utf8;

my $w = shift || 100;
my $h = shift || $w;

my $pxsize  = 4;
my $init_pattern_subs = create_pattern_subs();
my $pattern = shift;
my $pattarn_sub = $pattern && exists $init_pattern_subs->{$pattern}
? $init_pattern_subs->{$pattern} 
: sub { 0 };

my $mw = Tk::MainWindow->new(-title => 'ラングトンの蟻');
my $control_frame = $mw->Frame;
$control_frame->pack(
  -side => 'bottom',
  -anchor => 's',
  -fill => 'x',
);
my $drawing_frame = $mw->Frame;
$drawing_frame->pack(
  -side => 'top',
  -anchor => 'n',
  -fill => 'both',
);
my $canvas = $drawing_frame->Canvas(
  -width => $w * $pxsize, -height => $h * $pxsize,
  -background => 'white',
);
my @field;
for my $x ( 0 .. $w-1 ) {
  for my $y ( 0 .. $h-1 ) {
    $field[$x][$y] = $canvas->createRectangle(
      (map{$_ * $pxsize} $x,$y,$x+1,$y+1),
      -fill => 'white'
    );
  }
}

$canvas->pack(-fill => 'both');
my $start_button = $control_frame->Button(
  -text => 'START',
  -command => \&start_proc,
  -state => 'normal',
);
$start_button->pack(-anchor => 'w', -side => 'left');
my $stop_button = $control_frame->Button(
  -text => 'STOP',
  -command => \&stop_proc,
  -state => 'disabled',
);
$stop_button->pack(-anchor => 'w', -side => 'left', -padx => 20);
my $stat = step_label(0,0,0);
my $stat_label = $control_frame->Label(
  -textvariable => \$stat
);
$stat_label->pack(-anchor => 'w', -side => 'left', -padx => 30);

Tk->MainLoop;

sub create_pattern_subs
{
  +{
    ichimatsu => sub { (($_[0]/2) ^ ($_[1]/2)) & 0x1; },
    lines => sub { ($_[0]/12) & ($_[1]/2) & 0x1; },
    random => sub { !int rand 3 },
  };
}

sub step_label
{
  sprintf '%#8d steps (%#3d, %#3d)', @_;
}

{
  my ($count,$x,$y,$d,$timer);

  sub start_proc
  {
    $stop_button->configure(-state => 'normal');
    $start_button->configure(-state => 'disabled');

    $count = 0;
    $x = int(rand($w/2) + $w/4);
    $y = int(rand($h/2) + $h/4);
    $d = int(rand(4));
    for my $x_ ( 0 .. $w-1 ) {
      for my $y_ ( 0 .. $h-1 ) {
        $canvas->itemconfigure($field[$x_][$y_],
          -fill => $pattarn_sub->($x_,$y_) ? 'black' : 'white');
      }
    }

    $timer = $mw->repeat(10, \&next_step);
  }

  sub stop_proc
  {
    $stop_button->configure(-state => 'disabled');
    $start_button->configure(-state => 'normal');

    $mw->afterCancel($timer);
  }

  sub next_step
  {
    my $bit = $canvas->itemcget($field[$x][$y], '-fill') ne 'white';
    $canvas->itemconfigure($field[$x][$y], -fill => ($bit ? 'white' : 'black'));

    $d = ($bit ? ++$d : --$d) % 4;
    if    ( $d == 0 ) { --$x; }
    elsif ( $d == 1 ) { ++$y; }
    elsif ( $d == 2 ) { ++$x; }
    elsif ( $d == 3 ) { --$y; }

    if ( $x < 0 || $x >= $w || $y < 0 || $y >= $h ) {
      stop_proc;
      my $d = $mw->MsgBox(-title => 'END', -message => 'しゅーりょー', -type => 'ok');
      $d->Show;
      return;
    }

    $stat = step_label( ++$count, $x, $y);
  }

}

おお。Perl/Tkをあまり使ったことがなかったので、Perlでの投稿を期待して待ってました。


ActionScriptで作成してみました。 30Frame/secでは遅かったので、1Frameに10Step進ませています。

ブラウザ上で確認できるようにwonderflにもおいておきます。

http://wonderfl.net/code/519de9f580e291a6309e2e8df3d5e8319a647433

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package {
    // Yet another implimentation of Langton's ant
    // This is made for "どう書く?org", http://ja.doukaku.org/276/
    // This code is written by xsd.
    import flash.display.Sprite;
    import flash.display.BitmapData;
    import flash.display.Bitmap;
    import flash.geom.Rectangle;
    import flash.events.Event;
    import flash.text.TextField;
    import flash.text.TextFormat;

    [SWF(width="465", height="465", backgroundColor="0xFFFFFF", frameRate="30")]
    public class LangtonsAnt extends Sprite {
    private const SCREEN_WIDTH:int = 465;
    private const SCREEN_HEIGHT:int = 465;
    private const CELL_SIZE:int = 4;
    private const STEP:int = 10;

    private var bm:BitmapData;
    private var bmp:Bitmap;
    private var tf:TextField;
    private var xAnt:int = 200;
    private var yAnt:int = 200;
    private var vx:int = 1;
    private var vy:int = 0;
    private var count:int = 0;
        
    public function LangtonsAnt():void {
            bm = new BitmapData(SCREEN_WIDTH, SCREEN_HEIGHT, false, 0xFFFFFF);
            bmp = new Bitmap(bm);
            this.addChild(bmp);

            tf = new TextField();
            tf.defaultTextFormat = new TextFormat("_Serif", 24, 0, true);
            tf.x = 0; tf.y = SCREEN_HEIGHT - 32; tf.width = SCREEN_HEIGHT; tf.height = 32;
            tf.selectable = false;
            this.addChild(tf);
            this.addEventListener(Event.ENTER_FRAME, update);
        }
        
        public function update(event:Event):void {
            bm.lock();
            var t:int, i:int;
    
            for (i = 0; i < STEP; i++) {
                xAnt += vx * CELL_SIZE;
                yAnt += vy * CELL_SIZE;
                var a:int = bm.getPixel(xAnt, yAnt);
                if (a == 0) {
                    bm.fillRect(new Rectangle(xAnt, yAnt, CELL_SIZE, CELL_SIZE), 0xFFFFFF);
                    t = vx; vx = vy; vy = -t;
                } else {
                    bm.fillRect(new Rectangle(xAnt, yAnt, CELL_SIZE, CELL_SIZE), 0x000000);
            t = vy; vy = vx; vx = -t;
                }
                bm.unlock();
                if (xAnt <= CELL_SIZE || xAnt >= SCREEN_WIDTH - CELL_SIZE ||
                    yAnt <= CELL_SIZE || yAnt >= SCREEN_HEIGHT - CELL_SIZE) {
                    this.removeEventListener(Event.ENTER_FRAME, update);
                    break;
                }
                count++;
                tf.text = "Step: "+count;
            }
        }
    }
}

Ruby でシンプルに書いてみました。

入力された数字の分だけどんどん世代を進めます。qが入力されたら終わりです。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#
#  World.rb
#  iLangAnt
#
#  Created by 石井 大海 on 09/07/17.
#  Copyright (c) 2009 __MyCompanyName__. All rights reserved.
#

class Ant
  attr_reader :world, :x, :y, :generation

  def pos()
    return [x,y]
  end

  def initialize(world=World.new, pos_x=40, pos_y=60)
    @world = world
    @x = pos_x
    @y = pos_y
    @direction = [0, -1]
    @generation = 0
  end

  def position()
    @world.at(@x, @y)
  end

  def step()
    @x += @direction[0]
    @y += @direction[1]
    return nil unless position()
    position.toggle()
    if @world.black?(@x,@y)
      @direction = [-@direction[1], @direction[0]]
    else
      @direction = [@direction[1], -@direction[0]]
    end
    @generation += 1
  end

end

class World
  attr_reader :height, :width

  class <<self
    def define_block_delegate(mtd)
      define_method(mtd) {|x,y|
        at(x,y).__send__(mtd)
      }
    end
  end

  def initialize(height=100, width=100, wrap=true)
    @world = Array.new(height){ Array.new(width){ Block.new } }
    @height, @width = height, width
    @wrap = wrap
  end

  def at(x, y)
    unless (0...@height).include?(y) && (0...@width).include?(x)
      if @wrap
        x, y = x % width, y % height
      else
        return nil
      end
    end
    @world[y][x]
  end

  define_block_delegate :white?
  define_block_delegate :black?
  define_block_delegate :color

  def inspect()
    @world.map{|r| r.map(&:inspect).join("")}.join("\n")
  end

end

class Block
  attr_reader :state
  alias color state

  WHITE = true
  BLACK = false

  def initialize(state=WHITE)
    @state = state
  end

  def toggle
    @state = !@state
  end

  def set_white
    @state = WHITE
  end

  def set_black
    @state = BLACK
  end

  def black?
    @state
  end

  def white?
    !@state
  end

  def inspect()
    case @state
    when WHITE
      " □ "
    when BLACK
      " ■ "
    end
  end

end

if $0 == __FILE__
  w, h ,= ARGV
  w ||= h ||= 20
  wd = World.new(w, h)
  ant = Ant.new(wd, w*6/10, h*4/10)
  while (print"> ";gets)
    break if $_ =~ /^q/i
    count = 1 unless (count = $_.to_i) > 0
    count.times {
      ant.step
      p ant.generation
      p wd
    }
  end
end

自分のマシンは CPUが Pentium 4 (2.66GHz)、メモリが 512MBとリ
ソースが貧弱なためか、うまく動作してくれませんでした。

なので、 #9331を参考に table要素を利用したものへと書き直して
みました。

念のため、以下のブラウザーで動作を確認してあります。

  Firefox 2.0.0.6
  Internet Explorer 6.0.29
  Google Chrome 1.0.154.48
  Opera 9.23
  Safari 3.1.2

Firefox, Google Chrome, Safariは CPUをほとんど食わないのです
が、 Internet Explorerと Operaはともに CPU使用率が高く、動作
も遅かったです。(実装の違いですかね? )
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<html>
<head>
    <style type="text/css">
        .C_0 { background-color : #ffffff; height : 3px; width : 3px; }
        .C_1 { background-color : #000000; height : 3px; width : 3px; }
    </style>
    <script type="text/javascript">
        Function.prototype.repeat = 
        function (t, o) {
            var _ = this;
            return setInterval(function () { _.apply(o); }, t);
        };

        var $ = function (i) { return document.getElementById(i); };

        var USER_AGENT = navigator.userAgent.toLowerCase();
        var CLASS_NAME = (USER_AGENT.indexOf('msie') > -1) ? 'className' : 'class';

        var Colony = 
        function (w, h) {
            this.w = w;
            this.h = h;
            this.generate = 
            function () {
                var i, j;

                document.write('<table border="0" cellpadding="0" cellspacing="0">');
                for (j = 0; j < this.h; j++) {
                    document.write('<tr>');
                    for (i = 0; i < this.w; i++) document.write('<td id="' + i + '_' + j + '" class="C_0"></td>');
                    document.write('</tr>');
                }
                document.write('</table>');
            };
        };

        var Ant = 
        function (x, y, w, h) {
            this.x  = x;
            this.y  = y;
            this.dx = -1;
            this.dy = 0;
            this.w  = w;
            this.h  = h;
            this.id = 0;
            this.move = 
            function () {
                var C = $(this.x + '_' + this.y);
                var c;
                var t;

                if (this.x < 0 || this.x >= this.w || this.y < 0 || this.y >= this.h) {
                    alert('DEAD END ...');
                    clearTimeout(this.id);
                    return;
                }

                if (c = ((C.getAttribute(CLASS_NAME)).split('_')[1] ^ 1)) { // 反転
                    t = this.dx;
                    this.dx = this.dy;
                    this.dy = -t;
                } else {
                    t = this.dx;
                    this.dx = -this.dy;
                    this.dy = t;
                }

                C.setAttribute(CLASS_NAME, 'C_' + c);

                this.x += this.dx;
                this.y += this.dy;

                if (!this.id) this.id = this.move.repeat(10, this);
            };
        };
    </script>
</head>
<body>
    <script type="text/javascript">
        var W = 100, H = 100;
        new Colony(W, H).generate();
        new Ant(50, 50, W, H).move();
    </script>
</body>
</html>

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def ant(world_size, x_pos = world_size / 2, y_pos = world_size / 2)
  world = Array.new
  generation = 0
  direc = {}; direc[:x] = 0; direc[:y] = 1
  world_size.times{world <<  [true] * world_size}
  while true
    system "clear"
    p generation += 1
    world[x_pos][y_pos] = !world[x_pos][y_pos]
    world.each{|i|
      i.each{|k| printf("%s ",k ? : : :)}
      p ''
    }
    sleep 0.1

    if world[x_pos][y_pos] then
      direc[:x], direc[:y] = direc[:y], -direc[:x]
    else
      direc[:x], direc[:y] = -direc[:y], direc[:x]
    end
    
    x_pos = (x_pos + direc[:x]) % world_size
    y_pos = (y_pos + direc[:y]) % world_size
    
  end
end

world_size = 20
ant(world_size)

OpenGL/GLUTで。

ウィンドウの表示と描画がコードの半分以上orz。

作成はMac OS Xでしています。他の環境の場合には調整してください。

コンパイルは、

g++ -ansi -Wall -framework OpenGL -framework GLUT -framework Foundation -o doukaku276 doukaku276.cpp

終了条件はないので、ESCキーかQキーで終了させてください。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#include <bitset>
#include <GLUT/glut.h>

class Field
{
public:
    virtual ~Field() {}

    virtual bool operator () (int x, int y) const = 0;
    virtual void set(int x, int y) = 0;
    virtual void unset(int x, int y) = 0;
};

template<int Width, int Height>
class ConcreteField : public Field
{
public:
    ConcreteField() : bits_() {}

    bool operator () (int x, int y) const { return bits_[y * Width + x]; }
    void set(int x, int y) { bits_[y * Width + x] = true; }
    void unset(int x, int y) { bits_[y * Width + x] = false; }
private:
    std::bitset<Width * Height> bits_;
};

class Direction
{
public:
    static const int North = 0;
    static const int East  = 1;
    static const int South = 2;
    static const int West  = 3;

    Direction(int dir) : dir_(dir) {}

    void set(int dir) { dir_ = dir; }
    int get() const { return dir_; }

    void turnRight()
    {
        dir_ += 1;
        dir_ %= 4;
    }

    void turnLeft()
    {
        dir_ += 3;
        dir_ %= 4;
    }

private:
    int dir_;
};

class Ant
{
public:
    Ant(int x, int y, Direction direction) : x_(x), y_(y), direction_(direction) {}

    void move(Field& field)
    {
        if(field(x_, y_))
        {
            field.unset(x_, y_);
            direction_.turnRight();
            moveForward();
        }
        else
        {
            field.set(x_, y_);
            direction_.turnLeft();
            moveForward();
        }
    }

    void moveForward()
    {
        switch(direction_.get())
        {
        case Direction::North: --y_; break;
        case Direction::East:  ++x_; break;
        case Direction::South: ++y_; break;
        case Direction::West:  --x_; break;
        default:                     break;
        }
    }

private:
    int       x_;
    int       y_;
    Direction direction_;
};

static const int Interval = 0;
static const int Width    = 320;
static const int Height   = 240;

static ConcreteField<Width, Height> field;
static Ant                          ant(Width / 2, Height / 2, Direction::North);

void display()
{
    glClear(GL_COLOR_BUFFER_BIT);

    static const GLdouble black[] = { 0.0, 0.0, 0.0 };
    glColor3dv(black);

    glBegin(GL_POINTS);
    for(int y = 0; y < Height; ++y)
    {
        for(int x = 0; x < Width; ++x)
        {
            if(field(x, y))
            {
                glVertex2f(x, y);
            }
        }
    }
    glEnd();

    glutSwapBuffers();
}

void resize(int w, int h)
{
    glViewport(0, 0, w, h);
    glLoadIdentity();
    glOrtho(-0.5, w - 0.5, h - 0.5, -0.5, -1.0, 1.0);
}

void key(unsigned char key, int x, int y)
{
    switch(key)
    {
    case 'q':
    case 'Q':
    case '\033':
        std::exit(0);
        break;

    default:
        break;
    }
}

void timer(int n)
{
    ant.move(field);
    display();
    glutTimerFunc(Interval, timer, 0);
}

void initWindow(int argc, char* argv[])
{
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(Width, Height);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);

    glutCreateWindow("doukaku#276");

    glClearColor(1.0, 1.0, 1.0, 1.0);
}

void initEvent()
{
    glutDisplayFunc(display);
    glutReshapeFunc(resize);
    glutKeyboardFunc(key);
    glutTimerFunc(Interval, timer, 0);
}

int main(int argc, char* argv[])
{
    initWindow(argc, argv);
    initEvent();

    glutMainLoop();

    return 0;
}

Excel VBAにて、ごくシンプルに。

Excelを以下のように調整すると、実行結果が見やすくなります。

  • 行の高さと列の幅を調整して、全てのセルを正方形にする。
  • 表示倍率を小さくして、なるべく多くのセルが画面内に収まるようにする
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
Option Explicit

'// [定数] 蟻の方向
Const DIR_UP = 0        '上向き
Const DIR_RIGHT = 1     '右向き
Const DIR_DOWN = 2      '下向き
Const DIR_LEFT = 3      '左向き

'// [定数] 色
Const COLOR_WHITE = &HFFFFFF    '白
Const COLOR_BLACK = &H0         '黒

'// ラングトンの蟻
Sub Langtons_ant()
    Dim r_Ant As Long       '// 蟻の位置(行番号)
    Dim c_Ant As Long       '// 蟻の位置(行番号)
    Dim dir_Ant As Integer  '// 蟻の方向
    
    '////////////////////////////////////////////////////////////////////
    ' 初期処理
    '////////////////////////////////////////////////////////////////////
    
    '// 蟻の位置を決める。
    r_Ant = 50
    c_Ant = 50
    '// 蟻の方向を決める。
    dir_Ant = DIR_UP    '上向き
    
    '////////////////////////////////////////////////////////////////////
    ' 主処理
    '////////////////////////////////////////////////////////////////////
    
    '// 蟻がシート内にいる間、以下を繰り返す。
    Do While 0 < c_Ant And c_Ant <= Columns.Count _
            And 0 < r_Ant And r_Ant <= Rows.Count
    
        '// 蟻がいるマスの色による場合分け。
        If Cells(r_Ant, c_Ant).Interior.Color = COLOR_BLACK Then
            '// マスの色が黒:
            
            '// 蟻を90°右に方向転換する。
            dir_Ant = (dir_Ant + 1) Mod 4
            
            '// マスの色を白に変える。
            Cells(r_Ant, c_Ant).Interior.Color = COLOR_WHITE
        Else
            '// マスの色が白:
            
            '// 蟻を90°左に方向転換する。
            dir_Ant = (dir_Ant + 3) Mod 4
            
            '// マスの色を黒に変える。
            Cells(r_Ant, c_Ant).Interior.Color = COLOR_BLACK
        End If
        
        '// 蟻を一歩進める。
        Select Case dir_Ant
            Case DIR_UP
                '// 蟻が上向き:
                r_Ant = r_Ant - 1
            Case DIR_RIGHT
                '// 蟻が右向き:
                c_Ant = c_Ant + 1
            Case DIR_DOWN
                '// 蟻が下向き:
                r_Ant = r_Ant + 1
            Case DIR_LEFT
                '// 蟻が左向き:
                c_Ant = c_Ant - 1
        End Select
    Loop
End Sub

Excel VBAにて、複数の蟻が動くようにしました。(#9388の変更)

Excelを以下のように調整すると、実行結果が見やすくなります。

  • 行の高さと列の幅を調整して、全てのセルを正方形にする。
  • 表示倍率を小さくして、なるべく多くのセルが画面内に収まるようにする。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
Option Explicit

'// [定数] 蟻の方向
Const DIR_UP = 0        '上向き
Const DIR_RIGHT = 1     '右向き
Const DIR_DOWN = 2      '下向き
Const DIR_LEFT = 3      '左向き

'// [定数] 色
Const COLOR_WHITE = &HFFFFFF    '白
Const COLOR_BLACK = &H0         '黒

'// [構造体] 蟻
Type typ_Ant
    r As Long       '// 蟻の位置(行番号)
    c As Long       '// 蟻の位置(列番号)
    dir As Long     '// 蟻の方向
    died As Boolean '// 死亡フラグ
End Type

'// ラングトンの蟻(複数版)
Sub Langtons_ant_s()

    Dim ants() As typ_Ant   '蟻
    Dim i As Integer        'インデックス
    Dim AllDied As Boolean  '全ての蟻が死亡したか?
    
    '////////////////////////////////////////////////////////////////////
    ' 初期処理
    '////////////////////////////////////////////////////////////////////
    
    '// 蟻の数を決める。
    ReDim ants(5)
    
    '// 蟻1匹目
    ants(0).r = 70
    ants(0).c = 100
    ants(0).dir = DIR_UP
    
    '// 蟻2匹目
    ants(1).r = 100
    ants(1).c = 100
    ants(1).dir = DIR_UP
    
    '// 蟻3匹目
    ants(2).r = 30
    ants(2).c = 120
    ants(2).dir = DIR_RIGHT
    
    '// 蟻4匹目
    ants(3).r = 40
    ants(3).c = 90
    ants(3).dir = DIR_DOWN
    
    '// 蟻5匹目
    ants(4).r = 40
    ants(4).c = 10
    ants(4).dir = DIR_LEFT
    
    '// 蟻6匹目
    ants(5).r = 70
    ants(5).c = 100
    ants(5).dir = DIR_UP
    
    '////////////////////////////////////////////////////////////////////
    ' 主処理
    '////////////////////////////////////////////////////////////////////
    
    '// いずれかの蟻が生きている間、以下を繰り返す。
    Do While True
        AllDied = True
        For i = 0 To UBound(ants)
            If Not ants(i).died Then
                AllDied = False
                Exit For
            End If
        Next
        If AllDied Then
            '// 全ての蟻が死亡している。
            
            '// 処理を中断する。
            Exit Do
        End If
        
        '// 生きているすべての蟻について、以下を繰り返す。
        For i = 0 To UBound(ants)
            If Not ants(i).died Then
                
                '// 蟻がいるマスの色による場合分け。
                If Cells(ants(i).r, ants(i).c).Interior.Color = COLOR_BLACK Then
                    '// マスの色が黒:
                    
                    '// 蟻を90°右に方向転換する。
                    ants(i).dir = (ants(i).dir + 1) Mod 4
                    
                    '// マスの色を白に変える。
                    Cells(ants(i).r, ants(i).c).Interior.Color = COLOR_WHITE
                Else
                    '// マスの色が白:
                    
                    '// 蟻を90°左に方向転換する。
                    ants(i).dir = (ants(i).dir + 3) Mod 4
                    
                    '// マスの色を黒に変える。
                    Cells(ants(i).r, ants(i).c).Interior.Color = COLOR_BLACK
                End If
                
                '// 蟻を一歩進める。
                Select Case ants(i).dir
                    Case DIR_UP
                        '// 蟻が上向き:
                        ants(i).r = ants(i).r - 1
                    Case DIR_RIGHT
                        '// 蟻が右向き:
                        ants(i).c = ants(i).c + 1
                    Case DIR_DOWN
                        '// 蟻が下向き:
                        ants(i).r = ants(i).r + 1
                    Case DIR_LEFT
                        '// 蟻が左向き:
                        ants(i).c = ants(i).c - 1
                End Select
                
                '// 蟻がシート内にいることを確認する。
                If Not (0 < ants(i).c And ants(i).c <= Columns.Count _
                        And 0 < ants(i).r And ants(i).r <= Rows.Count) Then
                    '// シート内にいない:
        
                    '// 蟻の死亡フラグをオンにする。
                    ants(i).died = True
                End If
            
            End If
        Next
    Loop
End Sub

F# 1.9.6.16 で作成しました。
初期状態の背景色を白黒選択可能にしました。
アリの初期状態の向きはランダムにしています。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
open System
open System.Windows.Forms
open System.Drawing

let size = 300
let (white, black) = (-1, 1)
let dirTable = [|(1, 0); (0, 1); (-1, 0); (0, -1)|]
let dirTextTable = [|"右"; "下"; "左"; "上"|]

type Langton'sAntForm() as this =
    inherit Form()
    [<DefaultValue>]
    val mutable ant : int -> int[,] -> Async<unit>
    let bitmap = new Bitmap(size, size)
    let picture = new PictureBox(Image = bitmap, Size = new Size(size, size), Location = new Point(0, 30))
    let button = new Button(Size = new Size(50, 30), Location = new Point(0, 0), Text = "Start")
    let checkbox = new CheckBox(Size = new Size(70, 30), Location = new Point(60, 0), Text = "黒背景")
    let labelDir = new Label(Size = new Size(80, 30), Location = new Point(140, 0), TextAlign = ContentAlignment.MiddleLeft)
    let labelStep = new Label(Size = new Size(60, 30), Location = new Point(230, 0), TextAlign = ContentAlignment.MiddleLeft)
    do this.Controls.AddRange([|(picture :> Control); (button :> Control); (checkbox :> Control); (labelDir :> Control); (labelStep :> Control)|])
    do this.Text <- "ラングトンのアリ"
    do this.ClientSize <- new Size(size, size + 30)
    do this.FormBorderStyle <- FormBorderStyle.Fixed3D
    do this.MaximizeBox <- false
    do button.Click.Add(fun _ ->
        let dir = (new Random()).Next(4)
        labelDir.Text <- "初期方向:" ^ dirTextTable.[dir]
        let (place, color) = if checkbox.Checked then (black, Color.Black) else (white, Color.White)
        [for x in 0..(size - 1) -> [for y in 0..(size - 1) -> (x, y)]] |> List.concat
        |> List.iter(fun (x, y) -> bitmap.SetPixel(x, y, color))
        Array2D.create size size place |> this.Ant dir |> Async.Start
        button.Enabled <- false)
    
    member this.Ant with get() = this.ant and set(value) = this.ant <- value
    member this.Print (x, y) place step =
        try
            if this.InvokeRequired then
                this.Invoke(new MethodInvoker(fun () ->
                    bitmap.SetPixel(x, y, if place = white then Color.White else Color.Black)
                    labelStep.Text <- string step
                    this.Refresh()))
                |> ignore
        with
        | :? ObjectDisposedException -> ()

let rec loop step (x, y) dir (field : int[,]) (form : Langton'sAntForm) =
    let rotate dir place = (dir + place + 4) % 4
    let turnover = ( * ) -1
    let move (x, y) dir =
        let (dx, dy) = dirTable.[dir] in (x + dx, y + dy)
    let (|InField|OutOfField|) (x, y) =
        if 0 <= x && x < size && 0 <= y && y < size then InField else OutOfField

    match (x, y) with
    | OutOfField -> ()
    | InField ->
        let newDir = rotate dir field.[x, y]
        field.[x, y] <- turnover field.[x, y]
        form.Print (x, y) field.[x, y] step
        loop (step + 1) (move (x, y) newDir) newDir field form

[<STAThread()>]
do
    use form = new Langton'sAntForm()
    form.Ant <- fun dir field -> async { loop 1 (size / 2, size / 2) dir field form }
    Application.EnableVisualStyles()
    Application.Run(form) |> ignore

アリの動作をどう書くかという話じゃなく、余談にはなりますが、参考ページのブログエントリに書かれていたビットマップ表現と効率について。

DOMのあるノード直下に非常に多数のノードを作る(兄弟ノードが多すぎる)・それらのノードにアクセスするというのは、速度低下の原因になりがちです。どのくらいの数からどこに影響が出るかは実装によるでしょうが、ノード総数が少々多くなろうとも入れ子・小分けにすることで改善できる場合があります。

添付コードのように変更を加えた場合、Pen4 2.53GHzにおいて時間を計測すると次のようになりました(単位:ms ― 遅いCPUだから差が顕著になっているのであって、今時のCPUではこれほどの改善は見込めないかもしれませんが)。

Firefox 3.5.1:
キャンバス作成(ノード作成)が34,551から1,021へと大幅に改善、アリの行進(ノードアクセス)でも330,770から216,934に改善
Opera 10b1:
キャンバス作成は328から344にと若干遅くなるが、もともとあまり時間が掛かっていない。アリの行進の方は1,188,625から415,312に改善

一方で、テーブル要素にしてしまう方法も試してみましたが、手元の環境ではdiv要素で小分けとほとんど変わりませんでした。セルの色表現を、要素のクラス書き換え・クラスごとにCSSで色指定する方法も試してみたが、こちらでもほとんど変化なし。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<style type="text/css">
#canvas{
       border: 1px solid #999;
       width: 300px;
       height: 300px;
}
/* 子divへのCSS指定を、孫divへの指定にするだけ */
#canvas div div{
       width: 3px;
       height: 3px;
       float: left;
}
</style>



// スクリプト部の変更点はwindow.onloadのコールバックのみ

window.onload = function(){
// キャンバス作成部 セルを表すdivを入れ子に
    var canvas = document.getElementById('canvas');
    var k = 0;
    for(var i=0; i< WORLD_SIZE; i++){
        var row = document.createElement('div');
        canvas.appendChild(row);
        for(var j=0; j< WORLD_SIZE; j++){
            var cell = document.createElement('div');
            row.appendChild(cell);
            earth[k] = new Cell(cell);
            k++;
        }
    }
// 以下変更なし
    lang_ant = new Ant();//蟻の誕生
    lang_ant.world = earth;//地球に降り立つ
    lang_ant.ageDisplay = document.getElementById('step');
    
    document.getElementById('run').disabled = false;
}

Haskellで実装。要:UTF8-String

$ runhaskell langton.hs 100 100 20

とかすると、100x100マスの世界の模様を20ステップごとに表示してくれます。最後の数字を省略すると律儀に一世代ごとに印字します。

無限リストばんざい!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import qualified System.IO.UTF8 as U
import System.Environment (getArgs, getProgName)
import Control.Monad.State
import qualified Data.Map as M
import Data.List (sortBy, groupBy)

type Point = (Int, Int)
type Direction = (Int, Int)
data Ant = Ant {pos::Point, direction::Direction} deriving Show
data Color = White | Black

data AntsState = AS { world::(M.Map Point Color), ants::[Ant], wrap :: Bool, height::Int, width::Int, generation::Int }

instance Show Color where
  show Black = "■"
  show White = "□"

cmp f ((_,a),_) ((_,b),_) = f a b

instance Show AntsState where
  show AS{world=wd, generation=g} = "gen: " ++ show g ++ "\n" ++ (unlines $ map (concatMap(show.snd)) $ groupBy (cmp (==)) $ (sortBy (cmp compare) $ M.toList wd))

main = do args <- getArgs
          pname <- getProgName
          case args of
            (x:y:s:_) -> mapM_ (U.putStrLn . show . head) $ iterate (drop (read s)) $ evolutions (read x) (read y)
            (x:y:_)   -> mapM_ (U.putStrLn . show) $ evolutions (read x) (read y)
            _         -> putStrLn ("usage: " ++ pname ++ " height width [step]")

makeAnt x y = Ant {pos=(x, y), direction=(0,-1)}

makeWorld height width ants wrap = AS {
  world = M.fromList [((x,y), White) | x <- [0..width-1], y <- [0..height-1]],
  ants = ants,
  height = height,
  width = width,
  generation = 0,
  wrap = wrap
}

proceed :: State AntsState AntsState
proceed = do  st@AS{ants=as,generation=g} <- get
              a' <- mapM procAnts as
              st <- get
              let s = st{ants = a',generation=g+1}
              put s
              return s

procAnts ant@Ant{pos=p@(x,y), direction=(dx,dy)} = do
  w@AS{world=wd, wrap=wr, width=wdt, height=h} <- get
  let pt = ((x+dx) `mod` wdt, (y+dy) ` mod` h)
      st = maybe Black id $ M.lookup pt wd
      (dr, s') = case st of
                  Black -> ((dy, -dx), White)
                  _     -> ((-dy, dx), Black)
  put w{world= M.insert pt s' wd}
  return ant{pos = pt, direction = dr}

evolutions width height = iterate (execState proceed) (makeWorld width height [makeAnt (width*3`div`5) (height*2`div`5)] True)

画面のサイズや画面端での扱いについて特に規定されていないようなので、何も考えずにサックリと書いてみた。枠まで到達しても死にません。

方向転換に関する論理計算がパッと見で分かりにくいだけで、他は分かりやすいのではないかと。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
!東=0
!南=1
!西=2
!北=3

線スタイルは`透明`
必要な 間、蟻を進めて0.01秒待つ

■ブロック
 ・{整数}W{=5}
 ・{整数}H{=5}
 ・色取得(X,Yの)~
  X*W,Y*Wを点取得
 ・プロット(X,YにCOLを)~
  塗り色はCOL
  X*W,Y*Hから(X+1)*W,(Y+1)*Hへ四角

■蟻 +ブロック
 ・{整数}向き{=2}
 ・{整数}X{=40}
 ・{整数}Y{=40}
 ・進む~
  COLとは整数=X,Yの色取得
  向き=(向き-2*(COL==白色)+5)%4
  X,Yに(COLの反転色)をプロット
  X=X-(向き==西)+(向き==東)
  Y=Y-(向き==南)+(向き==北)

●反転色(COLの)
 白色-COLを戻す

連投失礼。Wikipediaに載っていた拡張版のラングトンの蟻(色反転でなく、複数色循環)も実装してみた。まだ誰もやっていなかったかな?拡張の解釈は多分合っていると思ふ。。。

あたかも要塞のように常に左右に対称に広がっていく様子は割と面白い。(都市発展系のシミュレーションゲームのよう)

# 発展にかなり時間がかかるので、ついでに500ステップ毎に描画を反映させるようにしました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
!ブロック幅 =3
!ブロック高さ=3
!世界幅 =150
!世界高さ=120

!東=0
!南=1
!西=2
!北=3

!色数 =4
!基準色=黒色
!STEPBY=500

ルールは『RLLR』
ルールの色配列は「{基準色},{赤色},{青色},{緑色}」を`,`で区切ったもの

母艦について
 クライアントW=ブロックのW*世界幅
 クライアントH=ブロックのH*世界高
 スタイルは`枠固定`
 基準色で画面クリア

STEPとは整数

線スタイルは`透明`
必要な 間
 蟻を進める。
 もしSTEP%STEPBYが0ならば
  0.01秒待つ
  母艦は「step: {STEP}」
 STEPに1を直接足す

■ルール
 ・{配列}ルール配列
 ・テキスト ←ルール設定 デフォルト
 ・ルール設定(V)~
  Vを文字列分解して反復
   ルール配列[回数-1]=2*(対象==`R`)-1 # Rなら1、それ以外は-1
 ・{非公開}色配列

■ブロック
 ・{非公開}W{=3}
 ・{非公開}H{=3}
 ・色取得(X,Yの)~
  X*W,Y*Wを点取得
 ・プロット(X,YにCOLを)~
  塗り色はCOL
  X*W,Y*Hから(X+1)*W,(Y+1)*Hへ四角

■蟻 +ブロック
 ・{整数}向き{=0}
 ・{整数}X{=75}
 ・{整数}Y{=50}
 ・進む~
  COLとは整数=X,Yの色取得
  COLで向きを方向転換する
  COLを色循環する
  X,YにCOLをプロット
  Xに(向き==東)-(向き==西)を世界幅でMOD加算
  Yに(向き==南)-(向き==北)を世界高でMOD加算

●方向転換する({整数}COLで{参照渡し}DIRを)
 Iとは整数=ルールの色配列でCOLを配列検索
 もしIが(-1)ならばI=0
 DIRに(ルールのルール配列[I])を4でMOD加算する

●色循環する({参照渡し}COLを)
 COLORSとは配列=ルールの色配列
 Iとは整数=COLORSでCOLを配列検索
 COL=COLORS[(I+1)%色数]

# Z/CZ上でAにBを直接足す
●MOD加算({参照渡し}Aに{整数}Bを{整数}Cで)
 A=(A+B+C)%C

バッチで。

  e.g.
    C:\>#276.bat 10
    
               |
          **   |
         *  *  |
        *    * |
        *    * |
         ****  |
               |
               |
               |
               |
    X = 7, Y = 5
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@echo off
setlocal enabledelayedexpansion
  set s=
  set t=
  set x=0
  set y=0
  set dx=-1
  set dy=0
  
  if "%~1" == "" (echo usage: %~n0 SIZE >&2 & exit /b 1)
  
  set /a x=%~1/2,y=%~1/2
  
  for /l %%j in (1,1,%~1) do (
    for /l %%i in (1,1,%~1) do set c[%%i][%%j]=0
  )
  
  :_
    if !x! lss 1 goto BREAK
    if !x! gtr %~1 goto BREAK
    if !y! lss 1 goto BREAK
    if !y! gtr %~1 goto BREAK
    
    if !c[%x%][%y%]! equ 0 (
      set c[%x%][%y%]=1
      set t=%dx%
      set dx=%dy%
      set /a dy=-!t!
    ) else (
      set c[%x%][%y%]=0
      set t=%dx%
      set /a dx=-!dy!
      set dy=!t!
    )
    set /a x+=%dx%,y+=%dy%
    
    cls
    for /l %%j in (1,1,%~1) do (
      set s=
      for /l %%i in (1,1,%~1) do (
        if !c[%%i][%%j]! equ 0 (
          set s=!s! 
        ) else (
          set s=!s!*
        )
      )
      echo ^ !s!^|
    )
    echo X = %x%, Y = %y%
    
    ping -n 2 127.0.0.1 > NUL
  goto _
  :BREAK
endlocal & echo DEAD END ...

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#!/usr/bin/python
# -*- coding: utf-8 -*-
# http://ja.doukaku.org/276/

import random
import sys

EAST = 0
NORTH = 90
WEST = 180
SOUTH = 270

BLACK = (0, 0, 0)
RED = (1, 0, 0)
GREEN = (0, 1, 0)
BLUE = (0, 0, 1)


class Grid(object):

    def __init__(self, width=40, height=20):
        self._width = width
        self._height = height
        self._color = {}

    def width(self):
        return self._width

    def height(self):
        return self._height

    def color(self, position, color=None):
        if color is not None:
            self._color[tuple(position)] = color

        return self._color.get(tuple(position), BLACK)

    def view(self):
        for y in range(0, self.height()):
            for x in range(0, self.width()):
                sys.stdout.write({
                    BLACK: ' ',
                    RED: '*',
                    GREEN: '+',
                    BLUE: '@',
                    }[self.color((x, y))])
            sys.stdout.write('\n')


class Ant(object):

    def __init__(
        self,
        grid,
        color=RED,
        position=[10, 10],
        direction=EAST,
        ):

        self._grid = grid
        self._position = position
        self._direction = direction
        self._color = color

    def position(self, position=None):
        if position is not None:
            self._position = list(position)

        return self._position

    def color(self, color=None):
        if color is not None:
            self._color = color

        return self._color

    def left(self):
        self._direction = (self._direction + 90) % 360

    def right(self):
        self._direction = (self._direction - 90) % 360

    def forward(self):
        if self._direction == NORTH:
            self._position[1] -= 1
        elif self._direction == SOUTH:
            self._position[1] += 1
        elif self._direction == EAST:
            self._position[0] -= 1
        elif self._direction == WEST:
            self._position[0] += 1

        self._position[0] %= self._grid.width()
        self._position[1] %= self._grid.height()

    def step(self):
        if self._color == self._grid.color(self._position):
            self._grid.color(self._position, BLACK)
            self.left()
        else:
            self._grid.color(self._position, self._color)
            self.right()

        self.forward()


if __name__ == '__main__':

    width = int(sys.argv[1])
    height = int(sys.argv[2])
    num = int(sys.argv[3])
    iteration = int(sys.argv[4])

    grid = Grid(width=width, height=height)
    ants = []
    for i in range(0, num):
        c = (RED, GREEN, BLUE)[i % 3]
        x = int(random.random() * width)
        y = int(random.random() * height)
        ants.append(Ant(grid, color=c, position=[x, y]))

    for i in range(0, iteration):
        for ant in ants:
            ant.step()
        if i % (iteration / 10) == 0:
            sys.stdout.write('step: %d\n' % i)
            grid.view()

Haskellに翻訳。
C++からの「翻訳」なのでHaskellらしくないかもしれません。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import Graphics.UI.GLUT
import Control.Exception
import System.Exit
import Data.IORef

interval     = 0
windowWidth  = 320::GLsizei
windowHeight = 240::GLsizei

data Direction = North | East | South | West deriving Enum
data Ant = Ant Position Direction

turnRight :: Direction -> Direction
turnRight d = toEnum (((fromEnum d) + 1) `mod` 4)

turnLeft :: Direction -> Direction
turnLeft d = toEnum (((fromEnum d) + 3) `mod` 4)

initialEnv :: (Ant, [Position])
initialEnv = (Ant (Position (windowWidth `div` 2) (windowHeight `div` 2)) East, [])

convd :: (Integral a) => a -> GLdouble
convd = fromInteger.toInteger

forward (Position x y) North = Position x (y - 1)
forward (Position x y) East  = Position (x + 1) y
forward (Position x y) South = Position x (y + 1)
forward (Position x y) West  = Position (x - 1) y

draw env = do
  (_, ps) <- readIORef env
  clearColor $= Color4 1.0 1.0 1.0 1.0
  clear [ColorBuffer]
  color $ Color3 (0.0::Double) 0.0 0.0
  renderPrimitive Points $ mapM_ (¥ (Position x y) -> vertex $ Vertex2 x y) ps
  swapBuffers

update (Ant pos dir, ps) =
  if elem pos ps
    then (Ant (forward pos (turnRight dir)) (turnRight dir), [p | p <- ps, p /= pos])
    else (Ant (forward pos (turnLeft dir)) (turnLeft dir), pos:ps)

-- event handlers

display env = do
  draw env

reshape (Size w h) = do
  viewport $= (Position 0 0, Size w h)
  loadIdentity
  ortho (-0.5) ((convd w) - 0.5) ((convd h) - 0.5) (-0.5) (-1.0) (1.0)

keyboardMouse key keystate modifiers position = do
  case key of
    Char 'q' -> throwIO $ ExitException ExitSuccess
    _        -> return ()

timer env = do
  modifyIORef env $ update
  draw env
  addTimerCallback interval $ timer env

main = do
  env <- newIORef initialEnv
  getArgsAndInitialize
  initialDisplayMode    $= [RGBAMode, DoubleBuffered]
  initialWindowPosition $= Position 100 100
  initialWindowSize     $= Size windowWidth windowHeight
  createWindow "doukaku#276"
  displayCallback       $= display env
  reshapeCallback       $= Just reshape
  keyboardMouseCallback $= Just keyboardMouse
  addTimerCallback interval $ timer env
  mainLoop

Python + Tkinter

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/usr/bin/python
# -*- coding: utf-8 -*-
# http://ja.doukaku.org/276/

import random
import sys
import Tkinter as Tk

DOT_SIZE = 5


class Ant(object):

    def __init__(self, grid, x, y, direction, color):
        self._grid = grid
        self.x = x
        self.y = y
        self.direction = direction
        self.color = color

    def left(self):
        self.direction = (self.direction + 90) % 360

    def right(self):
        self.direction = (self.direction - 90) % 360

    def forward(self):
        diff = {  # 0:EAST, 90:NORTH 180:SOUTH 270:WEST
            0: (1, 0),
            90: (0, -1),
            180: (-1, 0),
            270: (0, 1),
            }[self.direction]
        self.x = (self.x + diff[0]) % self._grid.width
        self.y = (self.y + diff[1]) % self._grid.height

    def is_my_color(self):
        return self.color == self._grid.get_color(self.x, self.y)

    def black(self):
        self._grid.delete_color(self.x, self.y)

    def my_color(self):
        self._grid.update_color(self.x, self.y, self.color)

    def step(self):
        if self.is_my_color():
            self.black()
            self.left()
        else:
            self.my_color()
            self.right()

        self.forward()


class Grid(Tk.Canvas):

    def __init__(self, parent, width=40, height=20):
        self.width = width
        self.height = height
        self._color = {}

        xw = DOT_SIZE * self.width
        xh = DOT_SIZE * self.height
        Tk.Canvas.__init__(self, parent, background='black', width=xw,
                           height=xh)

    def color(self, x, y):
        return self._color.get((x, y), {'id': None, 'color': 'black'})

    def get_color(self, x, y):
        return self.color(x, y)['color']

    def delete_color(self, x, y):
        old = self.color(x, y)
        if old['id'] is not None:
            self.delete(old['id'])
            del self._color[(x, y)]

    def update_color(self, x, y, color):
        old = self.color(x, y)
        if old['id'] is None:
            xx0 = x * DOT_SIZE
            xy0 = y * DOT_SIZE
            xx1 = xx0 + DOT_SIZE
            xy1 = xy0 + DOT_SIZE
            id = self.create_rectangle(xx0, xy0, xx1, xy1, fill=color,
                                       outline=color)
            self._color[(x, y)] = {'id': id, 'color': color}
        else:
            self.itemconfigure(old['id'], fill=color, outline=color)


class Frame(Tk.Frame):

    def __init__(self, width, heigth, num_ant, master=None):
        Tk.Frame.__init__(self, master)
        self.master.title('Rangton Ant')

        self._grid = Grid(self, width=width, height=height)
        self._grid.pack()

        def _rand_int(n):
            return int(random.random() * n)

        def _make_ant(i):
            c = ('red', 'yellow', 'green', 'cyan', 'blue', 'cyan',
                 'magenta', 'white')[i % 7]
            d = (0, 90, 180, 270)[_rand_int(4)]
            x = _rand_int(width)
            y = _rand_int(height)
            return Ant(self._grid, x, y, d, c)

        self._ants = map(_make_ant, range(num_ant))

        self.move()

    def move(self):
        for ant in self._ants:
            ant.step()
        self.after(1, self.move)


if __name__ == '__main__':

    width = int(sys.argv[1])
    height = int(sys.argv[2])
    num_ant = int(sys.argv[3])

    f = Frame(width, height, num_ant)
    f.pack()
    f.mainloop()

出題者によるデモが初期読み込み時に重かったので、改編してみました。

おまけにアリ自体も最大5匹まで増量可能だったり。

ちなみにOperaだとCSSのエラーで動かないっぽいのが難点ですorz

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
<html>
  <head>
    <meta http-equiv="content-type" content="text/html;charset=shift_jis">
    <title>Langton's Ant</title>
    <script type="text/javascript"><!-- //
langton={};

langton.Direction=function(x,y){ this.x=(x)?x:0; this.y=(y)?y:0; };

langton.Direction.prototype={
  x : 0,
  y : 0,
  equals : function(test){
             return (this.x==test.x)&&(this.y==test.y);
           },
  change : function(dif){
             var val=langton.Direction.next(this,dif);
             this.x=val.x;
             this.y=val.y;
             return this;
           }
 };

langton.Direction.NORTH=new langton.Direction(0,-1);
langton.Direction.SOUTH=new langton.Direction(0,1);
langton.Direction.EAST=new langton.Direction(1,0);
langton.Direction.WEST=new langton.Direction(-1,0);

langton.Direction.CYCLE=[
  langton.Direction.NORTH,
  langton.Direction.EAST ,
  langton.Direction.SOUTH,
  langton.Direction.WEST
];

langton.Direction.FORWORD=0;
langton.Direction.RIGHT=1;
langton.Direction.BACKWORD=2;
langton.Direction.LEFT=3;

langton.Direction.next=function(current,change){
  var i = 0;
  for( ; i < langton.Direction.CYCLE.length; i++){
    if(current.equals(langton.Direction.CYCLE[i])){ break; }
  }
  i+=change;
  i%=langton.Direction.CYCLE.length;
  return langton.Direction.CYCLE[i];
};

langton.Ant=function(){};

langton.Ant.prototype={
  x : -1,
  y : -1,
  direction : null,
  color : null,
  go : function(lr){
         this.direction=this.direction.change(lr);
         this.x+=this.direction.x;
         this.y+=this.direction.y;
         if(this.x==langton.Canvas.rowsize){ this.x=0; }
         if(this.x<0){ this.x=langton.Canvas.rowsize-1; }
         if(this.y==langton.Canvas.rowsize){ this.y=0; }
         if(this.y<0){ this.y=langton.Canvas.rowsize-1; }
         return this;
       },
  eat : function(cell,isblack){
          var color = (isblack)?this.color:langton.Canvas.BLACK;
          cell.style.backgroundColor=color;
        },
  action : function(cells){
             var index = this.y*langton.Canvas.rowsize + this.x;
             if(!cells[index]){
               cells[index]=langton.Canvas.prepareCell();
               cells[index].style.position="absolute";
               cells[index].style.top=(this.x*3)+"px";
               cells[index].style.left=(this.y*3)+"px";
             }
             var isblack=(cells[index].style.backgroundColor==langton.Canvas.BLACK);
             this.eat(cells[index],isblack);
             var choice = (isblack) ? langton.Direction.RIGHT
                                    : langton.Direction.LEFT;
             this.go(choice);
             return this;
           }
};

langton.Canvas={};

langton.Canvas.body=null;
langton.Canvas.counter=null;

langton.Canvas.rowsize=100;
langton.Canvas.cells=[];

langton.Canvas.clear=function(){
  if(langton.Canvas.counter){
    langton.Canvas.counter.value=0;
    langton.Canvas.counter=null;
  }
  if(langton.Canvas.body){
    langton.Canvas.body.innerHTML="";
    langton.Canvas.body=null;
  }
  langton.Canvas.ants=[];
  langton.Canvas.cells=[];
};

langton.Canvas.ants=[];

langton.Canvas.BLACK="black";
langton.Canvas.WHITE="white";
langton.Canvas.BLUE="blue";
langton.Canvas.RED="red";
langton.Canvas.YELLOW="yellow";
langton.Canvas.GREEN="green";

langton.Canvas.COLORS=[
  langton.Canvas.WHITE,
  langton.Canvas.BLUE,
  langton.Canvas.RED,
  langton.Canvas.YELLOW,
  langton.Canvas.GREEN
];

langton.Canvas.prepareCell=function(){
  var div = document.createElement("div");
  langton.Canvas.body.appendChild(div);
  div.style.width="3px";
  div.style.height="3px";
  div.style.backgroundColor=langton.Canvas.BLACK;
  return div;
};

langton.Canvas.addAnt=function(){
  if(langton.Canvas.ants.length < langton.Canvas.COLORS.length){
    var ant =new langton.Ant();
    ant.x=Math.floor(Math.random()*langton.Canvas.rowsize);
    ant.y=Math.floor(Math.random()*langton.Canvas.rowsize);
    ant.color=langton.Canvas.COLORS[langton.Canvas.ants.length];
    var dirid = Math.floor(Math.random()*langton.Direction.CYCLE.length);
    var dir = langton.Direction.CYCLE[dirid];
    ant.direction=new langton.Direction(dir.x,dir.y);
    langton.Canvas.ants.push(ant);
  };
};

langton.Action=function(){
  if(!langton.Canvas.body){
    langton.Canvas.body=document.getElementById("canvas");
  }
  if(langton.Canvas.ants.length==0){
    langton.Canvas.addAnt();
  }
  for(var i = 0; i < langton.Canvas.ants.length; i++){
    langton.Canvas.ants[i].action(langton.Canvas.cells);
  }
  if(!langton.Canvas.counter){
    langton.Canvas.counter=document.getElementById("counter");
  }
  var value = parseInt(langton.Canvas.counter.value,10)+1;
  langton.Canvas.counter.value=value;
};

/* -------------------------------------------------------------------------------- */
langton.timer=0;
langton.isTimerOn=false;

langton.startTimer=function(){
  langton.isTimerOn=true;
  document.getElementById("run").disabled=true;
  document.getElementById("clear").disabled=true;
  langton.timer = setInterval(langton.executeTimer, 1);
};

langton.stopTimer=function(){
  langton.isTimerOn=false;
};

langton.executeTimer=function(){
  if(langton.isTimerOn) langton.Action();
  else{
    clearInterval(langton.timer);
    document.getElementById("run").disabled=false;
    document.getElementById("clear").disabled=false;
    return 0;
  }
};

function test(){
  langton.Action();
}

    // --></script>
    <style type="text/css">
#canvas{
       border: 1px solid #999;
       width: 300px;
       height: 300px;
       background-color: black;
}
    </style>
  </head>
  <body>
    <p>
      <input type="button"
             value="run"
             onclick="langton.startTimer();"
             id="run" />
      <input type="button"
             value="add"
             onclick="langton.Canvas.addAnt();" />
      <input type="button"
             value="stop"
             onclick="langton.stopTimer();" />
      <input type="button"
             value="clear"
             onclick="langton.Canvas.clear()"
             id="clear" />
      <input type="text" id="counter" disabled value=0 />
    </p>
    <div id="canvas" style="position:relative"></div>
  </body>
</html>

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
%!PS
gsave
    0 dict begin
        /fieldsize 100 def
        /white [1 1 1] def
        /field [ fieldsize{ [fieldsize{ white }repeat] }repeat ] def
        /check-mark{
            % ant -- bool
            begin
                field position-x get position-y get
                color eq
            end
        }def
        /draw-mark{
            % ant --
            begin
                field position-x get position-y color put
            end
        }def
        /erase-mark{
            % ant --
            begin
                field position-x get position-y white put
            end
        }def

        /generate-ant{
            % init-x init-y direction-x direction-y color -- dict
            5 dict begin
                /color exch def
                /direction-y exch def
                /direction-x exch def
                /position-y exch def
                /position-x exch def
                currentdict
            end
        }def
        /move{
            % ant --
            begin
                /position-x position-x direction-x add def
                /position-y position-y direction-y add def
            end
        }def
        /turn-left{
            % ant --
            begin
               direction-y neg
               direction-x
               /direction-y exch def
               /direction-x exch def
           end
        }def
        /turn-right{
            % ant --
            begin
                direction-y
                direction-x neg
                /direction-y exch def
                /direction-x exch def
            end
        }def
        
        /draw-field{
            2 2 scale
            0 1 fieldsize 1 sub{
                % x
                0 1 fieldsize 1 sub{
                % x y
                    field 2 index get 1 index get
                    aload pop setrgbcolor
                    2 copy 1 1 rectfill
                    pop
                }for
                pop
            }for
            showpage
        }def

        /ant1
            fieldsize 2 idiv dup 0 1 [0 0 0] generate-ant
        def
        0 1 20000{
            ant1
            dup check-mark{
                dup erase-mark
                dup turn-right
                move
            }{
                dup draw-mark
                dup turn-left
                move
            }ifelse
            ant1 begin
                position-x dup 0 lt exch fieldsize ge or
                position-y dup 0 lt exch fieldsize ge or
                or
            end
            {
                exit
            }if
            % 20歩毎に表示
            20 mod 0 eq{
                draw-field
            }if
        }for
    end
grestore

秀丸マクロで。
セルを全角で表現しているので、かなり幅が広くなってしまいます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#w = 100;
#dct = 0;
$sp = " ";
$line = "";

#i = 0;
while (#i < #w) {
    $line = $line+$sp;
    #i = #i + 1;
}
$line = $line+"\n";

#i = 0;
while (#i < #w) {
    insert $line;
    #i = #i + 1;
}
movetolineno #w,#w/2;

#i = 0;
while (1) {
    $ant = gettext(x, y, x+2, y);
    if ($ant == $sp) {
        overwrite "○";
        #dct = (#dct+1)%4;
    } else {
        overwrite $sp;
        #dct = (((#dct+1)%4)+2)%4;
    }
    left;
    if (#dct == 0) {
        up;
    } else if (#dct == 1) {
        right;
    } else if (#dct == 2) {
        down;
    } else if (#dct == 3) {
        left;
    }
    if ((x > #w*2-1)||(x == 0)||(y == 0)||(y > #w-1)) {
        endmacro;
    }
}
endmacro;

Tkで描画。
canvasを配置し、そこにドットを描く。
使うモジュールはTk, Tkclient。

メモ
・TKの使い方
・初期値つきの二次元配列の定義
・ドットの代わりにlineを使用している。メモリを沢山消費しているかも
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
implement d276;
# doukaku?276 Langton

include "sys.m";
    sys:Sys;
include "draw.m";
    draw: Draw;
include "tk.m";
    tk: Tk;
include "tkclient.m";
    tkclient: Tkclient;

d276: module {
    init: fn(ctxt: ref Draw->Context, argv: list of string);
};

init(ctxt: ref Draw->Context, argv: list of string)
{
    sys = load Sys Sys->PATH;
    sys->pctl(sys->NEWPGRP, nil);
    draw = load Draw Draw->PATH;
    tk = load Tk Tk->PATH;
    tkclient = load Tkclient Tkclient->PATH;

    # canvas size
    height := 128;
    width := 128;

    pixel := array [width] of { * => array [height] of { * => 0}}; # 2dimensional array
    curx := width/2;
    cury := height/2;
    dir := 0; # N: 0 E: 1 S: 2 W: 3

    tkclient->init();

    if(ctxt == nil)
        ctxt = tkclient->makedrawcontext();

    (win, wmctl) := tkclient->toplevel(ctxt, nil, "d276", 0);

    # create canvas
    tk->cmd(win, sys->sprint("canvas .b -width %d -height %d -background white",width , height));
    tk->cmd(win, "pack .b");
    tk->cmd(win, "update");
    
    tkclient->onscreen(win, nil);
    tkclient->startinput(win, "kbd"::"ptr"::nil);

    for(;;){
        # toggle pixel and turn
        color :=pixel[curx][cury];
        cstr : string;
        if(color){
            cstr = "white";
            color = 0;
            dir += 1;
            if(dir > 3) dir = 0;
        }else{
            cstr = "black";
            color = 1;
            dir -= 1;
            if(dir < 0) dir = 3;
        }
        pixel[curx][cury] = color;

        # Plot (using line)
        tk->cmd(win, sys->sprint(".b create line %d %d %d %d -fill %s", curx,cury, curx,cury,cstr));
        tk->cmd(win, "update");

        # update direction
        case dir{
            0 =>
                cury--;
            1 =>
                curx++;
            2 =>
                cury++;
            3 =>
                curx--;
        }

        # if it reaches edge, it stop
        if((curx < 0) || (curx >= width) || (cury < 0) || (cury >= height)){
            break;
        }

    }

    # Window event loop?
    for(;;) alt{
        s := <-win.ctxt.kbd =>
            tk->keyboard(win, s);
        s := <-win.ctxt.ptr =>
            tk->pointer(win, *s);
        s := <-win.ctxt.ctl or
        s = <-win.wreq or
        s = <-wmctl =>
            if(s == "exit")
                return;
    }
}

GNU Octave 3.0.1で動作確認
アリの行動履歴を3次元表示します。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
world = zeros(100,100);
dirs = [[0,-1];[1,0];[0,1];[-1,0]];
posX = [60]; % X座標履歴
posY = [40]; % Y座標履歴
dir = 0;
while posX(1) > 0 & posX(1) <= 100 & posY(1) > 0 & posY(1) <= 100;
  if world(posX(1),posY(1)) == 1
    dir = mod(dir+1,4); % 右折
  else
    dir = mod(dir-1,4); % 左折
  end;
  world(posX(1),posY(1)) = abs(world(posX(1),posY(1))-1); % 白黒反転
  posX = [[posX(1)+dirs(dir+1,1)],posX]; % X方向の移動
  posY = [[posY(1)+dirs(dir+1,2)],posY]; % Y方向の移動
end;
plot3(posX,posY,length(posX):-1:1); % 軌跡表示 (Z軸:時間)

面白いですね。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// 盤面の幅
WIDTH=100
HEIGHT=100

// 盤面の値
WHITE=0
BLACK=1

// 上下左右移動する際のリニア増減分
UP_DIFF=-WIDTH
RIGHT_DIFF=+1
DOWN_DIFF=+WIDTH
LEFT_DIFF=-1

// 上下左右移動に対応するリニア増減分を保存してある配列
direcVec=[UP_DIFF,RIGHT_DIFF,DOWN_DIFF,LEFT_DIFF]

// 上下左右の方向を表す定数値
UP=0
RIGHT=1
DOWN=2
LEFT=3

def move(x, y, dir) {
  def pos = x + y*WIDTH
  pos += direcVec[dir]
  if (! (pos in (0 ..< WIDTH*HEIGHT))) {
    throw new Exception("over")
  }
  x = pos % WIDTH
  y = pos.intdiv(WIDTH)
  return [x, y]
}

def reverse(x, y) {
  def pos = x + y*WIDTH
  bd[pos] = -(bd[pos]-1)
}

def color(x, y) {
  def pos = x + y*WIDTH
  bd[pos]
}

def turnRight(dir) {
  (dir+1) % 4
}

def turnLeft(dir) {
  (dir-1) % 4
}

def displayBoard() {
  for (int y=0; y<HEIGHT; y++) {
    for (int x=0; x<WIDTH; x++) {
      print bd[x + y*WIDTH] == WHITE ? ' ' : '*'
    }
    println ""
  }
}

def doit() {
  def dir = RIGHT
  while (true) {
    def oldX=x
    def oldY=y
    if (color(x,y) == WHITE) { // 白なら右回転
      dir = turnRight(dir)
    }
    else { // 白ではないなら左回転
      dir = turnLeft(dir)
    }
    (x,y) = move(x,y,dir)
    reverse(oldX, oldY)
  }
}

// 盤面
bd = new int[WIDTH*HEIGHT]

// 画面の中央に配置
x = WIDTH.intdiv(2)
y = HEIGHT.intdiv(2)

try {
  doit()
}
catch (Exception e) {
  displayBoard()
}

コマンドライン上で描写するので、フィールドが狭いですがギリギリ列の生成は確認できました。列が生成されるまで時間がかかったので100回の移動ごとに描写していますが、この辺は適宜変更してください。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include<stdio.h>
#include<stdlib.h>
#define XMAX 75                                //フィールドの横幅
#define YMAX 75                                //フィールドの縦幅
#define CNTSPACE 100                        //描写間隔

char field[XMAX][YMAX];

enum Direction {up, right, down, left};

int main(void)
{
    enum Direction dir;
    int x, y, i, j, cnt = 0;
    
    for(i = 0; i < XMAX; i++)                //0が白、1が黒なので全て白に初期化
    {
        for(j = 0; j < YMAX; j++)
        {
            field[i][j] = 0;
        }
    }

    x = XMAX / 2;                            //フィールド中央からスタート
    y = YMAX / 2;
    dir = up;                                //進行方向は上からスタート
    
    while((x >= 0) && (y >= 0) && (x < XMAX) && (y < YMAX))
    {
        cnt++;
        if(field[x][y])
        {
            field[x][y] = 0;
            if(dir == left)    dir = up;
            else    dir++;
        }
        else
        {
            field[x][y] = 1;
            if(dir == up)    dir = left;
            else    dir--;
        }
        switch(dir)
        {
            case up:    y++;        break;
            case down:    y--;        break;
            case right:    x++;        break;
            case left:    x--;        break;
        }
        if(cnt == CNTSPACE)
        {
            system("cls");
            for(i = 0; i < XMAX; i++)
            {
                for(j = 0; j < YMAX; j++)
                {
                    field[i][j] ? putchar('@') : putchar('_');
                }
                putchar('\n');
            }
            cnt = 0;
        }
    }
    
    return 0;
}

Scalaで。春っぽい雰囲気にしてみました!?(季節を無視して)。ScalaどころかSwingアプリとしても拙い作りかもしれません。。。 paintComponent内で結構ぐるぐる回してもいけるもんなんですね。もしかしたら、マシンパワーによっては表示が追いつかないのかもしれません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import scala.collection.mutable.ListBuffer
import scala.concurrent.ops._
import scala.swing._
import scala.util._
import java.awt.{Graphics, Dimension}

trait AntWorld {
    protected val w:Int
    protected val h:Int
    protected def reflect
     var ants:List[Ant] = Nil
    lazy val table:List[Array[Int]] = List.tabulate(w, {_=>Array.make(h,0)})
    private val r = new Random()
    def ant(c:Int) = new Ant(r.nextInt(w)/2+w/4, r.nextInt(h)/2+h/4, c)
    def antStart() = {
        spawn {
            while(true) {
                ants.foreach {_.move}
                reflect
                Thread.sleep(1)
            }
        }
    }
    class Ant(var x:Int, var y:Int, c:Int) {
        private val rads = List((0,-1), (1,0), (0,1), (-1,0))
        private val (initX, initY) = (x, y)
        private var radi = 0
        def init = { x = initX; y = initY; radi = 0 }
        def move = {
            val v = table(x)(y)
            table(x)(y) = (if (v != 0) 0 else c)
            radi = (radi + (if (v != 0) 1 else 3) ) % 4
            x = (x + rads(radi)._1 + w) % w
            y = (y + rads(radi)._2 + h) % h
        }
    }
}

object LangtonAnt extends SimpleGUIApplication {
    import java.awt.Color
    val aSize = 5
    def top = new MainFrame {
        title = "Langton's Ant"
        val panel = new Panel() with AntWorld {
            def reflect = repaint
            val (w, h) = (100, 100)
            peer.setPreferredSize(new Dimension(w * aSize, h * aSize))
            override def paintComponent(g:Graphics) = {
                super.paintComponent(g)
                for (x <- 0 until w; y <- 0 until h) {
                    table(x)(y) match {
                        case 0 => g.setColor(Color.WHITE)
                        case c => g.setColor(new Color(c))
                    }
                    g.fillRect(x * aSize, y * aSize, aSize, aSize)
                }
            }
            def clear = {
                table.foreach { a => for (i <- 0 until a.length) a(i) = 0 }
            }
            def reset = {
                ants =
                    ant(Color.GREEN.getRGB) ::
                    ant(Color.PINK.getRGB) ::
                    ant(Color.BLUE.getRGB) ::
                    ant(Color.RED.getRGB) ::
                    ant(Color.CYAN.getRGB) ::
                    Nil
                clear
            }
            def replay = {
                this.synchronized {
                    ants.foreach( _.init )
                    clear
                }
            }
            reset
        }
        contents = new BoxPanel(Orientation.Vertical ) {
            contents += new BoxPanel(Orientation.Horizontal ) {
                contents += new Button(Action("Clear"){ panel.clear })
                contents += new Button(Action("Replay"){ panel.replay })
                contents += new Button(Action("Reset"){ panel.reset })
            }
            contents += panel
        }
        panel.antStart
    }
}

Index

Feed

Other

Link

Pathtraq

loading...