matlab_solitaire/sulitear.m

365 lines
15 KiB
Matlab

% Original author: En Yi
% A solitaire game on MATLAB, very self-explanatory
% Not optimised though
% Have fun!
% Anyone can modify it, just need to give credits to the original author
% Future considerations:
% -Add dragging functions
% -Add card animations
% -Find a better way to render the game for better resolution
% -Allow deck customisation?
function sulitear()
clc;
% Get information about the screen
scrsz = get(0,'ScreenSize');
win_ratio = scrsz(3:4)/scrsz(3);
win_size = scrsz(3:4)*0.8;
% Construct the window
win = figure('ToolBar','none','Name','Solitaire',...
'NumberTitle','off','MenuBar','none',...
'Resize','off','Visible','off','Color',[0 0 0]/255,...
'Position',[scrsz(3:4)-win_size*1.05 win_size],...
'ButtonDownFcn',@check_clicked_deck,...
'KeyPressFcn',@restart);
% Prepare a variable to indicate which deck are selected
previous_selected_deck = 0;
win_game = 0;
% Prepare cards and the playing field
playing_cards = prepare_playing_cards();
[playing_decks,draw_deck,discard_deck,goal_decks,playfield_size] = prepare_playfield(playing_cards,win_ratio);
% Prepare the drawing axes
disp_axes = axes('Parent',win,'Position',[0 0 1 1]);
set(disp_axes,'Xlim',[0 playfield_size(1)],'Ylim',[0 playfield_size(2)],...
'XLimMode','manual','YLimMode','manual','Visible','off','NextPlot','add');
% Prepare winning text
% Draw the playing field on the axes
draw_playfield();
% Prepare the text and hide it
win_text = text(disp_axes,playfield_size(1)/2,playfield_size(2)/4,'You Won! Press R to Try Again!',...
'PickableParts','none','Color',[1 1 1],'FontSize',15,'Visible','off',...
'HorizontalAlignment','center');
load_text = text(disp_axes,playfield_size(1),playfield_size(2)/8,'Loading...',...
'PickableParts','none','Color',[1 1 1],'FontSize',15,'Visible','off',...
'HorizontalAlignment','right');
set(win,'Visible','on')
%% Callback functions
%%% Idea: can optimise by splitting the window into regions and check only
%%% the decks in that region
function check_clicked_deck(~,~)
% Only allow left clicks, subject to changes
if ~strcmp(get(win,'selectiontype'),{'normal','open'})
return
end
[Xx,Yy] = get_mouse_pos();
if draw_deck.check_Deck_Collision(Xx,Yy,'first')
reset_card_selection();
% Check for the draw pile collision
if draw_deck.get_Number_Of_Cards() + discard_deck.get_Number_Of_Cards()>0 % If not all cards are taken away
if draw_deck.get_Number_Of_Cards()>0 % If there's cards to be drawn
previous_selected_deck = draw_deck;
draw_deck.selected_start_index = min(draw_deck.get_Number_Of_Cards(),3);
discard_deck.set_Current_Display(draw_deck.selected_start_index); % Set the discard to show that amount of cards transferred
draw_deck.transfer_Selected_Cards(discard_deck,'flip'); % Transfer cards to discard pile, up to 3
reset_card_selection();
else
previous_selected_deck = discard_deck; % Transfer back the cards from discard pile
discard_deck.selected_start_index = discard_deck.get_Number_Of_Cards();
discard_deck.transfer_Selected_Cards(draw_deck,'flip');
reset_card_selection();
end
draw_deck.update_Deck_Graphics(disp_axes);
discard_deck.update_Deck_Graphics(disp_axes);
end
return
end
% Check for the draw pile collision
if discard_deck.check_Deck_Collision(Xx,Yy,'first')
if previous_selected_deck == discard_deck
reset_card_selection();
return
end
reset_card_selection();
if discard_deck.get_Number_Of_Cards() > 0
discard_deck.selected_start_index = 1; % Only allow selection, up to one card
previous_selected_deck = discard_deck;
discard_deck.update_Deck_Graphics(disp_axes);
end
return
end
% Check for the playing deck
for i = 1:length(playing_decks)
if playing_decks(i).check_Deck_Collision(Xx,Yy,'full')
selected_deck = playing_decks(i);
s_index = selected_deck.check_selection(Xx,Yy); %Check which card is selected
if s_index>=0
% Manually open a hidden card, not used
% if playing_decks(i).check_Deck_Collision(Xx,Yy,'first') && s_index == -1
% reset_card_selection();
% selected_deck.reveal_Hidden_Card(1)
% selected_deck.update_Deck_Graphics(disp_axes);
% return
% end
% If a deck is previously selected
if (previous_selected_deck ~= 0)
if previous_selected_deck ~= selected_deck
[transferring_num,transferring_col]= determine_card(get_bottom_selected(previous_selected_deck));
[destination_num,destination_col] = determine_card(selected_deck.get_Last_Cards());
if (transferring_col ~= destination_col &&... % If the colour alternates
transferring_num == destination_num-1) % If the number are in sequence
transfer_Selected_Cards(previous_selected_deck,selected_deck);
end
auto_open_hiddencard(); % Reveal a hidden card if there is one
end
reset_card_selection();
else
if ~selected_deck.is_Empty()
reset_card_selection();
selected_deck.selected_start_index = s_index;
previous_selected_deck = selected_deck;
end
end
else
reset_card_selection();
end
selected_deck.update_Deck_Graphics(disp_axes);
return
end
end
% Check for the goal decks
for i = 1:length(goal_decks)
if goal_decks(i).check_Deck_Collision(Xx,Yy,'first')
selected_deck = goal_decks(i);
if (previous_selected_deck ~= 0)
if previous_selected_deck.selected_start_index == 1 % Only allow one card transfer
[transferring_num,~,transferring_suit]= ...
determine_card(get_bottom_selected(previous_selected_deck));
[destination_num,~,destination_suit] = ...
determine_card(selected_deck.get_Last_Cards());
if (transferring_suit == destination_suit ... % If card is same suit
&& transferring_num == destination_num+1 )... % Must be consecutive number
|| transferring_num == 1 % Or is the ace
transfer_Selected_Cards(previous_selected_deck,selected_deck);
end
end
auto_open_hiddencard();
reset_card_selection();
else
if ~selected_deck.is_Empty()
reset_card_selection();
selected_deck.selected_start_index = 1;
previous_selected_deck = selected_deck;
end
end
selected_deck.update_Deck_Graphics(disp_axes);
% Check for winning condition
total_goal_cards = 0;
for j = 1:length(goal_decks)
total_goal_cards = total_goal_cards+goal_decks(j).get_Number_Of_Cards();
end
if total_goal_cards == 52 && win_game == 0
win_game = 1;
set(win_text,'Visible','on');
reset_card_selection();
end
return
end
end
end
%% Non-Callback functions
% Prepare the card holders
function restart(~,evtdata)
set(load_text,'Visible','on');drawnow;
% Press R to try again
if strcmp(evtdata.Key,'r')
reset_entire_game(playing_cards);
end
set(load_text,'Visible','off');
end
% Prepare the playing field dimension with the card holders
function [playing_decks,draw_deck,discard_deck,goal_decks,playfield_size] = prepare_playfield(cards,win_ratio)
card_size = size(cards(1).get_Card_Image('front'));
card_width = card_size(2);
card_height = card_size(1);
offset = round(card_width);
n = 7;
border_offset = 10;
playfield_width = round((card_size(2)+offset)*n-offset+2*border_offset);
% i = 1;
% while(playfield_width-card_width*i>=0)
% i = i+1;
% end
% playfield_width = card_width*i;
% i = 1;
playfield_size = round([playfield_width playfield_width].*win_ratio);
% while(playfield_size(2)-card_height*i>=0)
% i = i+1;
% end
% playfield_size(2) = card_height*i;
% Compute the position and dimensions
start_x = border_offset;
start_y =playfield_size(2)-card_height-4*border_offset;
card_offset = (start_y-card_height-offset)/18;
% Initialise the card holders
draw_deck = cardHolder(start_x,playfield_size(2)-border_offset,...
[],card_width,card_height,card_offset,'horizontal',1,1,1,0);
discard_deck = cardHolder(start_x+card_width+offset,playfield_size(2)-border_offset,...
[],card_width,card_height,card_offset,'horizontal',3,0,0,0);
for i = 4:n
goal_decks(i-3) = cardHolder(start_x+(card_width+offset)*(i-1),...
playfield_size(2)-border_offset,[],card_width,card_height,card_offset,'vertical',1,0,0,1);
end
% Shuffle the cards
remaining_cards = cards(randperm(length(cards)));
for i = 1:n
dealt_cards = remaining_cards(1:i);
playing_decks(i) = cardHolder(start_x+(card_width+offset)*(i-1),...
start_y,dealt_cards,card_width,card_height,card_offset,'vertical',-1,i-1,0,1);
remaining_cards = remaining_cards(i+1:end);
end
draw_deck.append_Cards(remaining_cards);
end
% Prepare a deck of cards
function all_cards = prepare_playing_cards()
card_values = cumsum(ones(13,4))'+ cumsum(ones(4,13)*100);
try
crd = load('card_images.mat');
card_images = crd.cards;
card_backimage = crd.card_backimage;
for i = 1:52
j = ceil(i/13);
k = mod(i-1,13)+1;
all_cards(i) = Cards(card_values(j,k),card_images{j,k},card_backimage);
end
catch
disp('Load failed')
close all
% Should maybe have a variable for loading failure
end
end
% % Distribute the playing cards, not used
% function [dealt_cards,remaining_cards] = deal_cards(cards,amount)
% remaining_cards = cards;
% for i = 1:amount
% n_of_cards = length(remaining_cards);
% index = randi(n_of_cards);
% dealt_cards(i) = remaining_cards(index);
% remaining_cards = [remaining_cards(1:index-1) remaining_cards(index+1:end)];
% end
% end
% Reset the game
function reset_entire_game(cards)
if win_game
win_game=0;
set(win_text,'Visible','off');
end
remaining_cards = cards(randperm(length(cards)));
for i = 1:length(playing_decks)
playing_decks(i).clear_Deck();
playing_decks(i).append_Cards(remaining_cards(1:i));
playing_decks(i).hidden_start_index = i-1;
playing_decks(i).update_Deck_Graphics(disp_axes);
remaining_cards = remaining_cards(i+1:end);
end
for i = 1:length(goal_decks)
goal_decks(i).clear_Deck();
goal_decks(i).update_Deck_Graphics(disp_axes);
end
discard_deck.clear_Deck();
discard_deck.update_Deck_Graphics(disp_axes);
draw_deck.clear_Deck();
draw_deck.append_Cards(remaining_cards);
draw_deck.update_Deck_Graphics(disp_axes);
reset_card_selection();
end
% Reset the card selection to none and updating the deck graphic
function reset_card_selection()
if previous_selected_deck ~= 0
previous_selected_deck.selected_start_index = 0;
previous_selected_deck.update_Deck_Graphics(disp_axes)
previous_selected_deck = 0;
end
end
% Draw the play field
function draw_playfield()
cla(disp_axes); % Clear the play field axes, not that it is needed since it is only called during initialisation
for i = 1:length(playing_decks)
playing_decks(i).render_deck_outline(disp_axes);
playing_decks(i).update_Deck_Graphics(disp_axes);
end
draw_deck.render_deck_outline(disp_axes);
draw_deck.update_Deck_Graphics(disp_axes);
%discard_deck.render_deck_outline(disp_axes);
discard_deck.update_Deck_Graphics(disp_axes);
for i = 1:length(goal_decks)
goal_decks(i).render_deck_outline(disp_axes);
goal_decks(i).update_Deck_Graphics(disp_axes);
end
end
% Determine the number, colour , and suit of the colours
function [num,colour,suit] = determine_card(card,varargin)
if isa(card, 'Cards')
[num,colour,suit] = card.get_Card_Info();
else
if card == 0
if ~isempty(varargin)
num = varargin{1}; % Specify the value an empty deck should take
else
num = 14; % Allow king to go into empty slots by default
end
else
num = -1; % The deck is having hidden cards
end
suit = -1;
colour = -1;
end
end
% Open hidden cards automatically
function auto_open_hiddencard()
if (previous_selected_deck.hidden_start_index >0 ...
&& previous_selected_deck.get_Number_Of_Cards() == previous_selected_deck.hidden_start_index)
previous_selected_deck.reveal_Hidden_Card(1)
end
end
% Get the mouse position
function[X,Y]= get_mouse_pos()
mpos = get(disp_axes,'CurrentPoint');
X = mpos(1,1);
Y = mpos(1,2);
end
end