はじめに
関数型言語 Mokkosu で LifeGame を作ってみました。
ソースコード
LifeGame.mok
#=============================================================================
#! @file LifeGame.mok
#! @brief Conway's Game of Life
#! @author kielnow
#=============================================================================
#__define "CONSOLE_APPLICATION";
include "Print.mok";
include "List.mok";
include "Graphics.mok";
using System;
using System.Collections;
#-----------------------------------------------------------------------------
# AFX
#-----------------------------------------------------------------------------
let object_to_int (o : {Object}) = cast<Object,Int32>(o);
let object_to_bool (o : {Object}) = cast<Object,Boolean>(o);
let int_to_object (o : Int) = cast<Int32,Object>(o);
let bool_to_object (o : Bool) = cast<Boolean,Object>(o);
let is_nil = {
[] -> true;
_ -> false;
};
fun map f = {
[] -> [];
x::xs -> f x :: map f xs;
};
fun concat = {
[] -> [];
x::xs -> x ++ concat xs;
};
let concat_map f lis = concat (map f lis);
let __for_bind lis f = concat_map f lis;
#-----------------------------------------------------------------------------
# Global constants
#-----------------------------------------------------------------------------
let (screenWidth,screenHeight) = (800,600);
let fps = 30;
let fpsInv = 1000 / fps;
let title = "Mokkosu Life Game";
let mokkosu_brush = new_solid_brush 103 128 170;
let (cellWidth,cellHeight) = (10,10);
let (fieldWidth,fieldHeight) = (screenWidth/cellWidth,screenHeight/cellHeight);
let fieldSize = fieldWidth * fieldHeight;
let lifeNum = fieldSize * 4/10;
#-----------------------------------------------------------------------------
# Global states
#-----------------------------------------------------------------------------
let random = new Random();
let fieldAlive = new ArrayList(fieldWidth*fieldHeight);
let fieldDensity = new ArrayList(fieldWidth*fieldHeight);
let counter = ref 0;
let inverse = ref false;
#-----------------------------------------------------------------------------
# Miscellaneous functions
#-----------------------------------------------------------------------------
let pack x y = y * fieldWidth + x;
let getFieldAlive (index : Int) = object_to_bool fieldAlive.get_Item(index);
let setFieldAlive (index : Int) alive = fieldAlive.set_Item(index, bool_to_object alive);
let getFieldDensity (index : Int) = object_to_int fieldDensity.get_Item(index);
let setFieldDensity (index : Int) density = fieldDensity.set_Item(index, int_to_object density);
#-----------------------------------------------------------------------------
# Life game
#-----------------------------------------------------------------------------
fun generateLife = {
0 -> ();
n ->
let index = random.Next(fieldSize) in
if getFieldAlive index -> generateLife n
else do
setFieldAlive index true;
generateLife (n-1);
end;
};
let calcDensity () =
let stencil =
[(fieldWidth-1,fieldHeight-1),(0,fieldHeight-1),(1,fieldHeight-1),
(fieldWidth-1,0),(1,0),
(fieldWidth-1,1),(0,1),(1,1)]
in ignore (
for y <- 0 .. (fieldHeight-1);
x <- 0 .. (fieldWidth-1);
in let pos = map {(dx,dy) -> ((x+dx)%fieldWidth, (y+dy)%fieldHeight)} stencil
in let density = sum <| map {p -> if getFieldAlive (uncurry pack p) -> 1 else 0} pos
in do
setFieldDensity (pack x y) density;
end
);
let stepLife () = ignore (
for index <- 0 .. (fieldSize-1)
in let alive = getFieldAlive index
in let density = getFieldDensity index
in do
if (not alive) && density == 3 -> setFieldAlive index true
else if alive && (density < 2 || 3 < density) -> setFieldAlive index false
else ();
end
);
#-----------------------------------------------------------------------------
# Scene: init
#-----------------------------------------------------------------------------
let tick_init () = do
println "tick_init";
counter := 0;
fieldAlive.Clear();
fieldDensity.Clear();
println "clear";
iter (\index ->
do
fieldAlive.Insert((index : Int), bool_to_object false);
fieldDensity.Insert((index : Int), int_to_object 0);
end
) <| 0 .. (fieldSize-1);
println "init";
generateLife lifeNum;
calcDensity ();
switch "main";
end;
#-----------------------------------------------------------------------------
# Scene: main
#-----------------------------------------------------------------------------
let tick_main () = do
if !counter == 0 -> println "tick_main" else ();
counter := 1 + !counter;
stepLife ();
calcDensity ();
redraw ();
end;
let draw_main gr =
let (fg,bg) = if !inverse -> (white_brush,mokkosu_brush) else (mokkosu_brush,white_brush) in
do fill_rectangle gr mokkosu_brush 0 0 screenWidth screenHeight;
ignore (
for y <- 0 .. (fieldHeight-1);
x <- 0 .. (fieldWidth-1);
in let index = pack x y
in let alive = getFieldAlive index
in let density = getFieldDensity index
in let brush = if alive -> fg else bg
in do
fill_rectangle gr brush (x*cellWidth) (y*cellHeight) (cellWidth-1) (cellHeight-1);
end
);
end;
#-----------------------------------------------------------------------------
# Main
#-----------------------------------------------------------------------------
let main () = do
# setup window
set_size screenWidth screenHeight;
set_title title;
set_speed fpsInv;
# setup scene: init
scene "init" {
~Tick -> tick_init ();
_ -> ()
};
# setup scene: main
scene "main" {
~Tick -> tick_main ();
~Draw(gr) -> draw_main gr;
~MouseUp(RightButton,_,_) -> switch "init";
~MouseUp(LeftButton,_,_) -> inverse := not !inverse;
_ -> ()
};
# startup
show_window "init";
end;
#-----------------------------------------------------------------------------
# Entry point
#-----------------------------------------------------------------------------
do main ();
解説
1. 標準ライブラリ関数の置き換えています
最新の Mokkosu 1.3.2 では、標準ライブラリ関数の実装が末尾再帰版になっているようなのですが、~~コンパイラの最適化が不十分なようで細かいクロージャが大量に生成されて激重なので、~~LifeGame を快適に動かすには少々重たいので標準ライブラリ関数の実装を非末尾再帰版に置き換えています。もちろんスタックオーバーフローする恐れはありますが LifeGame が快適に動かなかったので仕方がありません。今後のバージョンアップに期待しましょう。
途中の __for_bind
は for 式を使用した際に内部的に使用される関数です。いわゆるリストモナドの bind に相当するものです。
2. .NETのArrayListを使っています
LifeGame のセルの情報は、Mokkosu 標準のリストなどのデータ構造を使用すると激重なのは容易に想像がついたので、.NET の ArrayList に格納しています。
.NET のクラスを Mokkosu から使用するには結構頑張らないといけないです。今後のバージョンアップに期待しましょう。
感想
.NET のクラスを普通に使用することができて Mokkosu すごい!と思いました。