Created
September 23, 2012 17:16
-
-
Save mokele/3772339 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-module(goban). | |
-export([ | |
init/0, | |
init/2, | |
set_point_state/2, | |
place_stone_at/5, | |
move/2, | |
undo_move/2, | |
key/1, | |
size/1, | |
points/1, | |
stone_at/3, | |
to_listlist/1, ascii/1, | |
set_from_listlist/2, | |
writable/1, | |
loadable/2, | |
clear/1, | |
point_key/3, more/3, | |
left/2, right/2, up/2, down/2, | |
connected_points/3 | |
]). | |
-include("goban.hrl"). | |
-export_type([ | |
goban/0, | |
snapshot/0 | |
]). | |
-type snapshot() :: list(#point_state{}). | |
init() -> | |
tabletable(). | |
tabletable() -> | |
ets:new(?TT, [ | |
named_table, | |
set, | |
public | |
]). | |
init(Key, Size) when Size =:= 9; Size =:= 13; Size =:= 19 -> | |
{ok, #goban{ | |
key = Key, | |
size = Size | |
}}; | |
init(_, _) -> | |
{error, board_size_not_supported}. | |
writable(#goban{ | |
key = Key, | |
size = Size | |
}) -> #goban{ | |
key = Key, | |
size = Size | |
}. | |
loadable(Goban, LoadFun) -> | |
% only callable via a single match process | |
% or else they will trip each other up due to shared ets table | |
Goban#goban{ | |
load_fun = LoadFun | |
}. | |
key(#goban{key = Key}) -> Key. | |
size(#goban{size = Size}) -> Size. | |
point_key(#goban{key = Key}, X, Y) -> | |
{Key, X, Y}. | |
set_from_listlist(T, LL) -> | |
set_from_listlist(T, LL, 1, 1). | |
set_from_listlist(T, [[]], _, _) -> | |
{ok, T}; | |
set_from_listlist(T, [[]|TY], _, Y) -> | |
set_from_listlist(T, TY, 1, Y + 1); | |
set_from_listlist(T, [[H|TX]|TY], X, Y) -> | |
PointKey = point_key(T, X, Y), | |
case H of | |
empty -> ok; | |
_ -> | |
ets:insert(?TT, {PointKey, H}) | |
end, | |
set_from_listlist(T, [TX|TY], X + 1, Y). | |
move(Goban, #move{ | |
stone = Stone, | |
point = #point{x = X, y = Y}, | |
number = MoveNumber | |
}) -> | |
{ok, Goban, _} = place_stone_at(Goban, Stone, X, Y, MoveNumber); | |
move(Goban, Move) -> | |
{ok, Goban, Move}. | |
place_stone_at(#goban{load_fun = F} = Goban, Stone, X, Y, MoveNumber) | |
when is_function(F) -> | |
place_stone_at(load_fun(Goban), Stone, X, Y, MoveNumber); | |
place_stone_at(#goban{size = Size}, _, X, Y, _MoveNumber) | |
when X < 1; Y < 1; X > Size; Y > Size -> | |
{error, out_of_bounds}; | |
place_stone_at(T, Stone, X, Y, MoveNumber) -> | |
PointKey = point_key(T, X, Y), | |
case ets:lookup(?TT, PointKey) of | |
[] -> | |
ets:insert(?TT, {PointKey, Stone}), | |
check(T, Stone, PointKey, X, Y, MoveNumber); | |
[_] -> | |
{error, not_empty} | |
end. | |
undo_move(_, #move{is_pass = true}) -> | |
ok; | |
undo_move(T, #move{ | |
stone = Stone, | |
point = #point{x = X, y = Y}, | |
taken_points = ClearedPoints | |
}) -> | |
PointKey = point_key(T, X, Y), | |
ets:delete(?TT, PointKey), | |
ClearedStone = go:opposite_stone(Stone), | |
lists:foreach( | |
fun(#point{x = ClearedX, y = ClearedY}) -> | |
ClearedPointKey = point_key(T, ClearedX, ClearedY), | |
ets:insert(?TT, {ClearedPointKey, ClearedStone}) | |
end, | |
ClearedPoints | |
), | |
ok. | |
check(T, Stone, PointKey, X, Y, MoveNumber) -> | |
Directions = [up(T, PointKey), right(T, PointKey), down(T, PointKey), left(T, PointKey)], | |
{Stone, OwnLiberties, _} = connected_points(T, PointKey, X, Y), | |
ClearedPoints = [], | |
check(T, Stone, PointKey, MoveNumber, OwnLiberties, ClearedPoints, Directions). | |
check(T, Stone, PointKey, MoveNumber, OwnLiberties, ClearedPoints, []) -> | |
if | |
OwnLiberties =:= 0 andalso ClearedPoints =:= [] -> | |
ets:delete(?TT, PointKey), | |
{error, suicide}; | |
true -> | |
{_, X, Y} = PointKey, | |
{ok, T, #move{ | |
number = MoveNumber, | |
stone = Stone, | |
point = #point{x = X, y = Y}, | |
taken_points = ClearedPoints | |
}} | |
end; | |
check(T, Stone, PointKey, MoveNumber, OwnLiberties, ClearedPoints, [undefined|Tail]) -> | |
check(T, Stone, PointKey, MoveNumber, OwnLiberties, ClearedPoints, Tail); | |
check(T, Stone, PointKey, MoveNumber, OwnLiberties, ClearedPoints0, [NextPointKey|Tail]) -> | |
{_, X, Y} = NextPointKey, | |
{NextStone, Liberties, ConnectedPoints} = connected_points(T, NextPointKey, X, Y), | |
ClearedPoints = | |
if | |
Liberties =:= 0, Stone =/= NextStone, NextStone =/= undefined -> | |
clear_points(T, ConnectedPoints, ClearedPoints0); | |
true -> | |
ClearedPoints0 | |
end, | |
check(T, Stone, PointKey, MoveNumber, OwnLiberties, ClearedPoints, Tail). | |
clear_points(_, [], ClearedPoints) -> | |
ClearedPoints; | |
clear_points(T, [XY|Tail], ClearedPoints) -> | |
#point{x = X, y = Y} = XY, | |
PointKey = point_key(T, X, Y), | |
ets:delete(?TT, PointKey), | |
clear_points(T, Tail, [XY|ClearedPoints]). | |
connected_points(T, X, Y) -> | |
PointKey = point_key(T, X, Y), | |
connected_points(T, PointKey, X, Y). | |
connected_points(T, PointKey, X, Y) -> | |
XY = #point{x = X, y = Y}, | |
Directions = [up(T, PointKey), right(T, PointKey), down(T, PointKey), left(T, PointKey)], | |
ConnectedPoints = [XY], | |
Seen = [XY], | |
Liberties = 0, | |
Stone = | |
case ets:lookup(?TT, PointKey) of | |
[] -> undefined; | |
[{PointKey, Stone0}] -> Stone0 | |
end, | |
connected_points(T, Stone, Liberties, Seen, ConnectedPoints, Directions). | |
connected_points(_, Stone, Liberties, _Seen, ConnectedPoints, []) -> | |
{Stone, Liberties, ConnectedPoints}; | |
connected_points(T, Stone, Liberties, Seen0, ConnectedPoints, [PointKey|Tail]) -> | |
case PointKey of | |
undefined -> | |
connected_points(T, Stone, Liberties, Seen0, ConnectedPoints, Tail); | |
{_Key, X, Y} -> | |
XY = #point{x = X, y = Y}, | |
case lists:member(XY, Seen0) of | |
true -> | |
connected_points(T, Stone, Liberties, Seen0, ConnectedPoints, Tail); | |
false -> | |
Seen = [XY|Seen0], | |
case ets:lookup(?TT, PointKey) of | |
[] when Stone =:= undefined -> | |
connected_points(T, Stone, Liberties, Seen, | |
[XY|ConnectedPoints], | |
more([up(T, PointKey), right(T, PointKey), down(T, PointKey), left(T, PointKey)], Seen, Tail) | |
); | |
[{PointKey, Stone}] -> | |
connected_points(T, Stone, Liberties, Seen, | |
[XY|ConnectedPoints], | |
more([up(T, PointKey), right(T, PointKey), down(T, PointKey), left(T, PointKey)], Seen, Tail) | |
); | |
[] -> | |
connected_points(T, Stone, Liberties + 1, Seen, ConnectedPoints, Tail); | |
[_] -> | |
connected_points(T, Stone, Liberties, Seen, ConnectedPoints, Tail) | |
end | |
end | |
end. | |
more([], _, Directions) -> | |
Directions; | |
more([undefined|T], Seen, Directions) -> | |
more(T, Seen, Directions); | |
more([{_, _X, _Y} = H|T], Seen, Directions) -> | |
more(T, Seen, [H|Directions]). | |
%XY = {X, Y}, | |
%case lists:member(XY, Seen) of % todo: improve this | |
% true -> | |
% more(T, Seen, Directions); | |
% false -> | |
% more(T, Seen, [H|Directions]) | |
%end. | |
up(_, {_, _, 1}) -> undefined; | |
up(_, {Key, X, Y}) -> {Key, X, Y - 1}. | |
down(#goban{size = Size}, {_, _, Size}) -> undefined; | |
down(_, {Key, X, Y}) -> {Key, X, Y + 1}. | |
left(_, {_, 1, _}) -> undefined; | |
left(_, {Key, X, Y}) -> {Key, X - 1, Y}. | |
right(#goban{size = Size}, {_, Size, _}) -> undefined; | |
right(_, {Key, X, Y}) -> {Key, X + 1, Y}. | |
points(#goban{load_fun = F} = T) when is_function(F) -> | |
points(load_fun(T)); | |
points(T) -> | |
points(T, 1, 1, []). | |
points(#goban{size = Size}, C, C, L) when C =:= Size + 1 -> | |
L; | |
points(#goban{size = Size} = T, X, Y, L) when X =:= Size + 1 -> | |
points(T, 1, Y + 1, L); | |
points(T, X, Y, L) -> | |
PointKey = point_key(T, X, Y), | |
case ets:lookup(?TT, PointKey) of | |
[] -> | |
points(T, X + 1, Y, L); | |
[{PointKey, Stone}] -> | |
points(T, X + 1, Y, [#point_state{ | |
stone = Stone, | |
x = X, y = Y | |
}|L]) | |
end. | |
to_listlist(#goban{load_fun = F} = T) when is_function(F) -> | |
to_listlist(load_fun(T)); | |
to_listlist(T) -> | |
ll(T, 1, 1, [[]]). | |
ll(#goban{size = Size}, C, C, [_|LL]) when C =:= Size + 1 -> | |
lists:reverse(LL); | |
ll(#goban{size = Size} = T, X, Y, [L|Tail]) when X =:= Size + 1 -> | |
ll(T, 1, Y + 1, [[],lists:reverse(L)|Tail]); | |
ll(T, X, Y, [L|Tail]) -> | |
PointKey = point_key(T, X, Y), | |
Cell = | |
case ets:lookup(?TT, PointKey) of | |
[] -> empty; | |
[{PointKey, Stone}] -> Stone | |
end, | |
ll(T, X + 1, Y, [[Cell|L]|Tail]). | |
ascii(#goban{} = Goban) -> | |
ascii(to_listlist(Goban)); | |
ascii([H|T]) -> | |
ascii(H, T, ""). | |
ascii([], [], Acc) -> | |
lists:reverse(Acc); | |
ascii([], [H|T], Acc) -> | |
ascii(H, T, ["\n"|Acc]); | |
ascii([H|T], Rows, Acc) -> | |
ascii(T, Rows, [ascii_point(H)|Acc]). | |
ascii_point(?BLACK) -> "B "; | |
ascii_point(?WHITE) -> "W "; | |
ascii_point('BLACK_TERRITORY') -> "b "; | |
ascii_point('WHITE_TERRITORY') -> "w "; | |
ascii_point('DAME') -> "d "; | |
ascii_point('SEKI') -> "- "; | |
ascii_point('WHITE_DEAD') -> "o "; | |
ascii_point('BLACK_DEAD') -> "* "; | |
ascii_point(_) -> ". ". | |
load_fun(#goban{load_fun = F} = Goban) -> | |
clear(Goban), | |
F(Goban#goban{ | |
load_fun = undefined | |
}). | |
clear(#goban{size = Size} = Goban) -> | |
L = lists:seq(1, Size), | |
lists:foreach( | |
fun(X) -> | |
lists:foreach( | |
fun(Y) -> | |
PointKey = point_key(Goban, X, Y), | |
ets:delete(?TT, PointKey) | |
end, | |
L | |
) | |
end, | |
L | |
). | |
stone_at(T, X, Y) -> | |
PointKey = point_key(T, X, Y), | |
case ets:lookup(?TT, PointKey) of | |
[] -> undefined; | |
[{PointKey, Stone}] -> Stone | |
end. | |
set_point_state(T, #point_state{ | |
x = X, y = Y, | |
stone = Stone | |
}) -> | |
PointKey = point_key(T, X, Y), | |
ets:insert(?TT, {PointKey, Stone}). |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment