388 lines
15 KiB
Matlab
388 lines
15 KiB
Matlab
%% Information
|
|
% Original author: En Yi
|
|
% A snake game on MATLAB, very self-explanatory
|
|
% Have fun!
|
|
% Anyone can modify it, just need to give credits to the original author
|
|
function snek_final()
|
|
%% Creating the game window
|
|
clc;clear;
|
|
%Default Constants
|
|
default_updatetime = 0.08;
|
|
default_ftsize = 10;
|
|
%Create the update timer, used for continuously updating the game screen
|
|
update_t = timer('ExecutionMode','fixedSpacing','Period',default_updatetime,'TimerFcn',@update_screen);
|
|
%Create the window
|
|
scrsz = get(0,'ScreenSize');
|
|
start_dim = min(scrsz(3)/1.5,scrsz(4)/1.5);%Used for rescaling
|
|
win = figure('KeyPressFcn',@key_check,'DeleteFcn',@delete_t,'ToolBar',...
|
|
'none','Name','SNEK','NumberTitle','off','MenuBar','none',...
|
|
'Resize','off','Visible','off','Color',[170 255 211]/255,...
|
|
'Position',[[scrsz(3),scrsz(4)]/4.5 start_dim start_dim]);
|
|
%For displaying instructions and score
|
|
load_map_button = uicontrol('Style','pushbutton','Units','Normalized',...
|
|
'Position',[0.01 0.01 0.2 0.08],'FontSize',default_ftsize,...
|
|
'Callback',@select_mappack,'String','Change Pack');
|
|
console_text = uicontrol('Style','text','Units','Normalized',...
|
|
'Position',[0.25 0.01 0.7 0.08],'FontSize',default_ftsize);
|
|
%Just to display the version and author
|
|
uicontrol('Style','text','Units','Normalized','Position',[24,28,6,2]/30,...
|
|
'String','V1.0 Made by En Yi','BackgroundColor',[170 255 211]/255);
|
|
%Create the axes for drawing the game
|
|
disp_axes = axes('Parent',win);
|
|
%graze = 0;
|
|
|
|
%% Load the map levels and define game variables
|
|
%Prepare the map
|
|
width = 30;height = 30;
|
|
map_number = 0;n_of_maps = 0;
|
|
maze_map = {};map_name = {};speed = [];map = zeros(width,height,3);
|
|
start_body={};player_body = [];player_len = 1;move_dir = 2;
|
|
body_colour = {};selected_colour = [];fruit_colour = [0.5 0.5 0];
|
|
load_map_pack('mazemap')
|
|
%Define the fruit fruit_values+bonus and player score
|
|
fruit_value = 0;
|
|
score = 0;
|
|
max_value = 60;
|
|
fruit_x = -1;fruit_y = -1;
|
|
%Create the sounds for collecting a fruit and losing the game
|
|
Fs = 10000;
|
|
t = 0:1/Fs:.02;
|
|
y = sin(2*pi*4000*t);
|
|
beep = audioplayer(y, Fs);
|
|
t = 0:1/Fs:0.1;
|
|
x = sawtooth(2*pi*60*t);
|
|
x = [x zeros(1,5) x];
|
|
gover = audioplayer(x, Fs);
|
|
%Define the variable to check whether the player is playing the game
|
|
game_on = 0;
|
|
%Update the screen and display controls
|
|
update_screen();
|
|
set(win,'Visible','on')
|
|
set(console_text,'String',{'WASD or Arrows key to select map','Space to start'});
|
|
%% Callback functions
|
|
%Function to update the game screen
|
|
function update_screen(~,~)
|
|
scr_mat = map; %Put in the current map
|
|
if(game_on ==1) %If the player is playing
|
|
isdead = move(scr_mat); %Move the player and check whether the player is dead
|
|
if(isdead) %If so
|
|
stop(update_t); %Stop continuously updating the screen
|
|
set(console_text,'String',{'Game Over. Press R to restart.',...
|
|
sprintf('Final Score : %d\n',floor(score/10))} );
|
|
play(gover);
|
|
return %Stop the game
|
|
end
|
|
end
|
|
%Put in the player body after updating
|
|
body_sz = size(player_body);
|
|
scr_mat(player_body(2,1),player_body(1,1),1) = 1;
|
|
for n=2:body_sz(2)
|
|
scr_mat(player_body(2,n),player_body(1,n),:) = selected_colour;
|
|
end
|
|
%Spawn the fruit if it is collected
|
|
if(fruit_x == -1)
|
|
scr_mat = spawn_fruit(scr_mat);
|
|
fruit_value = max_value; %Set the fruit fruit_values
|
|
end
|
|
%Put in the fruit
|
|
scr_mat(fruit_y,fruit_x,:) = fruit_colour;
|
|
%Set focus on the game window
|
|
figure(win);
|
|
%Draw the game screen
|
|
image(scr_mat,'Parent',disp_axes);
|
|
set(disp_axes,'XTickLabel',[],'YTickLabel',[]);
|
|
if (map_number>0)
|
|
title(map_name{map_number});
|
|
else
|
|
title('Open');
|
|
end
|
|
|
|
if(game_on ==0) %If the player is not playing
|
|
%Readjust the window screen to fit the aspect ratio of the level
|
|
current_winpos = get(win,'Position');
|
|
topright = current_winpos(2)+current_winpos(4);
|
|
new_winpos = [0 0 width height]/max([0 0 width height])...
|
|
*start_dim;
|
|
new_winpos = new_winpos+[current_winpos(1) topright-new_winpos(4) 0 0];
|
|
set(win,'Position',new_winpos);
|
|
end
|
|
end
|
|
%Function to check the key presses
|
|
function key_check(~,evtdata)
|
|
%disp(evtdata.Key);
|
|
if(game_on == 1)
|
|
%When the game is running
|
|
%Direction keys change the direction of the snake
|
|
%The inputs are recorded up to 3 key presses for updating
|
|
n = length(move_dir)+1;
|
|
if (n<4)
|
|
if (move_dir(end)~=2 && move_dir(end)~=1)
|
|
switch(evtdata.Key)
|
|
case 'leftarrow'
|
|
move_dir(n) = 1;
|
|
case 'rightarrow'
|
|
move_dir(n) = 2;
|
|
case 'a'
|
|
move_dir(n) = 1;
|
|
case 'd'
|
|
move_dir(n) = 2;
|
|
end
|
|
elseif (move_dir(end)~=3 && move_dir(end)~=4)
|
|
switch(evtdata.Key)
|
|
case 'uparrow'
|
|
move_dir(n) = 3;
|
|
case 'downarrow'
|
|
move_dir(n) = 4;
|
|
case 'w'
|
|
move_dir(n) = 3;
|
|
case 's'
|
|
move_dir(n) = 4;
|
|
end
|
|
end
|
|
end
|
|
%Press R to try again. restarting the screen
|
|
if(strcmp(evtdata.Key,'r'))
|
|
restart();
|
|
end
|
|
else
|
|
%When the game is not running
|
|
%Direction keys change the map
|
|
if(strcmp(evtdata.Key,'leftarrow') ||strcmp(evtdata.Key,'rightarrow')||...
|
|
strcmp(evtdata.Key,'a') ||strcmp(evtdata.Key,'d'))
|
|
switch(evtdata.Key)
|
|
case 'leftarrow'
|
|
map_number = map_number -1;
|
|
case 'rightarrow'
|
|
map_number = map_number +1;
|
|
case 'a'
|
|
map_number = map_number -1;
|
|
case 'd'
|
|
map_number = map_number +1;
|
|
end
|
|
if map_number>n_of_maps
|
|
map_number = 0;
|
|
elseif map_number<0
|
|
map_number = n_of_maps;
|
|
end
|
|
restart();
|
|
end
|
|
end
|
|
%Space to start the game
|
|
if(strcmp(evtdata.Key,'space'))
|
|
if(game_on == 0)
|
|
set(load_map_button,'Enable','off');
|
|
drawnow;
|
|
start(update_t);
|
|
game_on =1;
|
|
set(console_text,'String',{sprintf('Score : %d\n',floor(score/10));} );
|
|
end
|
|
end
|
|
end
|
|
%% Non Callback functions
|
|
%Function to update the player coordinates and check is it dead
|
|
function[isdead] = move(scr_mat)
|
|
isdead = 0;
|
|
player_newx = player_body(1,1);player_newy = player_body(2,1);
|
|
%Check the next key press if there is one, otherwise last key press
|
|
if(length(move_dir)>1)
|
|
move_dir = move_dir(2:end);
|
|
end
|
|
switch(move_dir(1))
|
|
case 1
|
|
player_newx = player_newx - 1;
|
|
case 2
|
|
player_newx = player_newx + 1;
|
|
case 3
|
|
player_newy = player_newy - 1;
|
|
case 4
|
|
player_newy = player_newy + 1;
|
|
end
|
|
%Wrap the player around the edge of the map
|
|
if player_newx>width
|
|
player_newx = 1;
|
|
end
|
|
if player_newx<1
|
|
player_newx = width;
|
|
end
|
|
if player_newy>height
|
|
player_newy = 1;
|
|
end
|
|
if player_newy<1
|
|
player_newy = height;
|
|
end
|
|
%Check if the head of the player intersect with the body
|
|
xcheck = (player_body(1,2:end)==player_newx);
|
|
ycheck = (player_body(2,2:end)==player_newy);
|
|
if(sum(scr_mat(player_newy,player_newx,:))>2.9 ||any(xcheck & ycheck))
|
|
% if(graze <1)
|
|
% graze = graze+1;
|
|
% else
|
|
isdead = 1;
|
|
return
|
|
%end
|
|
else
|
|
%graze = 0;
|
|
if (player_len >1)
|
|
player_body = [[player_newx;player_newy] player_body(:,1:end-1)];
|
|
else
|
|
player_body = [player_newx;player_newy];
|
|
end
|
|
end
|
|
%Check if the player head intersect with the fruit
|
|
if(player_body(1,1) == fruit_x && player_body(2,1) == fruit_y)
|
|
fruit_x = -1;
|
|
play(beep);
|
|
player_len = player_len+1;
|
|
player_body = [player_body player_body(:,end)];
|
|
score = score + fruit_value;
|
|
set(console_text,'String',{sprintf('Score : %d',floor(score/10)),...
|
|
sprintf('+%.1f',fruit_value/10)} );
|
|
else
|
|
%Decrease the fruit value if it is not collected
|
|
if fruit_value>10
|
|
fruit_value = fruit_value -1;
|
|
else
|
|
fruit_value = 10;
|
|
end
|
|
end
|
|
end
|
|
%Function to spawn the fruit
|
|
function [scr_mat] = spawn_fruit(scr_mat)
|
|
comp_mat = sum(scr_mat,3);
|
|
[empty_y,empty_x] = find(comp_mat==0);
|
|
fruit_pos = floor(rand(1)*length(empty_x))+1;
|
|
fruit_x = empty_x(fruit_pos);fruit_y = empty_y(fruit_pos);
|
|
end
|
|
%Function to restart the game
|
|
function restart()
|
|
stop(update_t); %Stop continuously updating the game
|
|
game_on = 0; %Indicate the game is not running
|
|
map_load(map_number); %Load the selected map
|
|
fruit_x = -1; %Remove the fruit
|
|
fruit_value = 1; %Reset the fruit value
|
|
score = 0; %Reset the score
|
|
update_screen(); %Update the screen
|
|
set(console_text,'String',{'WASD or Arrows key to select map','Space to start'});
|
|
set(load_map_button,'Enable','on');
|
|
end
|
|
%Function to load the selected map
|
|
function map_load(num)
|
|
if(num>0)
|
|
map = maze_map{num};
|
|
map_sz = size(maze_map{num});
|
|
map_sz_norm = map_sz/max(map_sz);
|
|
width = map_sz(2);
|
|
height = map_sz(1);
|
|
set(update_t,'Period',round(1000/speed(num))/1000);
|
|
max_value = ceil(60*map_sz(2)*map_sz(1)/900*speed(num)/12.5);
|
|
set(console_text,'FontSize',round(default_ftsize*map_sz_norm(2)*map_sz_norm(1)));
|
|
player_body = start_body{num};
|
|
selected_colour = body_colour{num};
|
|
player_len = length(player_body(1,:));
|
|
if (player_len>1)
|
|
if(player_body(1,1)==player_body(1,2))
|
|
if(player_body(2,1)>player_body(2,2))
|
|
move_dir = 4;
|
|
else
|
|
move_dir = 3;
|
|
end
|
|
else
|
|
if(player_body(1,1)>player_body(1,2))
|
|
move_dir = 2;
|
|
else
|
|
move_dir = 1;
|
|
end
|
|
end
|
|
else
|
|
move_dir = 2;
|
|
end
|
|
else
|
|
width = 30;
|
|
height = 30;
|
|
map = zeros(height,width,3);
|
|
set(update_t,'Period',default_updatetime);
|
|
max_value = 60;
|
|
set(console_text,'FontSize',default_ftsize);
|
|
player_body = zeros(2,3); %Reset the player body
|
|
selected_colour = [0.7 0.2 0.5];
|
|
player_headx = round(30/2);
|
|
player_heady = round(30/2);
|
|
player_len = 3;
|
|
for n=1:player_len
|
|
player_body(1,n) = player_headx-n+1;
|
|
player_body(2,n) = player_heady;
|
|
end
|
|
move_dir = 2;
|
|
end
|
|
end
|
|
function load_map_pack(fname)
|
|
try
|
|
mzmp = load(fname);
|
|
maze_map = mzmp.map;
|
|
n_of_maps = length(maze_map);
|
|
map_name = mzmp.map_name;
|
|
speed = mzmp.speed;
|
|
start_body = mzmp.player_body;
|
|
body_colour = mzmp.colours;
|
|
map_number = 0;
|
|
map_load(map_number);
|
|
catch
|
|
errorbox = errordlg('mazemap.mat might not exist or not compatible within the directory. No levels loaded','modal');
|
|
uiwait(errorbox);
|
|
maze_map = {};
|
|
n_of_maps = 0;
|
|
map_number = 0;
|
|
% player_body = zeros(2,3); %Reset the player body
|
|
% player_headx = round(30/2);
|
|
% player_heady = round(30/2);
|
|
% player_len = 3;
|
|
% for n=1:player_len
|
|
% player_body(1,n) = player_headx-n+1;
|
|
% player_body(2,n) = player_heady;
|
|
% end
|
|
mapload(0);
|
|
end
|
|
end
|
|
%Function to delete the timer when the game is closed
|
|
function delete_t(~,~)
|
|
stop(update_t);
|
|
delete(update_t);
|
|
end
|
|
function select_mappack(~,~)
|
|
winpos = get(win,'Position');
|
|
change_pack = 0;change_confirm = 0;
|
|
changepack = dialog('Name','Select a Map Pack','Position',[winpos(1:2)+winpos(3:4)/4 winpos(3:4)/1.5]);
|
|
matfiles = what;
|
|
matfiles = matfiles.mat;
|
|
file_listbox = uicontrol('Style','listbox','Units','normalized',...
|
|
'Position',[0.1 0.1 0.5 0.85],...
|
|
'String',matfiles);
|
|
uicontrol('Style','pushbutton','Units','normalized',...
|
|
'Position',[0.7 0.7 0.2 0.2],...
|
|
'String','Okay','Callback',@confirm_change);
|
|
uicontrol('Style','pushbutton','Units','normalized',...
|
|
'Position',[0.7 0.5 0.2 0.2],...
|
|
'String','Cancel','Callback',@cancel_change);
|
|
function confirm_change(~,~)
|
|
fnum = get(file_listbox,'value');
|
|
change_pack = matfiles{fnum};
|
|
change_pack = strrep(change_pack,'.mat','');
|
|
change_confirm = 1;
|
|
close(changepack);
|
|
end
|
|
function cancel_change(~,~)
|
|
close(changepack);
|
|
end
|
|
uiwait(changepack);
|
|
|
|
if(change_confirm)
|
|
load_map_pack(change_pack);
|
|
restart();
|
|
confirm = msgbox('Load Complete','modal');
|
|
uiwait(confirm);
|
|
end
|
|
set(load_map_button,'Enable','off')
|
|
drawnow;
|
|
set(load_map_button,'Enable','on')
|
|
end
|
|
end |