Replace "transferring_deck" to "previously_selected_deck". Add comments
parent
f3b3af18f1
commit
c56b7ad776
209
sulitear.m
209
sulitear.m
|
@ -1,16 +1,20 @@
|
||||||
function sulitear()
|
|
||||||
% Original author: En Yi
|
% Original author: En Yi
|
||||||
% A solitaire game on MATLAB, very self-explanatory
|
% A solitaire game on MATLAB, very self-explanatory
|
||||||
% Not optimised though
|
% Not optimised though
|
||||||
% Have fun!
|
% Have fun!
|
||||||
% Anyone can modify it, just need to give credits to the original author
|
% 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;
|
clc;
|
||||||
% Construct the window with the axes
|
% Get information about the screen
|
||||||
scrsz = get(0,'ScreenSize');
|
scrsz = get(0,'ScreenSize');
|
||||||
%TODO Find new method to draw the window
|
|
||||||
%start_dim = min(scrsz(3)/1.5,scrsz(4)/1.5);%Used for rescaling
|
|
||||||
win_ratio = scrsz(3:4)/scrsz(3);
|
win_ratio = scrsz(3:4)/scrsz(3);
|
||||||
win_size = scrsz(3:4)*0.8;
|
win_size = scrsz(3:4)*0.8;
|
||||||
|
% Construct the window
|
||||||
win = figure('ToolBar','none','Name','Solitaire',...
|
win = figure('ToolBar','none','Name','Solitaire',...
|
||||||
'NumberTitle','off','MenuBar','none',...
|
'NumberTitle','off','MenuBar','none',...
|
||||||
'Resize','off','Visible','off','Color',[0 0 0]/255,...
|
'Resize','off','Visible','off','Color',[0 0 0]/255,...
|
||||||
|
@ -18,37 +22,41 @@ win = figure('ToolBar','none','Name','Solitaire',...
|
||||||
'ButtonDownFcn',@check_clicked_deck,...
|
'ButtonDownFcn',@check_clicked_deck,...
|
||||||
'KeyPressFcn',@restart);
|
'KeyPressFcn',@restart);
|
||||||
|
|
||||||
%Prepare card decks and the playing field
|
% Prepare a variable to indicate which deck are selected
|
||||||
|
previous_selected_deck = 0;
|
||||||
|
% Prepare cards and the playing field
|
||||||
playing_cards = prepare_playing_cards();
|
playing_cards = prepare_playing_cards();
|
||||||
[playing_decks,draw_deck,discard_deck,goal_decks,playfield_size] = prepare_cardHolders(playing_cards,win_ratio);
|
[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]);
|
disp_axes = axes('Parent',win,'Position',[0 0 1 1]);
|
||||||
set(disp_axes,'Xlim',[0 playfield_size(1)],'Ylim',[0 playfield_size(2)],...
|
set(disp_axes,'Xlim',[0 playfield_size(1)],'Ylim',[0 playfield_size(2)],...
|
||||||
'XLimMode','manual','YLimMode','manual','Visible','off','NextPlot','add');
|
'XLimMode','manual','YLimMode','manual','Visible','off','NextPlot','add');
|
||||||
|
|
||||||
set(win,'Visible','on')
|
% Draw the playing field on the axes
|
||||||
% Prepare some variable to indicate cards are selected HERE
|
|
||||||
transferring_deck = 0;
|
|
||||||
% draw it on the axes
|
|
||||||
draw_playfield();
|
draw_playfield();
|
||||||
|
set(win,'Visible','on')
|
||||||
|
|
||||||
%% Callback functions
|
%% Callback functions
|
||||||
%%% Idea: can optimise by splitting the window into regions
|
%%% Idea: can optimise by splitting the window into regions and check only
|
||||||
|
%%% the decks in that region
|
||||||
function check_clicked_deck(~,~)
|
function check_clicked_deck(~,~)
|
||||||
if ~strcmp(get(win,'selectiontype'),'normal')
|
% Only allow left clicks, subject to changes
|
||||||
|
if ~strcmp(get(win,'selectiontype'),{'normal','open'})
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
[Xx,Yy] = get_mouse_pos();
|
[Xx,Yy] = get_mouse_pos();
|
||||||
if draw_deck.check_Deck_Collision(Xx,Yy,'first')
|
if draw_deck.check_Deck_Collision(Xx,Yy,'first')
|
||||||
reset_card_selection();
|
reset_card_selection();
|
||||||
if draw_deck.get_Number_Of_Cards() + discard_deck.get_Number_Of_Cards()>0
|
% Check for the draw pile collision
|
||||||
if draw_deck.get_Number_Of_Cards()>0 % If there's cards
|
if draw_deck.get_Number_Of_Cards() + discard_deck.get_Number_Of_Cards()>0 % If not all cards are taken away
|
||||||
transferring_deck = draw_deck;
|
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);
|
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
|
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
|
draw_deck.transfer_Selected_Cards(discard_deck,'flip'); % Transfer cards to discard pile, up to 3
|
||||||
reset_card_selection();
|
reset_card_selection();
|
||||||
else
|
else
|
||||||
transferring_deck = discard_deck; % Transfer back the cards from discard pile
|
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.selected_start_index = discard_deck.get_Number_Of_Cards();
|
||||||
discard_deck.transfer_Selected_Cards(draw_deck,'flip');
|
discard_deck.transfer_Selected_Cards(draw_deck,'flip');
|
||||||
reset_card_selection();
|
reset_card_selection();
|
||||||
|
@ -59,72 +67,75 @@ draw_playfield();
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if discard_deck.check_Deck_Collision(Xx,Yy,'first') % Else check for discard deck
|
% Check for the draw pile collision
|
||||||
if transferring_deck == discard_deck
|
if discard_deck.check_Deck_Collision(Xx,Yy,'first')
|
||||||
|
if previous_selected_deck == discard_deck
|
||||||
reset_card_selection();
|
reset_card_selection();
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
reset_card_selection();
|
reset_card_selection();
|
||||||
if discard_deck.get_Number_Of_Cards() > 0 % Only allow selection, up to one card
|
if discard_deck.get_Number_Of_Cards() > 0
|
||||||
discard_deck.selected_start_index = 1;
|
discard_deck.selected_start_index = 1; % Only allow selection, up to one card
|
||||||
transferring_deck = discard_deck;
|
previous_selected_deck = discard_deck;
|
||||||
discard_deck.update_Deck_Graphics(disp_axes);
|
discard_deck.update_Deck_Graphics(disp_axes);
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
% Check for the playing deck
|
||||||
for i = 1:length(playing_decks)
|
for i = 1:length(playing_decks)
|
||||||
% This part is only for the playing deck
|
if playing_decks(i).check_Deck_Collision(Xx,Yy,'full')
|
||||||
if playing_decks(i).check_Deck_Collision(Xx,Yy,'full') %Check if any deck is clicked
|
|
||||||
selected_deck = playing_decks(i);
|
selected_deck = playing_decks(i);
|
||||||
s_index = selected_deck.check_selection(Xx,Yy); %If so, check which card is selected
|
s_index = selected_deck.check_selection(Xx,Yy); %Check which card is selected
|
||||||
%If no selection was before or more than a card is
|
|
||||||
%selected and there are cards remaining in the deck
|
|
||||||
% 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);
|
|
||||||
% break
|
|
||||||
% end
|
|
||||||
|
|
||||||
if (transferring_deck ~= 0)
|
% Manually open a hidden card, not used
|
||||||
%if s_index>=0
|
% if playing_decks(i).check_Deck_Collision(Xx,Yy,'first') && s_index == -1
|
||||||
if transferring_deck ~= selected_deck % If the selected deck is not the previously selected deck
|
% reset_card_selection();
|
||||||
[transferring_num,transferring_col]= determine_card(get_bottom_selected(transferring_deck));
|
% 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());
|
[destination_num,destination_col] = determine_card(selected_deck.get_Last_Cards());
|
||||||
if (transferring_col ~= destination_col &&... % If the colour alternates
|
if (transferring_col ~= destination_col &&... % If the colour alternates
|
||||||
transferring_num == destination_num-1) % If the number are in sequence
|
transferring_num == destination_num-1) % If the number are in sequence
|
||||||
transfer_Selected_Cards(transferring_deck,selected_deck);
|
transfer_Selected_Cards(previous_selected_deck,selected_deck);
|
||||||
end
|
end
|
||||||
% Reveal a hidden card if there is one
|
auto_open_hiddencard(); % Reveal a hidden card if there is one
|
||||||
auto_open_hiddencard();
|
|
||||||
end
|
end
|
||||||
%end
|
|
||||||
reset_card_selection();
|
reset_card_selection();
|
||||||
else
|
else
|
||||||
% Otherwise move previously selected cards to the currently selected deck
|
if ~selected_deck.is_Empty()
|
||||||
if s_index>0
|
|
||||||
%Get the selected cards to be transfered
|
|
||||||
reset_card_selection();
|
reset_card_selection();
|
||||||
selected_deck.selected_start_index = s_index;
|
selected_deck.selected_start_index = s_index;
|
||||||
transferring_deck = selected_deck;
|
previous_selected_deck = selected_deck;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
selected_deck.update_Deck_Graphics(disp_axes);
|
selected_deck.update_Deck_Graphics(disp_axes);
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
% Check for the goal decks
|
||||||
for i = 1:length(goal_decks)
|
for i = 1:length(goal_decks)
|
||||||
if goal_decks(i).check_Deck_Collision(Xx,Yy,'first')
|
if goal_decks(i).check_Deck_Collision(Xx,Yy,'first')
|
||||||
selected_deck = goal_decks(i);
|
selected_deck = goal_decks(i);
|
||||||
|
|
||||||
if (transferring_deck ~= 0)
|
if (previous_selected_deck ~= 0)
|
||||||
if transferring_deck.selected_start_index == 1
|
if previous_selected_deck.selected_start_index == 1 % Only allow one card transfer
|
||||||
[transferring_num,~,transferring_suit]= determine_card(get_bottom_selected(transferring_deck));
|
[transferring_num,~,transferring_suit]= ...
|
||||||
[destination_num,~,destination_suit] = determine_card(selected_deck.get_Last_Cards());
|
determine_card(get_bottom_selected(previous_selected_deck));
|
||||||
if (transferring_suit == destination_suit && transferring_num == destination_num+1)...
|
[destination_num,~,destination_suit] = ...
|
||||||
|| transferring_num == 1
|
determine_card(selected_deck.get_Last_Cards());
|
||||||
transfer_Selected_Cards(transferring_deck,selected_deck);
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
auto_open_hiddencard();
|
auto_open_hiddencard();
|
||||||
|
@ -133,34 +144,38 @@ draw_playfield();
|
||||||
if ~selected_deck.is_Empty()
|
if ~selected_deck.is_Empty()
|
||||||
reset_card_selection();
|
reset_card_selection();
|
||||||
selected_deck.selected_start_index = 1;
|
selected_deck.selected_start_index = 1;
|
||||||
transferring_deck = selected_deck;
|
previous_selected_deck = selected_deck;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
selected_deck.update_Deck_Graphics(disp_axes);
|
selected_deck.update_Deck_Graphics(disp_axes);
|
||||||
|
|
||||||
|
% Check for winning condition
|
||||||
total_goal_cards = 0;
|
total_goal_cards = 0;
|
||||||
for j = 1:length(goal_decks)
|
for j = 1:length(goal_decks)
|
||||||
total_goal_cards = total_goal_cards+goal_decks(j).get_Number_Of_Cards();
|
total_goal_cards = total_goal_cards+goal_decks(j).get_Number_Of_Cards();
|
||||||
end
|
end
|
||||||
if total_goal_cards == 52
|
if total_goal_cards == 52
|
||||||
uiwait(msgbox('You Won! Press R to Try Again!','YAY!','modal'));
|
uiwait(msgbox('You Won! Press R to Try Again!','YAY!','modal'));
|
||||||
|
reset_card_selection();
|
||||||
end
|
end
|
||||||
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
%%% TODO: check for winning condition
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
%% Non-Callback functions
|
%% Non-Callback functions
|
||||||
%Prepare the card holders
|
% Prepare the card holders
|
||||||
function restart(~,evtdata)
|
function restart(~,evtdata)
|
||||||
|
% Press R to try again
|
||||||
if strcmp(evtdata.Key,'r')
|
if strcmp(evtdata.Key,'r')
|
||||||
reset_entire_game(playing_cards);
|
reset_entire_game(playing_cards);
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
function [playing_decks,draw_deck,discard_deck,goal_decks,playfield_size] = prepare_cardHolders(cards,win_ratio)
|
% Prepare the playing field dimension with the card holders
|
||||||
%%% TODO: Automate the draw, discard piles and the goal pile
|
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_size = size(cards(1).get_Card_Image('front'));
|
||||||
card_width = card_size(2);
|
card_width = card_size(2);
|
||||||
card_height = card_size(1);
|
card_height = card_size(1);
|
||||||
|
@ -168,18 +183,17 @@ draw_playfield();
|
||||||
n = 7;
|
n = 7;
|
||||||
border_offset = 10;
|
border_offset = 10;
|
||||||
playfield_width = round((card_size(2)+offset)*n-offset+2*border_offset);
|
playfield_width = round((card_size(2)+offset)*n-offset+2*border_offset);
|
||||||
i = 1;
|
% i = 1;
|
||||||
while(playfield_width-card_width*i>=0)
|
% while(playfield_width-card_width*i>=0)
|
||||||
i = i+1;
|
% i = i+1;
|
||||||
end
|
% end
|
||||||
playfield_width = card_width*i;
|
% playfield_width = card_width*i;
|
||||||
i = 1;
|
% i = 1;
|
||||||
playfield_size = round([playfield_width playfield_width].*win_ratio);
|
playfield_size = round([playfield_width playfield_width].*win_ratio);
|
||||||
while(playfield_size(2)-card_height*i>=0)
|
% while(playfield_size(2)-card_height*i>=0)
|
||||||
i = i+1;
|
% i = i+1;
|
||||||
end
|
% end
|
||||||
playfield_size(2) = card_height*i;
|
% playfield_size(2) = card_height*i;
|
||||||
% Input the number of decks and offset between deck position
|
|
||||||
|
|
||||||
% Compute the position and dimensions
|
% Compute the position and dimensions
|
||||||
start_x = border_offset;
|
start_x = border_offset;
|
||||||
|
@ -187,7 +201,6 @@ draw_playfield();
|
||||||
card_offset = (start_y-card_height-offset)/18;
|
card_offset = (start_y-card_height-offset)/18;
|
||||||
% Initialise the card holders
|
% Initialise the card holders
|
||||||
|
|
||||||
% Manually initialise for testing purposes
|
|
||||||
draw_deck = cardHolder(start_x,playfield_size(2)-border_offset,...
|
draw_deck = cardHolder(start_x,playfield_size(2)-border_offset,...
|
||||||
[],card_width,card_height,card_offset,'horizontal',1,1,1,0);
|
[],card_width,card_height,card_offset,'horizontal',1,1,1,0);
|
||||||
discard_deck = cardHolder(start_x+card_width+offset,playfield_size(2)-border_offset,...
|
discard_deck = cardHolder(start_x+card_width+offset,playfield_size(2)-border_offset,...
|
||||||
|
@ -197,9 +210,10 @@ draw_playfield();
|
||||||
goal_decks(i-3) = cardHolder(start_x+(card_width+offset)*(i-1),...
|
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);
|
playfield_size(2)-border_offset,[],card_width,card_height,card_offset,'vertical',1,0,0,1);
|
||||||
end
|
end
|
||||||
|
% Shuffle the cards
|
||||||
a = randperm(length(cards));
|
a = randperm(length(cards));
|
||||||
remaining_cards = cards(a);
|
remaining_cards = cards(a);
|
||||||
%Loop method, which will be used
|
|
||||||
for i = 1:n
|
for i = 1:n
|
||||||
dealt_cards = remaining_cards(1:i);
|
dealt_cards = remaining_cards(1:i);
|
||||||
playing_decks(i) = cardHolder(start_x+(card_width+offset)*(i-1),...
|
playing_decks(i) = cardHolder(start_x+(card_width+offset)*(i-1),...
|
||||||
|
@ -225,18 +239,21 @@ draw_playfield();
|
||||||
catch
|
catch
|
||||||
disp('Load failed')
|
disp('Load failed')
|
||||||
close all
|
close all
|
||||||
|
% Should maybe have a variable for loading failure
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
% Distribute the playing cards
|
% % Distribute the playing cards, not used
|
||||||
function [dealt_cards,remaining_cards] = deal_cards(cards,amount)
|
% function [dealt_cards,remaining_cards] = deal_cards(cards,amount)
|
||||||
remaining_cards = cards;
|
% remaining_cards = cards;
|
||||||
for i = 1:amount
|
% for i = 1:amount
|
||||||
n_of_cards = length(remaining_cards);
|
% n_of_cards = length(remaining_cards);
|
||||||
index = randi(n_of_cards);
|
% index = randi(n_of_cards);
|
||||||
dealt_cards(i) = remaining_cards(index);
|
% dealt_cards(i) = remaining_cards(index);
|
||||||
remaining_cards = [remaining_cards(1:index-1) remaining_cards(index+1:end)];
|
% remaining_cards = [remaining_cards(1:index-1) remaining_cards(index+1:end)];
|
||||||
end
|
% end
|
||||||
end
|
% end
|
||||||
|
|
||||||
|
% Reset the game
|
||||||
function reset_entire_game(cards)
|
function reset_entire_game(cards)
|
||||||
a = randperm(length(cards));
|
a = randperm(length(cards));
|
||||||
remaining_cards = cards(a);
|
remaining_cards = cards(a);
|
||||||
|
@ -263,18 +280,19 @@ draw_playfield();
|
||||||
|
|
||||||
reset_card_selection();
|
reset_card_selection();
|
||||||
end
|
end
|
||||||
|
|
||||||
% Reset the card selection to none and updating the deck graphic
|
% Reset the card selection to none and updating the deck graphic
|
||||||
function reset_card_selection()
|
function reset_card_selection()
|
||||||
if transferring_deck ~= 0
|
if previous_selected_deck ~= 0
|
||||||
transferring_deck.selected_start_index = 0;
|
previous_selected_deck.selected_start_index = 0;
|
||||||
transferring_deck.update_Deck_Graphics(disp_axes)
|
previous_selected_deck.update_Deck_Graphics(disp_axes)
|
||||||
transferring_deck = 0;
|
previous_selected_deck = 0;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
% Draw the play field
|
% Draw the play field
|
||||||
function draw_playfield()
|
function draw_playfield()
|
||||||
cla(disp_axes); % Clear the play field axes
|
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)
|
for i = 1:length(playing_decks)
|
||||||
playing_decks(i).render_deck_outline(disp_axes);
|
playing_decks(i).render_deck_outline(disp_axes);
|
||||||
playing_decks(i).update_Deck_Graphics(disp_axes);
|
playing_decks(i).update_Deck_Graphics(disp_axes);
|
||||||
|
@ -297,23 +315,26 @@ draw_playfield();
|
||||||
else
|
else
|
||||||
if card == 0
|
if card == 0
|
||||||
if ~isempty(varargin)
|
if ~isempty(varargin)
|
||||||
num = varargin{1};
|
num = varargin{1}; % Specify the value an empty deck should take
|
||||||
else
|
else
|
||||||
num = 14;
|
num = 14; % Allow king to go into empty slots by default
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
num = -1;
|
num = -1; % The deck is having hidden cards
|
||||||
end
|
end
|
||||||
suit = -1;
|
suit = -1;
|
||||||
colour = -1;
|
colour = -1;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
% Open hidden cards automatically
|
||||||
function auto_open_hiddencard()
|
function auto_open_hiddencard()
|
||||||
if (transferring_deck.hidden_start_index >0 ...
|
if (previous_selected_deck.hidden_start_index >0 ...
|
||||||
&& transferring_deck.get_Number_Of_Cards() == transferring_deck.hidden_start_index)
|
&& previous_selected_deck.get_Number_Of_Cards() == previous_selected_deck.hidden_start_index)
|
||||||
transferring_deck.reveal_Hidden_Card(1)
|
previous_selected_deck.reveal_Hidden_Card(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
% Get the mouse position
|
% Get the mouse position
|
||||||
function[X,Y]= get_mouse_pos()
|
function[X,Y]= get_mouse_pos()
|
||||||
mpos = get(disp_axes,'CurrentPoint');
|
mpos = get(disp_axes,'CurrentPoint');
|
||||||
|
|
Loading…
Reference in New Issue