Skip to content

Instantly share code, notes, and snippets.

@mokele
Created September 23, 2012 17:16
Show Gist options
  • Save mokele/3772339 to your computer and use it in GitHub Desktop.
Save mokele/3772339 to your computer and use it in GitHub Desktop.
-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