function Tangram_mzip % MATLAB zip file, a self-extracting MATLAB archive. % Usage: Run this file to recreate the original directory. fname = mfilename; fin = fopen([fname '.m'],'r'); dname = fname(1:find(fname=='_',1,'last')-1); mkdir(dname); mkdir([dname filesep 'lib']) addpath(dname) addpath([dname filesep 'lib']) L = fgetl(fin); while length(L) < 2 || ~isequal(L(1:2),'%%') L = fgetl(fin); end while ~isequal(L,'%% EOF') F = [dname filesep L(4:end)]; disp(F) fout = fopen(F,'w'); L = fgetl(fin); while length(L) < 2 || ~isequal(L(1:2),'%%') fprintf(fout,'%s\n',L); L = fgetl(fin); end fclose(fout); end fclose(fin); end %% Tangram.m function P = Tangram % Tangram Tangram puzzle % Copyright 2014-2025 Cleve Moler i = 1i; triangle = @(s) [-s-s*i s-s*i 0 -s-s*i]; square = @(s) [0 s s+s*i s*i 0]; parallelogram = @(s) [-2*s-2*s*i 0-2*s*i s-s*i -s-s*i -2-2*s*i]; T{1} = triangle(2); T{2} = triangle(2); T{3} = triangle(sqrt(2)); T{4} = triangle(1); T{5} = triangle(1); T{6} = square(sqrt(2)); T{7} = parallelogram(1); init_plot(T); xit = init_buttons; if nargout >= 0 return end while xit.Value == 0 drawnow end close(gcf) % ------------------------ function button_motion(varargin) % Button motion, either drag or rotate a piece. % Use complex arithmetic. point = get(gca,'currentpoint'); v = point(1,1) + point(1,2)*1i; hit = get(gca,'userdata'); if ~isempty(hit) && ishandle(hit) && hit ~= 1 u = get(hit,'xdata') + get(hit,'ydata')*1i; z = get(hit,'userdata'); % Check if closer to center or vertex. w = mean(u(1:end-1)); if abs(w-v) < min(abs(u-v)) % Drag u = u - (z - v); else % Rotate about the center by an integer % multiple of pi/20, which is 9 degrees. dtheta = pi/20; theta = angle(v-w) - angle(z-w); theta = round(theta/dtheta)*dtheta; omega = exp(1i*theta); u = omega*(u-w) + w; v = omega*(z-w) + w; end set(hit,'xdata',real(u),'ydata',imag(u),'userdata',v); end end function init_plot(T) % Initialize primary plot. clf shg h = 5.5; w = 11; set(gcf,'position',[100 100 800 400], ... 'numbertitle','off', ... 'menubar','none', ... 'name','Tangram') ax = axes( ... 'color',get(gcf,'color'), ... 'xlim',[-w w], ... 'ylim',[-h h], ... 'userdata',[]); hold on axis off equal colors = dark_2; init_shift = [-2.25-1i 2.25-1i -2.5+1.5i 0.5+1i ... 3.5+1i -2.5+2i 2.5+4i]; for k = 1:7 t = T{k} + init_shift(k); P{k,1} = fill(real(t),imag(t),colors(k,:), ... 'markeredgecolor','black', ... 'linewidth',2, ... 'tag',int2str(k)); end x = [-w w w -w -w]; y = [-h -h h h -h]; plot(x,y,'k-','linewidth',3) set(gcf,'userdata',ax) end function xit = init_buttons(varargin) % Initialize buttons and callbacks. set(gcf, ... 'windowbuttondownfcn',@button_down, ... 'windowbuttonmotionfcn',@button_motion, ... 'windowbuttonupfcn',@button_up) fs = 12; x = 0.72; y = 0.14; bgc = [1 1 1]; uicontrol( ... 'units','normalized', ... 'position',[x y+.24 .16 .06], ... 'style','pushbutton', ... 'fontsize',fs, ... 'fontweight','bold', ... 'string','info', ... 'backgroundcolor',bgc, ... 'callback',@info) uicontrol( ... 'units','normalized', ... 'position',[x y+.16 .16 .06], ... 'style','pushbutton', ... 'fontsize',fs, ... 'fontweight','bold', ... 'string','restart', ... 'backgroundcolor',bgc, ... 'callback',@restart_cb) uicontrol( ... 'units','normalized', ... 'position',[x y+.08 .16 .06], ... 'style','pushbutton', ... 'fontsize',fs, ... 'fontweight','bold', ... 'string','flipper', ... 'backgroundcolor',bgc, ... 'callback',@flipper) xit = uicontrol( ... 'units','normalized', ... 'position',[x y .16 .06], ... 'style','pushbutton', ... 'fontsize',fs, ... 'fontweight','bold', ... 'backgroundcolor',bgc, ... 'string','exit', ... 'callback','close(gcf)'); end function restart_cb(varargin) ex = findobj('string','exit'); set(ex,'value',1); Tangram; end function button_down(varargin) % Select a piece and remember it in userdata. point = get(gca,'currentpoint'); z = point(1,1) + point(1,2)*1i; delete(findobj(gca,'type','line')) h = flipud(get(gca,'children')); hit = []; for k = 1:length(h) x = get(h(k),'xdata'); y = get(h(k),'ydata'); if inregion(real(z),imag(z),x,y) hit = h(k); set(hit,'userdata',z) break end end set(gca,'userdata',hit) h = 5.5; w = 11; x = [-w w w -w -w]; y = [-h -h h h -h]; line(x,y,'color','k','linewidth',3) end function button_up(varargin) % Button up, snap to any nearby piece, then deselect. % Use complex arithmetic. delete(findobj(gca,'type','line')) hit = get(gca,'userdata'); set(gca,'userdata',[]) % Compute distance to nearest vertex of other pieces. z = get(hit,'xdata') + get(hit,'ydata')*1i; h = get(gca,'children'); w = []; for k = 1:length(h) if h(k) ~= hit w = [w; get(h(k),'xdata')+get(h(k),'ydata')*1i]; end end dz = 1; for k = 1:length(z) d = z(k)-w; dw = d(find(abs(d)==min(abs(d)),1)); if abs(dw) < abs(dz) dz = dw; end end % If close enough, snap to nearby piece. tol = 0.5; if abs(dz) < tol set(hit,'xdata',real(z-dz),'ydata',imag(z-dz)) hitcolor = get(hit,'facecolor'); set(hit,'facecolor',min(1.25*hitcolor,1.0)) set(hit,'facecolor',min(1.25*hitcolor,1.0)) pause(1.5) set(hit,'facecolor',hitcolor) end h = 5.5; w = 11; x = [-w w w -w -w]; y = [-h -h h h -h]; line(x,y,'color','k','linewidth',3) end function info(~,~) web('https://blogs.mathworks.com/cleve/') end end %% lib\flipper.m function flipper(~,~) P7 = findobj(gca,'tag','7'); fc = P7.FaceColor; P7.FaceColor = fc/max(fc); x = P7.XData; x = 2*mean(x(1:end-1))-x; pause(2) P7.XData = x; pause(2) P7.FaceColor = fc; end %% lib\snap.m function snap(hit) % Button up, snap to any nearby piece, then deselect. % Use complex arithmetic. % Compute distance to nearest vertex of other pieces. z = get(hit,'xdata') + get(hit,'ydata')*1i; h = get(gca,'children'); w = []; for k = 2:length(h) if h(k) ~= hit w = [w; get(h(k),'xdata')+get(h(k),'ydata')*1i]; end end dz = 1; for k = 1:length(z) d = z(k)-w; dw = d(find(abs(d)==min(abs(d)),1)); if abs(dw) < abs(dz) dz = dw; end end % If close enough, snap to nearby piece. tol = 0.5; if abs(dz) < tol set(hit,'xdata',real(z-dz),'ydata',imag(z-dz)) hitcolor = get(hit,'facecolor'); set(hit,'facecolor',min(1.25*hitcolor,1.0)) pause(1.5) set(hit,'facecolor',hitcolor) end end %% lib\tanmove.m function tanmove(who,x,y) v = get(who,'vertices'); w = v + [x,y]; set(who,'vertices',w) end %% lib\tanrot.m function tanrot(who,deg) v = get(who,'vertices'); mu = mean(v(1:end-1,:)); R = [cosd(deg) sind(deg); -sind(deg) cosd(deg)]; w = (v - mu)*R + mu; set(who,'vertices',w) end %% EOF