DartでLifegame
表題通り、Dartでライフゲームを実装してみた。
環境
Dart Editor version 0.5.3_r22223
Dart SDK version 0.5.3.0_r22223
ソース
lifegame.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Lifegame</title> <link rel="stylesheet" href="lifegame.css"> </head> <body> <h1>Lifegame</h1> <canvas id="world" useMutation="1"></canvas> <div> <button id="btn_start">Start</button> <button id="btn_stop">Stop</button> <button id="btn_restart">Restart</button> </div> <script type="application/dart" src="lifegame.dart"></script> <script src="packages/browser/dart.js"></script> </body> </html>
lifegame.css
body { background-color: #F8F8F8; font-family: 'Open Sans', sans-serif; font-size: 14px; font-weight: normal; line-height: 1.2em; margin: 15px; } h1, p { color: #333; } #world { background-color:white; }
lifegame.dart
import 'dart:html'; import 'dart:math'; import 'dart:async'; void main() { var model = new LifeWorldModel("#world", 35, 35); var pixell = new LifeWorldCellBase(10); var view = new LifeWorldView(model, pixell); view.setupButton("#btn_start", "#btn_stop", "#btn_restart"); } class Color { final int _r, _g, _b; get r => this._r; get g => this._g; get b => this._b; Color(this._r, this._g, this._b); String toString() => "rgb($r,$g,$b)"; } class LifeCell { final int _x, _y; get x => this._x; get y => this._y; bool _live; get live => this._live; set live(value) { this._live = value; } LifeCell(this._x, this._y, [bool live = false]) { this._live = live; } String toString() => this.live ? "■" : "□"; } class LifeWorldModel { List<LifeCell> cells = new List<LifeCell>(); final String name; final int _width, _height; get width => this._width; get height => this._height; bool _closed = true; get closed => this._closed; set closed(value) { this._closed = value; } LifeWorldModel(this.name, this._width, this._height) { for(var x=0; x<this._width; x++) { for(var y=0; y<this._height; y++) { var cell = new LifeCell(x, y); this.cells.add(cell); } } this.randomInit(); } List<LifeCell> getNeighbors(int x, int y) { if(this.closed) { return this.getNeighborsClosed(x, y); } else { return this.getNeighborsOpened(x, y); } } /** * 端が閉じているモデル。 * */ List<LifeCell> getNeighborsClosed(int x, int y) { if(x == 0 || y == 0 || x == this.width - 1 || y == this.height - 1) { return this.cells.where( (LifeCell c) { bool xJudge; bool yJudge; if ((c.x == x && c.y == y) == false) { if (x == 0) { xJudge = (width - 1 <= c.x || c.x <= x + 1); } else if(x == width - 1) { xJudge = (x - 1 <= c.x || c.x <= 0); } else { xJudge = (x - 1 <= c.x && c.x <= x + 1); } if (y == 0) { yJudge = (height - 1 <= c.y || c.y <= y + 1); } else if(y == height - 1) { yJudge = (y - 1 <= c.y || c.y <= 0); } else { yJudge = (y - 1 <= c.y && c.y <= y + 1); } return ( xJudge && yJudge ); } else { return false; } } ).toList(); } else { return this.getNeighborsOpened(x, y); } } /** * 端が閉じていないモデル。 * */ List<LifeCell> getNeighborsOpened(int x, int y) => this.cells.where( (LifeCell c) => (c.x == x && c.y == y) == false && (x - 1 <= c.x && c.x <= x + 1) && (y - 1 <= c.y && c.y <= y + 1) ).toList(); /** * ランダムにセルを生成。 * */ void randomInit([int seed = null]) { Random r; if(seed == null) { r = new Random(seed); } else { r = new Random(); } var max = this.cells.length; for(var i=0; i<max; i++) { this.cells[i].live = (r.nextInt(100) % 2 == 1) ? true : false; } } /** * 世代を進めて、残りライフを返す。 * */ int step() { int max = this.cells.length; List<LifeCell> newCells = []; int lifeCount = 0; this.cells.forEach( (LifeCell thiscell) { List<LifeCell> neighbors = this.getNeighbors(thiscell.x, thiscell.y); int liveCount = 0; int deafCount = 0; bool live = false; if(neighbors.length > 0) { neighbors.forEach((c) { liveCount += c.live ? 1 : 0; deafCount += c.live ? 0 : 1; }); if(thiscell.live) { if(liveCount == 2 || liveCount == 3) { live = true; } else if(liveCount <= 1) { live = false; } else if(liveCount >= 4) { live = false; } } else { if(liveCount == 3) { live = true; } } } newCells.add(new LifeCell(thiscell.x, thiscell.y, live)); if(live) lifeCount++; } ); this.cells = newCells; return lifeCount; } String toString() { for(int y=0; y<this._height; y++) { StringBuffer sb = new StringBuffer(); var cells = this.cells.where((c) => c.y == y) .forEach((LifeCell c) => sb.write(c)); if(sb.length > 0) print(sb); } } } class LifeWorldCellBase { final int cellPixell; LifeWorldCellBase(this.cellPixell); } class LifeWorldView { final LifeWorldModel world; final LifeWorldCellBase cellBase; CanvasElement canvas; CanvasRenderingContext2D graphics; Color deadCellBgColor = new Color(255, 255, 255); Color deadCellBorderColor = new Color(192, 192, 192); Color liveCellBgColor = new Color(0, 0, 0); Color liveCellBorderColor = new Color(0, 0, 0); LifeWorldView(this.world, this.cellBase) { this.canvas = document.query(this.world.name); this.canvas.width = this.world.width * this.cellBase.cellPixell; this.canvas.height = this.world.height * this.cellBase.cellPixell; this.graphics = this.canvas.getContext("2d"); } List<Color> getLiveCellColor() => [this.liveCellBgColor, this.liveCellBorderColor]; List<Color> getDeadCellColor() => [this.deadCellBgColor, this.deadCellBorderColor]; void createCell(int x, int y, Color bgColor, Color borderColor) { int basex = x * this.cellBase.cellPixell; int basey = y * this.cellBase.cellPixell; int w = this.cellBase.cellPixell; this.graphics.fillStyle = bgColor.toString(); this.graphics.fillRect(basex, basey, w, w); this.graphics.beginPath(); this.graphics.moveTo(basex, basey); this.graphics.lineTo(w + basex, basey); this.graphics.lineTo(w + basex, w + basey); this.graphics.lineTo(basex, w + basey); this.graphics.lineTo(basex, basey); this.graphics.strokeStyle = borderColor.toString(); this.graphics.lineWidth = 1; this.graphics.stroke(); } void createDefaultCells(int width, int height) { var deadCellColor = this.getDeadCellColor(); Color bgColor = deadCellColor[0]; Color borderColor = deadCellColor[1]; for(var i=0; i<width; i++){ for(var j=0; j<height; j++){ this.createCell(i, j, bgColor, borderColor); } } } void show() { var max = this.world.cells.length; var deadColor = this.getDeadCellColor(); var liveColor = this.getLiveCellColor(); for(var i=0; i<max; i++){ var color; var cell = this.world.cells[i]; if(cell.live){ color = liveColor; } else{ color = deadColor; } this.createCell(cell.x, cell.y, color[0], color[1]); } } int step() { return this.world.step(); } bool _workable = false; get workable => this._workable; set workable(value) { this._workable = value; } Timer timer; void start() { if(this.workable) { timer = new Timer(const Duration(milliseconds: 100), (){ this.show(); int lifeCount = this.step(); if(lifeCount > 0) { this.start(); } }); } } void stop(){ this.workable = false; //if(timer != null) timer.cancel(); } void setupButton(String startButtonName, String stopButtonName, String resetButtonName){ ButtonElement btnStart = document.query(startButtonName); ButtonElement btnStop = document.query(stopButtonName); ButtonElement btnReset = document.query(resetButtonName); // イベント設定 btnStart.onClick.listen((e){ this.workable = true; this.start(); btnStart.attributes["disabled"] = "true"; btnStop.attributes.containsKey("disabled"); btnStop.attributes.remove("disabled"); }); btnStop.onClick.listen((e){ this.workable = false; this.stop(); btnStart.attributes.containsKey("disabled"); btnStart.attributes.remove("disabled"); btnStop.attributes["disabled"] = "true"; }); btnReset.onClick.listen((e) { this.world.randomInit(); this.createDefaultCells(this.world.width, this.world.height); btnStart.click(); }); btnStop.attributes["disabled"] = "true"; } }
結論
やっぱりDartとして動かすときびきびしていて、JavaScriptに変換して動かすと多少もっさりしている。
文法自体はストレスが少なくていいけど、ドキュメントが(発展途中ということもあって)少なめなのがよろしくない。
とはいえ、DartEditorでクラスメソッドの定義なんかも追いやすいので、そんなに困らないかも。普通にAPIリファレンス追ってもいいし(http://api.dartlang.org/docs/releases/latest/)。