%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
-module(othello_board).
-export([start/0,stop/0,init/0]).
%%----------------------------------------------------------------------
%% The Othello program now uses the gs graphical package instead of the
%% pxw package.
%%
%% Differences are, explanation why and source of change in parenthesis:
%%
%% - Buttons looks different (gs feature)
%% - The black box around "Black to draw" have been removed. (me)
%% - The Colour and Level menues have been moved directly down to the
%% 'Status' box. (usability update, my addition)
%% - The mouse pointer does not change into a watch when the computer
%% is thinking (not supported in gs)
%% - Buttons does not flash when being beeped. (not supported in gs)
%%
%%
%% /Peter
%%
%%----------------------------------------------------------------------
-define(BGCOL,forestgreen).
start() ->
spawn(othello_board,init,[]).
stop() ->
ok.
%% This is not an application so we don't have their way of knowing
%% a private data directory where the GIF files are located (this directory).
%% We can find GS and makes it relative from there /kgb
-define(BitmapPath,"../contribs/othello/priv").
setup_path() ->
GsPrivDir = code:priv_dir(gs),
Path = filename:join(GsPrivDir,?BitmapPath),
put(path,Path).
path() -> get(path).
%%
%% The button area are the Quit, Rules buttons at the top of the window.
%% The Status area is the black and white scores level and colour etc
%% inbetween the buttons and the othello board.
%% The board is the 8x8 board where othello battles are fought.
%%
init() ->
process_flag(trap_exit,true),
setup_path(),
S = gs:start(),
put(windowroot,S), % Ugly global store
%% Shell coordinates
W = 496,
H = 636,
%% Fix top window
Shell = gs:create(window, S, [{title,"Othello"},
{width, W},{height, H}]),
%% Setup window contents
setup_buttons(Shell,0,0,W,40), % Fix Menubar
setup_status_box(Shell,0,40,W,100), % Fix Status area
setup_board(Shell,0,140,496,496), % Combat board
GamePid = othello:new_game(white,black,1,first_time),
%% Default settings
Options = {white,black,1},
%%Wids = {Status,B,W,Dr,Le,Co},
Wids = {change,this,at,later,stage,ponto},
write_options(Options,Wids),
gs:config(Shell, {map, true}), %Make win visible
loop(computer,GamePid,Shell,Wids,Options).
loop(User,GamePid,Shell,Wids,Options) ->
receive
{gs,ButtId, click,_ButtId1,[Button]} ->
GamePid1 = but_pressed(Button,ButtId,User,GamePid,Shell,
Wids,Options),
loop(User,GamePid1,Shell,Wids,Options);
{gs,_, click,_,[MenuItem,_MenuIndex]} ->
Ops = menu_selected(MenuItem,User,GamePid,Wids,Options),
loop(User,GamePid,Shell,Wids,Ops);
{'EXIT',GamePid,_} ->
loop(User,null,Shell,Wids,Options);
{'EXIT',_,_} ->
loop(User,GamePid,Shell,Wids,Options);
GameMsg ->
game_msg(GameMsg,User,GamePid,Shell,Wids,Options)
end.
but_pressed("Quit",_ButtId,_User,_GamePid,_Shell,_Wids,_Op) ->
stop(),
exit(quit);
but_pressed("Rules",_ButtId,_User,GamePid,_Shell,_Wids,_Op) ->
io:format("No rules, do as you wish~n",[]),
GamePid;
but_pressed("Help",_ButtId,_User,GamePid,_Shell,_Wids,_Op) ->
io:format("Othello game~n",[]),
io:format("------------~n",[]),
io:format(" Put markers by clicking in squares~n",[]),
io:format(" Change level by clicking on it~n",[]),
io:format(" Change colour by clicking on it~n",[]),
io:format("~n",[]),
GamePid;
but_pressed("Newgame",_ButtId,_User,GamePid,_Shell,Wids,Options) ->
new_game(GamePid,Wids,Options);
but_pressed([],ButtId,User,GamePid,_Shell,_Wids,_Op)
when pid(GamePid),User == player ->
[C,R] = atom_to_list(ButtId),
GamePid ! {self(),position,othello_adt:pos(C-96,translate(R-48))},
GamePid;
but_pressed([],ButtId,_User,GamePid,_Shell,_Wids,_Op) ->
[C,R] = atom_to_list(ButtId),
beep(othello_adt:pos(C-96,translate(R-48))),
GamePid;
but_pressed(Button,ButtId,_User,GamePid,_Shell,_Wids,_Op) ->
io:format('Not implemented button pressed ~p, ~p!!!~n',[ButtId,Button]),
GamePid.
menu_selected("Black",_User,_GamePid,Wids,Options) ->
Op0 = setelement(1,Options,white),
Op1 = setelement(2,Op0,white),
write_options(Op1,Wids),
Op1;
menu_selected("White",_User,_GamePid,Wids,Options) ->
Op0 = setelement(1,Options,black),
Op1 = setelement(2,Op0,black),
write_options(Op1,Wids),
Op1;
menu_selected("Black (begin)",_User,_GamePid,Wids,Options) ->
Op0 = setelement(1,Options,white),
Op1 = setelement(2,Op0,black),
write_options(Op1,Wids),
Op1;
menu_selected("White (begin)",_User,_GamePid,Wids,Options) ->
Op0 = setelement(1,Options,black),
Op1 = setelement(2,Op0,white),
write_options(Op1,Wids),
Op1;
menu_selected("Beginner",_User,_GamePid,Wids,Options) ->
Op1 = setelement(3,Options,1),
write_options(Op1,Wids),
Op1;
menu_selected("Intermediate",_User,_GamePid,Wids,Options) ->
Op1 = setelement(3,Options,2),
write_options(Op1,Wids),
Op1;
menu_selected("Advanced",_User,_GamePid,Wids,Options) ->
Op1 = setelement(3,Options,3),
write_options(Op1,Wids),
Op1;
menu_selected("Expert",_User,_GamePid,Wids,Options) ->
Op1 = setelement(3,Options,4),
write_options(Op1,Wids),
Op1;
menu_selected(What,_User,_GamePid,_Wids,Options) ->
io:format('Menu item not implemented <~s>~n',[What]),
Options.
game_msg(Msg,User,GamePid,Shell,Wids,Options) ->
case Msg of
{GamePid,new_mark,Pos,Colour} ->
new_mark(Pos,Colour),
loop(User,GamePid,Shell,Wids,Options);
{GamePid,illegal_draw,Draw} ->
beep(Draw),
loop(User,GamePid,Shell,Wids,Options);
{GamePid,player,Computer,Computer} ->
show_player(element(1,Wids),Computer),
cursor("watch"),
GamePid ! {self(),go_on_play},
loop(computer,GamePid,Shell,Wids,Options);
{GamePid,player,_Computer,Player} ->
show_player(element(1,Wids),Player),
cursor("top_left_arrow"),
GamePid ! {self(),go_on_play},
loop(player,GamePid,Shell,Wids,Options);
{GamePid,omit_draw,Player} ->
omit_draw(GamePid,Player),
loop(User,GamePid,Shell,Wids,Options);
{GamePid,score,WhiteRes,BlackRes} ->
write_score(Wids,WhiteRes,BlackRes),
loop(User,GamePid,Shell,Wids,Options);
{GamePid,draw,Draw} ->
write_draw(Wids,Draw),
loop(User,GamePid,Shell,Wids,Options);
{GamePid,game_over,WhiteRes,BlackRes} ->
game_over(WhiteRes,BlackRes),
loop(User,GamePid,Shell,Wids,Options);
What ->
io:format('game_msg received: ~w~n',[What]),
loop(User,GamePid,Shell,Wids,Options)
end.
new_game(GamePid,Wids,Options) when pid(GamePid) ->
exit(GamePid,kill),
new_game(Wids,Options);
new_game(_,Wids,Options) ->
new_game(Wids,Options).
new_game(_Wids,Options) ->
label("",lastdraw),
Computer = element(1,Options),
Start = element(2,Options),
Depth = element(3,Options),
othello:new_game(Computer,Start,Depth,restart).
new_mark(Pos,Colour) ->
Col = othello_adt:col(Pos),
Row = othello_adt:row(Pos),
Name = [Col+96,translate(Row)+48],
Button = get(Name),
butbit(Button,Colour).
beep(Draw) ->
Col = othello_adt:col(Draw),
Row = othello_adt:row(Draw),
Name = [Col+96,translate(Row)+48],
Button = get(Name),
bell(Button).
show_player(_Status,white) ->
label("White to draw",todraw);
show_player(_Status,black) ->
label("Black to draw",todraw).
write_score(_Wids,WhiteRes,BlackRes) ->
label(integer_to_list(BlackRes),bscore),
label(integer_to_list(WhiteRes),wscore).
write_draw(_Wids,Draw) ->
Col = othello_adt:col(Draw),
Row = othello_adt:row(Draw),
label(lists:flatten(io_lib:format('{~w,~w}',[Col,Row])), lastdraw).
write_options(Options,Wids) ->
write_colour(Options,Wids),
write_level(Options,Wids).
write_colour(Options,Wids) ->
write_colour(element(1,Options),element(2,Options),Wids).
write_colour(black,white,_Wids) -> label("White (begin)",colour);
write_colour(black,black,_Wids) -> label("White",colour);
write_colour(white,black,_Wids) -> label("Black (begin)",colour);
write_colour(white,white,_Wids) -> label("Black",colour).
write_level(Options,_Wids) ->
case element(3,Options) of
1 -> label("Beginner",level);
2 -> label("Intermediate",level);
3 -> label("Advanced",level);
4 -> label("Expert",level)
end.
cursor(_What) ->
done.
%cursor(What) -> cursor(get(),What).
%cursor([{[C,R],Button}|Buts],What) ->
% set_widget(Button,"cursor",What),
% cursor(Buts,What);
%cursor([_|Buts],What) ->
% cursor(Buts,What);
%cursor([],_) ->
% true.
translate(1) -> 8;
translate(2) -> 7;
translate(3) -> 6;
translate(4) -> 5;
translate(5) -> 4;
translate(6) -> 3;
translate(7) -> 2;
translate(8) -> 1.
bitmap(grey) -> bitmap_path("square.bm");
bitmap(black) -> bitmap_path("marker.bm");
bitmap(white) -> bitmap_path("marker.bm").
bitmap_path(Bitmap) ->
filename:join(path(),Bitmap).
xy_position([[Letter,Digit],_,_]) ->
LettPos = Letter - 97,
X = LettPos*60 ,
Y = (8 - list_to_integer([Digit])) * 60,
{X+6,Y+6};
xy_position(X) ->
io:format("xy_position: ~w~n",[{error,X}]).
board() ->
[["a1",grey,nil],
["b1",grey,nil],
["c1",grey,nil],
["d1",grey,nil],
["e1",grey,nil],
["f1",grey,nil],
["g1",grey,nil],
["h1",grey,nil],
["a2",grey,nil],
["b2",grey,nil],
["c2",grey,nil],
["d2",grey,nil],
["e2",grey,nil],
["f2",grey,nil],
["g2",grey,nil],
["h2",grey,nil],
["a3",grey,nil],
["b3",grey,nil],
["c3",grey,nil],
["d3",grey,nil],
["e3",grey,nil],
["f3",grey,nil],
["g3",grey,nil],
["h3",grey,nil],
["a4",grey,nil],
["b4",grey,nil],
["c4",grey,nil],
["d4",white,nil],
["e4",black,nil],
["f4",grey,nil],
["g4",grey,nil],
["h4",grey,nil],
["a5",grey,nil],
["b5",grey,nil],
["c5",grey,nil],
["d5",black,nil],
["e5",white,nil],
["f5",grey,nil],
["g5",grey,nil],
["h5",grey,nil],
["a6",grey,nil],
["b6",grey,nil],
["c6",grey,nil],
["d6",grey,nil],
["e6",grey,nil],
["f6",grey,nil],
["g6",grey,nil],
["h6",grey,nil],
["a7",grey,nil],
["b7",grey,nil],
["c7",grey,nil],
["d7",grey,nil],
["e7",grey,nil],
["f7",grey,nil],
["g7",grey,nil],
["h7",grey,nil],
["a8",grey,nil],
["b8",grey,nil],
["c8",grey,nil],
["d8",grey,nil],
["e8",grey,nil],
["f8",grey,nil],
["g8",grey,nil],
["h8",grey,nil]].
omit_draw(GamePid,Player) ->
% %% Find mouse coords first
% %% This was not possible in gs
W = 200, H = 100, Root = get(windowroot),
Box = gs:create(window, Root, [{title,"OMIT"}, {width, W},{height, H}]),
mk_label_c(lists:flatten(io_lib:format('~w has to omit draw !',[Player])),
Box, W, 10),
mk_button_c("Ok", Box, W, H-40, 80, 30),
gs:config(Box, {map, true}), %Make win visible
receive
{gs,_, click,_,["Ok"]} ->
gs:destroy(Box),
GamePid ! {self(),continue}
end.
game_over(WhiteRes,BlackRes) ->
% %% Find mouse coords first
% %% This was not possible in gs
W = 200, H = 160,
Root = get(windowroot),
Box = gs:create(window, Root, [{title,"GAME OVER"},
{width, W},{height, H}]),
mk_label_c("GAME OVER", Box, W, 10),
mk_label_c(lists:flatten(io_lib:format('White score: ~w',[WhiteRes])),
Box,W,40),
mk_label_c(lists:flatten(io_lib:format('Black score: ~w',[BlackRes])),
Box,W,70),
mk_button_c("Ok", Box, W, H-40, 80, 30),
gs:config(Box, {map, true}), %Make win visible
receive
{gs,_, click,_,["Ok"]} ->
gs:destroy(Box)
end.
%% ----------------------------------------------------------------
%% Library functions.
%% ----------------------------------------------------------------
bell(Widget) ->
%% gs does not support bells,
Widget.
label(Text,Label) ->
gs:config(Label,[{label,{text,Text}}]).
%% mk_label in centered version
mk_label_c(Label,Parent,Width,Y) ->
W = 8*length(Label),
X = trunc((Width-W)/2),
gs:create(label,Parent,[{width,W}, {height, 20}, {x,X}, {y,Y},
{label, {text, Label}}]).
setup_buttons(Shell,X,Y,W,H) ->
ButBox = gs:create(frame, Shell,[{x,X}, {y,Y},{bg,white},
{width,W}, {height,H}]),
C = gs:create(canvas,ButBox,[{x,X}, {y,Y}, {width, W}, {height, H},
{bg,white}]),
gs:create(line, C, [{coords, [{0,H-1},{W,H-1}]}]),
mk_button("Quit",ButBox, 10, 10, 70, 20),
mk_button("Rules",ButBox, 80, 10, 70, 20),
mk_button("Newgame",ButBox, 150, 10, 70, 20),
mk_button("Help",ButBox, 220, 10, 70, 20),
%% mk_button("Level",ButBox, 290, 10, 70, 20),
done.
%%----------------------------------------
%% Sets up the middle window w. all the status info in.
%% The labels are given names:
%% bscore, wscore, lastdraw, todraw, level and colour to simplify
%% their frequent setting
%%
setup_status_box(Shell,X,Y,W,H) ->
F = gs:create(frame, Shell,[{x,X}, {y,Y},
{width,W}, {height,H},{bg,white}]),
C = gs:create(canvas,F,[{x,0}, {y,0}, {width, W}, {height, H},{bg,white}]),
gs:create(line, C, [{coords, [{0,H-1},{W,H-1}]}]),
%% Left side
gs:create(label,F,[{align,w},{x,10}, {y,5}, {width, 100},
{label,{text, "Black score:"}},{bg,white}]),
gs:create(label,bscore,F,[{align,w},{x,110}, {y,5}, {width, 40},
{label,{text, "2"}},{bg,white}]),
gs:create(label,F,[{align,w},{x,10}, {y,35}, {width, 100},
{label,{text, "White score:"}},{bg,white}]),
gs:create(label,wscore,F,[{align,w},{x,110}, {y,35}, {width, 40},
{label,{text, "2"}},{bg,white}]),
gs:create(label,F,[{align,w},{x,10}, {y,65}, {width, 100},
{label,{text, "Last draw:"}},{bg,white}]),
gs:create(label,lastdraw,F,[{align,w},{x,110}, {y,65}, {width, 40},
{label,{text, ""}},{bg,white}]),
%% Right side
X2 = trunc(W/2)+10,
gs:create(label,todraw,F,[{align,w},{x,X2}, {y,5}, {width, 100},
{label,{text, "Black to draw:"}},{bg,white}]),
gs:create(label,F,[{align,w},{x,X2}, {y,35}, {width, 80},
{label,{text, "Level:"}},{bg,white}]),
setup_level_menu(F,X2+80,35),
%% gs:create(label,level,F,[{align,w},{x,X2+80}, {y,35}, {width, 130},
%% {label,{text, "Intermediate"}},{bg,white}]),
gs:create(label,F,[{align,w},{x,X2}, {y,65}, {width, 80},
{label,{text, "Colour:"}},{bg,white}]),
setup_col_menu(F,X2+80,65),
%% gs:create(label,colour,F,[{align,w},{x,X2+80}, {y,65}, {width, 120},
%% {label,{text, "black (begin)"}},{bg,white}]),
done.
setup_col_menu(P,X,Y) ->
MB = gs:create(menubutton,colour,P,
[{x,X}, {y,Y}, {bw,3},
%%{width,W}, {height,H},
{align,w}, {bg,white},
{relief, raised},
{activefg,white}, {activebg,black},
{label, {text,"Colours"}}]),
M = gs:create(menu,MB,[]),
gs:create(menuitem,M,[{label,{text,"Black (begin)"}}]),
gs:create(menuitem,M,[{label,{text,"Black"}}]),
gs:create(menuitem,M,[{label,{text,"White (begin)"}}]),
gs:create(menuitem,M,[{label,{text,"White"}}]),
done.
setup_level_menu(P,X,Y) ->
MB = gs:create(menubutton,level,P,
[{x,X}, {y,Y},
%%{width,W}, {height,H},
{relief, raised},
{activefg,white}, {activebg,black},
{align,w}, {bg,white},
{label, {text,"Colours"}}]),
M = gs:create(menu,MB,[]),
gs:create(menuitem,M,[{label,{text,"Beginner"}}]),
gs:create(menuitem,M,[{label,{text,"Intermediate"}}]),
gs:create(menuitem,M,[{label,{text,"Advanced"}}]),
gs:create(menuitem,M,[{label,{text,"Expert"}}]),
done.
setup_board(Shell,X,Y,W,H) ->
F = gs:create(frame, Shell,[{x,X}, {y,Y},
{width,W}, {height,H},{bg,white}]),
display_board(F).
mk_button(Label, Parent, X, Y, W, H) ->
gs:create(button,Parent,[{width,W}, {height, H}, {x,X}, {y,Y},
{label, {text, Label}}, {bg,white},
{activefg,white}, {activebg,black}]).
%% Centers a button around Width
mk_button_c(Label, Parent, Width, Y, W, H) ->
X = trunc((Width-W)/2),
gs:create(button,Parent,[{width,W}, {height, H}, {x,X}, {y,Y},
{label, {text, Label}}, {bg,white}]).
butbit(Button,Col) ->
gs:config(Button,[
{label,{image,bitmap(Col)}},
{fg, Col},
{activefg,Col},
{label, {image, bitmap(Col)}}]),
Button.
mk_board_butt(Top,Name,X,Y,Col) ->
B = gs:create(button,list_to_atom(Name), Top,
[{x,X}, {y,Y}, {width,60}, {height,60},
{padx,5},{pady,5},
{relief,flat},
{bg,?BGCOL}, {activebg,?BGCOL}]),
butbit(B,Col),
B.
display_board(Top) ->
Board = board(),
display_board(Top,Board,1).
display_board(_,_,65) -> true;
display_board(Top,[H|T],Place) ->
[Name,Colour,_] = H,
{X,Y} = xy_position(H),
Button = mk_board_butt(Top,Name,X,Y,Colour),
%%Button = mk_button("",Name,Top,X,Y,60,60),
put(Name,Button),
%%Bitmap = bitmap(Colour),
%%butbit(Button,Bitmap),
%%set_widget(Button,"internalWidth","1"),
%%set_widget(Button,"internalHeight","1"),
%%borderWidth(2,Button),
display_board(Top,T,Place+1).