function hexxagon
%HEXXAGON Graphical User Interface for playing the Ataxx clone Hexxagon.
% Hexxagon is an abstract strategy board game which involves play by two
% parties on a hexagonal board. The object of the game is to make your
% pieces constitute a majority of the pieces on the board at the end of
% the game, by converting as many of your opponent's pieces as possible.
%
% Each player begins with three pieces for the first player and second
% player respectively. The game starts with the six pieces on the six
% corners of the board.
%
% During their turn players must first select the piece they wish to
% move, the source, and then select an empty square, the destination,
% that is either adjacent to, or having a distance of less than three,
% from the source. Diagonal distances are equivalent to orthogonal
% distances, i.e. it is legal move to a square who's relative position
% is two squares away both vertically and horizontally. If the
% destination is adjacent to the source, a new piece is created on the
% empty destination square. Otherwise the piece on the source moves to
% the destination. After the move, all of the opponent player's pieces
% adjacent to the destination piece are converted to the color of the
% moving player. Players must move unless no legal move is possible,
% in which case they must pass.
%
% The game ends when all positions of the board have been filled or one
% of the players has no remaining pieces. The player with the most
% pieces wins. A draw may occur when the number of squares are even.
%
% (text above has been adapted from Wikipedia)
%
% Hexxagon has single and double player modes, score tracking and sound
% effects.
%
% Example:
% hexxagon % Start Hexxagon Interface
%
% Developed by Per-Anders Ekstrm, 2003-2007 Facilia AB.
playerColor = [1,0.6,0.6;0.6,0.6,1];
yellow = [1,1,0];
green = [0,1,0];
gray = [0.8,0.8,0.8];
turn = 0;
soundOn = true;
playerChangeSound = @()soundsc(sin(0:302),12522);
patchClickSound = @()soundsc(sin(0:30),14555);
movedSound = @()soundsc(cos(2:32),3425);
killSound = @()soundsc(cos(2:32),4425);
twoPlayerOn = false;
run = false;
selected = 0;
animatedPatch = [];
playerText = [0,0];
createFigure()
pBit = resetPlyBit();
bjBit = createBoardJumpBit();
handle = drawBoard();
rulesText = addRulesText();
menuText = addMenuText();
turnText = addTurnText();
scoreText = addScoreText();
soundButton = addSoundButton();
set(gcf,'ResizeFcn',@resizeWindow)
resetGame()
createMenu()
resizeWindow(gcf)
function createFigure()
scrsz = get(0,'ScreenSize');
% Initialize figure window
figure('Name','Hexxagon',...
'Numbertitle','off',...
'Menubar','none',...
'DoubleBuffer','on',...
'Color',[.95 .95 .95],...
'ResizeFcn','', ...
'Position',[(scrsz(3)-450)/2 (scrsz(4)-450)/2 450 450]);
end
function createMenu()
set([rulesText,menuText,turnText,scoreText],'visible','off')
v = axis;
drawnow
patch([v(1),v(2),v(2),v(1),v(1)],[v(3),v(3),v(4),v(4),v(3)],...
[1 1 1],'FaceAlpha',.6,'tag','menu');
text(sum(v(1:2))/2.1*0.99,sum(v(3:4))/2*0.99,'Hexxagon',...
'Horizontalalignment','center','FontSize',55,...
'FontUnits','Normalized',...
'FontName','FixedWidth','Color',[.6,.6,.6],...
'FontWeight','bold','tag','menu');
text(sum(v(1:2))/2.1,sum(v(3:4))/2,'Hexxagon',...
'Horizontalalignment','center','FontSize',55,...
'FontUnits','Normalized',...
'FontName','FixedWidth','FontWeight','bold','tag','menu');
playerText(1)=text(sum(v(1:2))/2,sum(v(3:4))/3*1.12,...
' Human Vs. Computer ','Verticalalignment','bottom',...
'Horizontalalignment','left','FontSize',8,...
'EdgeColor','black','FontUnits','Normalized',...
'FontWeight','bold','BackgroundColor',[.9 .9 .9],...
'ButtonDownFcn',{@changeNumberOfPlayer,false},'tag','menu');
playerText(2)=text(sum(v(1:2))/2.1,sum(v(3:4))/3*1.12,...
' Human Vs. Human ','Verticalalignment','bottom',...
'Horizontalalignment','right','FontSize',8,...
'EdgeColor','black','FontUnits','Normalized',...
'FontWeight','bold','BackgroundColor',[.9 .9 .9],...
'ButtonDownFcn',{@changeNumberOfPlayer,true},'tag','menu');
set(playerText(twoPlayerOn+1),'EdgeColor','red',...
'BackgroundColor',[1 .9 .9],'LineWidth',1.5)
text(sum(v(1:2))/2.1,sum(v(3:4))/3,' START GAME ',...
'Verticalalignment','top','Horizontalalignment','center',...
'FontSize',8,'FontWeight','bold','Color','white',...
'FontUnits','Normalized',...
'BackgroundColor',[1 .5 .5],'EdgeColor','black',...
'ButtonDownFcn',@startGame,'tag','menu');
end
function handle = drawBoard()
patch([-1 9 9 -1 -1],[0 0 12 12 0],[1 1 1],...
'FaceColor',[.97,.97,.97]);
x = 1.1*[repmat(0,1,5),repmat(0.745,1,6),repmat(1.49,1,7),...
repmat(2.235,1,7),repmat(2.98,1,8),repmat(3.725,1,7),...
repmat(4.47,1,7),repmat(5.215,1,6),repmat(5.96,1,5)];
y = -.1+1.1*[7 6 5 4 3 7.5 6.5 5.5 4.5 3.5 2.5 8 7 6 5 4 3 2 ...
8.5 7.5 6.5 5.5 3.5 2.5 1.5 9 8 7 5 4 3 2 1 8.5 7.5 6.5 5.5 ...
3.5 2.5 1.5 8 7 6 5 4 3 2 7.5 6.5 5.5 4.5 3.5 2.5 7 6 5 4 3];
createPolygonAt = @(i,j)patch([0,0.255,0.745,1,0.745,0.255,0]+i,...
[0.5,0,0,0.5,1,1,0.5]+j,[1 1 1],'Visible','off');
% ARRAYFUN not used for backward compatibility
%handle = arrayfun(@(i)createPolygonAt(x(i),y(i)),1:58);
handle = zeros(1,58);
for i=1:58
handle(i) = createPolygonAt(x(i),y(i));
end
set(handle,'ButtonDownFcn',@patchButtonDownFcn,'Visible','on');
animatedPatch = createPolygonAt(-1,-1);
axis square
axis off
set(gca,'Units','Pixels','DrawMode','fast',...
'XLim',[-1 9],'YLim',[0 12])
end
function button = addSoundButton()
button = uicontrol('style','pushbutton','CData',getSoundCData(),...
'Callback',@changeSound);
end
function txt = addRulesText()
txt = text(1,1,'Rules','Color',[1 1 1],'Units','Pixels',...
'BackgroundColor',gray,'FontWeight','bold',...
'EdgeColor','black','Margin',5,'Horizontalalignment','left',...
'Verticalalignment','top','visible','on',...
'ButtonDownFcn',@showRules,'visible','off');
end
function txt = addMenuText()
txt = text(1,1,'Menu','Color',[1 1 1],'Units','Pixels',...
'BackgroundColor',gray,'FontWeight','bold',...
'EdgeColor','black','Margin',5,'Horizontalalignment','left',...
'Verticalalignment','top','visible','on',...
'ButtonDownFcn',@quitGame,'visible','off');
end
function txt = addTurnText()
txt = text(10,10,['Player ' num2str(turn+1)],'Color',[1 1 1],...
'Units','Pixels','BackgroundColor',playerColor(1,:),...
'FontWeight','bold','EdgeColor','black','Margin',5,...
'Horizontalalignment','left','Verticalalignment','bottom',...
'visible','off');
end
function txt = addScoreText()
v = axis;
txt(1) = text(v(2),0,'3','Color',[1 1 1],'Units','Pixels',...
'BackgroundColor',playerColor(1,:),'FontWeight','bold',...
'EdgeColor','black','Horizontalalignment','right',...
'Verticalalignment','top','visible','off');
txt(2) = text(v(2),v(4),'3','Color',[1 1 1],'Units','Pixels',...
'BackgroundColor',playerColor(2,:),'FontWeight','bold',...
'EdgeColor','black','Horizontalalignment','right',...
'Verticalalignment','bottom','visible','off');
end
function resetGame()
run = false;
turn = 1;
selected = 0;
changeTurn()
set(animatedPatch,'Visible','off')
pBit = resetPlyBit();
updateScoreText()
set(handle,'FaceColor',gray);
set(handle(1==(bitget(pBit(1),1:58))),...
'FaceColor',playerColor(1,:));
set(handle(1==(bitget(pBit(2),1:58))),...
'FaceColor',playerColor(2,:));
delete(findall(gca,'Type','line')) % clear all selected
end
function pBit = resetPlyBit()
pBit = uint64([0,0]);
pBit(1) = bitset(pBit(1),1);
pBit(1) = bitset(pBit(1),33);
pBit(1) = bitset(pBit(1),54);
pBit(2) = bitset(pBit(2),5);
pBit(2) = bitset(pBit(2),26);
pBit(2) = bitset(pBit(2),58);
end
function showRules(varargin)
helpdlg(help(mfilename),'Hexxagon Rules')
end
function changeSound(varargin)
soundOn = ~soundOn;
set(soundButton,'CData',getSoundCData())
end
function changeNumberOfPlayer(varargin)
if nargin==3&&varargin{3}~=twoPlayerOn
twoPlayerOn = ~twoPlayerOn;
set(playerText(twoPlayerOn+1),'EdgeColor','red',...
'BackgroundColor',[1 .9 .9],'LineWidth',1.5)
set(playerText(~twoPlayerOn+1),'EdgeColor','black',...
'BackgroundColor',[.9 .9 .9],'LineWidth',.5)
if soundOn
playerChangeSound()
end
end
end
function quitGame(varargin)
answer = 'Yes';
if run
answer = questdlg(...
'Do you really want to end the current game?', ...
'Hexxagon Quit', ...
'Yes', 'No','No');
end
switch answer,
case 'Yes',
resetGame()
set(findall(gcf,'tag','menu'),'Visible','on')
set([rulesText,menuText,turnText,scoreText],...
'visible','off')
case 'No',
end % switch
end
function startGame(varargin)
set(findall(gcf,'tag','menu'),'Visible','off')
set([rulesText,menuText,turnText,scoreText],'visible','on')
run = true;
end
function patchButtonDownFcn(varargin)
if run&&(twoPlayerOn||(~twoPlayerOn&&~turn))
ind = find(varargin{1}==handle);
if bitget(pBit(turn+1),ind) % this turn players pos
if soundOn
patchClickSound()
end
delete(findall(gca,'Type','line')) % clear all selected
if selected==ind
selected = 0;
else
selected = ind;
patchborder = @(h,color)line(get(h,'XData'),...
get(h,'YData'),'Color',color,'Linewidth',3);
patchborder(handle(ind),yellow)
empty_bit = bitand(bitnot(pBit(1)),bitnot(pBit(2)));
% ARRAYFUN not used for backward compatibility
% arrayfun(@(x)patchborder(handle(x),green),...
% find(bitget(bitand(bjBit(1,ind),empty_bit),1:58)))
% arrayfun(@(x)patchborder(handle(x),yellow),...
% find(bitget(bitand(bjBit(2,ind),empty_bit),1:58)))
greenpos = find(bitget(bitand(bjBit(1,ind),...
empty_bit),1:58));
for i=1:length(greenpos)
patchborder(handle(greenpos(i)),green)
end
yellowpos = find(bitget(bitand(bjBit(2,ind),...
empty_bit),1:58));
for i=1:length(yellowpos)
patchborder(handle(yellowpos(i)),yellow)
end
end
elseif ~bitget(pBit(~turn+1),ind) % no-ones pos
if selected
if soundOn
patchClickSound()
end
if any(find(bitget(bjBit(1,selected),1:58))==ind)
move(selected,ind)
elseif any(find(bitget(bjBit(2,selected),1:58))==ind)
move(selected,ind)
end
end
end
end
end
function move(from,to)
if bitget(bjBit(2,from),to)==1 % if it is a jump
set(handle(from),'FaceColor',gray)
pBit = bitset(pBit(:),from,0);
end
delete(findall(gca,'Type','line'))
selected = 0;
n = 20;
fromXData = get(handle(from),'XData');
fromYData = get(handle(from),'YData');
toXData = get(handle(to),'XData');
toYData = get(handle(to),'YData');
set(animatedPatch,'XData',fromXData,'YData',fromYData,...
'Visible','on','FaceColor',playerColor(turn+1,:))
pathX = linspace(0,toXData(1)-fromXData(1),n);
pathY = linspace(0,toYData(1)-fromYData(1),n);
for i=1:n
t0 = clock;
set(animatedPatch,'XData',fromXData+pathX(i),...
'YData',fromYData+pathY(i))
while etime(clock,t0)<1/(6*n)
drawnow
end
end
if soundOn
movedSound()
end
set(handle(to),'FaceColor',playerColor(turn+1,:))
pBit(turn+1) = bitset(pBit(turn+1),to);
pBit(~turn+1) = bitset(pBit(~turn+1),to,0);
kill = find(bitget(bitand(bjBit(1,to),pBit(~turn+1)),1:58));
for i=1:length(kill)
if soundOn
pause(.1)
killSound()
end
set(handle(kill(i)),'FaceColor',playerColor(turn+1,:))
pBit(turn+1) = bitset(pBit(turn+1),kill(i));
pBit(~turn+1) = bitset(pBit(~turn+1),kill(i),0);
end
set(animatedPatch,'XData',[-22 -24 -26],'YData',[-22 -22 -26],...
'Visible','off')
drawnow
changeTurn()
end
function changeTurn(varargin)
turn = ~turn;
set(turnText,'String',['Player ' num2str(turn+1)],...
'BackgroundColor',playerColor(turn+1,:))
updateScoreText()
if run&&isempty(find(bitget(bitand(bitnot(pBit(1)),...
bitnot(pBit(2))),1:58)==1,1))
pl1 = sum(bitget(pBit(1),1:58)==1);
pl2 = sum(bitget(pBit(2),1:58)==1);
if pl1>pl2
str = sprintf('Player 1 WINS!\n%d against %d points',...
pl1,pl2);
elseif pl2>pl1
if twoPlayerOn
str = sprintf('Player 2 WINS!\n%d against %d points',...
pl2,pl1);
else
str = sprintf('Computer WINS!\n%d against %d points',...
pl2,pl1);
end
else
str = sprintf('DRAW!\n%d-%d',pl1,pl2);
end
msgbox(str,'Hexxagon Winner')
run = false;
return
elseif run&&~canMove()
changeTurn()
elseif run&&~twoPlayerOn&&turn
computerMove();
end
end
function status = canMove()
status = false;
empty_bit = bitand(bitnot(pBit(1)),bitnot(pBit(2)));
positions = find(bitget(pBit(turn+1),1:58)==1);
for i=1:length(positions)
if ~isempty(find(bitget(bitand(bjBit(1,positions(i)),...
empty_bit),1:58)==1,1))
status = true;
return
end
if ~isempty(find(bitget(bitand(bjBit(2,positions(i)),...
empty_bit),1:58)==1,1))
status = true;
return
end
end
end
function computerMove()
empty_bit = bitand(bitnot(pBit(1)),bitnot(pBit(2)));
positions = find(bitget(pBit(2),1:58)==1);
bestpos = 0;
bestmove = 0;
bestscore = 0;
for i=1:length(positions)
freesinglespots = find(bitget(bitand(bjBit(1,...
positions(i)),empty_bit),1:58)==1);
for j=1:length(freesinglespots)
kills = length(find(bitget(bitand(bjBit(1,...
freesinglespots(j)),pBit(1)),1:58)))+1;
if kills>=bestscore+rand-.5
bestpos = positions(i);
bestmove = freesinglespots(j);
bestscore = kills;
end
end
end
for i=1:length(positions)
freedoublespots = find(bitget(bitand(bjBit(2,...
positions(i)),empty_bit),1:58)==1);
for j=1:length(freedoublespots)
kills = -1+length(find(bitget(bitand(bjBit(1,...
freedoublespots(j)),pBit(1)),1:58)));
if kills>=bestscore+rand-.5
bestpos = positions(i);
bestmove = freedoublespots(j);
bestscore = kills;
end
end
end
if bestscore>0
move(bestpos,bestmove)
else
changeTurn()
end
end
function updateScoreText()
score = @(player)sum(bitget(pBit(player),1:58)==1);
setText = @(player,score)set(scoreText(player),...
'String',[repmat(' ',1,score-3-(score>9)),num2str(score)]);
setText(1,score(1))
setText(2,score(2))
end
function newbit = bitnot(bit)%since bitcmp doesn't work on 64 bits uint
truthpos = find(~bitget(bit,1:58));
newbit = uint64(0);
for i=1:length(truthpos)
newbit = bitset(newbit,truthpos(i));
end
end
function bjBit = createBoardJumpBit()
singles = {[2 6 7],[1 3 7 8],[2 4 8 9],[3 5 9 10],[4 10 11],...
[1 7 12 13],[1 2 6 8 13 14],[2 3 7 9 14 15],...
[3 4 8 10 15 16],[4 5 9 11 16 17],[5 10 17 18],[6 13 19 20],...
[6 7 12 14 20 21],[7 8 13 15 21 22],[8 9 14 16 22],...
[9 10 15 17 23],[10 11 16 18 23 24],[11 17 24 25],...
[12 20 26 27],[12 13 19 21 27 28],[13 14 20 22 28],...
[14 15 21 29],[16 17 24 30 31],[17 18 23 25 31 32],...
[18 24 32 33],[19 27 34],[19 20 26 28 34 35],...
[20 21 27 35 36],[22 30 37],[23 29 31 38],...
[23 24 30 32 38 39],[24 25 31 33 39 40],[25 32 40],...
[26 27 35 41],[27 28 34 36 41 42],[28 35 37 42 43],...
[29 36 43 44],[30 31 39 45 46],[31 32 38 40 46 47],...
[32 33 39 47],[34 35 42 48],[35 36 41 43 48 49],...
[36 37 42 44 49 50],[37 43 45 50 51],[38 44 46 51 52],...
[38 39 45 47 52 53],[39 40 46 53],[41 42 49 54],...
[42 43 48 50 54 55],[43 44 49 51 55 56],[44 45 50 52 56 57],...
[45 46 51 53 57 58],[46 47 52 58],[48 49 55],[49 50 54 56],...
[50 51 55 57],[51 52 56 58],[52 53 57]};
doubles = { [3 8 14 13 12],[6 13 14 15 9 4],[1 7 14 15 16 10 5],...
[2 8 15 16 17 11],[3 9 16 17 18],[19 20 21 14 8 2],...
[12 20 21 22 15 9 3],[1 6 13 21 22 16 10 4],...
[2 7 14 22 23 17 11 5],[3 8 15 23 24 18],[4 9 16 23 24 25],...
[26 27 28 21 14 7 1],[19 27 28 22 15 8 2 1],...
[1 6 12 20 28 29 16 9 3 2],[23 17 10 4 3 2 7 13 21],...
[30 31 24 18 11 5 4 3 8 14 22],[30 31 32 25 5 4 9 15],...
[5 10 16 23 31 32 33],[34 35 28 21 13 6],...
[26 34 35 36 22 14 7 6],[19 27 35 36 29 15 8 7 6 12],...
[20 28 37 30 16 9 8 7 13],[29 38 39 32 25 18 11 10 9 15],...
[30 38 39 40 33 11 10 16],[11 17 23 31 39 40],...
[41 35 28 20 12],[41 42 36 21 13 12],...
[26 34 41 42 43 37 22 14 13 12 19],...
[36 43 44 38 31 23 15 14 21],[22 37 45 46 39 32 24 17 16],...
[29 45 46 47 40 33 25 18 17 16],[30 38 46 47 18 17 23],...
[18 24 31 39 47],[19 20 28 36 42 48],...
[26 19 20 21 37 43 49 48],[29 21 20 27 34 41 48 49 50 44],...
[30 22 28 35 42 49 50 51 45],...
[29 44 51 52 53 47 40 32 24 23],[45 52 53 33 25 24 23 30],...
[25 24 31 38 46 53],[26 27 28 36 43 49 54],...
[34 27 28 37 44 50 55 54],[54 48 41 35 28 29 45 51 56 55],...
[36 42 49 56 57 52 46 38 29],...
[37 43 50 56 57 58 53 47 39 31 30],...
[44 51 57 58 40 32 31 30],[58 52 45 38 31 32 33],...
[34 35 36 43 50 55],[41 35 36 37 44 51 56],...
[54 48 42 36 37 45 52 57],[55 49 43 37 38 46 53 58],...
[56 50 44 38 39 47],[57 51 45 38 39 40],[41 42 43 50 56],...
[48 42 43 44 51 57],[54 49 43 44 45 52 58],[55 50 44 45 53],...
[56 51 45 46 47]};
bjBit = uint64(zeros(2,58));
for ii=1:58
single = singles{ii};
for jj=1:length(single)
bjBit(1,ii) = bitset(bjBit(1,ii),single(jj));
end
double = doubles{ii};
for jj=1:length(double)
bjBit(2,ii) = bitset(bjBit(2,ii),double(jj));
end
end
end
function CData = getSoundCData()
CData = [...
0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0;
0 0 0 0 0 0 1 1 0 0 0 1 1 0 0 0;
0 0 0 0 0 1 1 1 0 0 0 0 0 1 0 0;
0 0 0 0 1 1 1 1 0 0 1 0 0 1 0 0;
0 0 0 1 1 1 1 1 0 0 0 1 0 0 1 0;
0 0 1 1 1 1 1 1 0 1 0 0 1 0 1 0;
1 1 1 1 1 1 1 1 0 0 1 0 1 0 1 0;
1 1 1 1 1 1 1 1 0 0 1 0 1 0 1 0;
1 1 1 1 1 1 1 1 0 0 1 0 1 0 1 0;
1 1 1 1 1 1 1 1 0 0 1 0 1 0 1 0;
0 0 1 1 1 1 1 1 0 1 0 0 1 0 1 0;
0 0 0 1 1 1 1 1 0 0 0 1 0 0 1 0;
0 0 0 0 1 1 1 1 0 0 1 0 0 1 0 0;
0 0 0 0 0 1 1 1 0 0 0 0 0 1 0 0;
0 0 0 0 0 0 1 1 0 0 0 1 1 0 0 0;
0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0];
if ~soundOn
CData(:,9:end)=0;
end
CData = repmat(CData,[1 1 3]);
CData(CData==1)=.7;
CData(CData==0)=.97;
end
function resizeWindow(varargin)
posf = get(varargin{1}, 'Position');
posf(3:4) = max(min(posf(3:4)),350);
set(varargin{1},'Position',posf);
set(menuText,'Position',[7,posf(4)-24])
set(rulesText,'Position',[54,posf(4)-24])
set(soundButton,'Position',[85,16,16,16])
set(turnText,'Position',[7,8])
set(scoreText(1),'Position',[posf(3)-21,posf(4)-20])
set(scoreText(2),'Position',[posf(3)-21,4])
set(gca, 'Position',[10 10 posf(3)-21 posf(4)-21])
end
end