The TikZ Game Package – A TeX.SX Project
Sliding Puzzle
(Acrobat Reader required; pdflatex
or lualatex
)
This is a non-trivial game with user interaction, based on PDF Layers (OCGs, package ocgx2
).
The picture is split into 15 tiles, stacked upon each other for every cell of the puzzle board, each on an individual PDF Layer. The total number of PDF Layers is thus 4 x 4 x 15 = 240. Their visibility is controlled by JavaScript, triggered by clicking on an array of transparent buttons overlaid on top of the puzzle board.
\documentclass[tikz,margin=1mm]{standalone}
\usepackage{xsavebox}
\usepackage[tikz]{ocgx2}
\usepackage{media9} % \mediabutton
\usepackage{tikzmarmots}
\usepackage{xcolor}\pagecolor{gray}
\usepackage{multido}
\usepackage{ifluatex}
\ifluatex\def\pdfpageattr{\pdfvariable pageattr}\fi
\begin{xlrbox}{Marmot}
\begin{tikzpicture}[xscale=-1,transform shape]
\useasboundingbox (0,0) rectangle (4.4,4.4);
\node at (current bounding box.center) {\tikz\marmot[scale=2.1];};
\end{tikzpicture}
\end{xlrbox}
%generating tiles Marmot.1, ..., Marmot.15
\multido{\nX=0.0+1.1,\iI=0+1}{4}{
\multido{\nY=3.3+-1.1,\iJ=0+1}{4}{
\ifnum\numexpr\iI+\iJ\relax>0
\begin{xlrbox}{Marmot.\the\numexpr\iI+\iJ*4\relax}
\begin{tikzpicture}
\useasboundingbox (\nX,\nY) rectangle ++(1.1,1.1);
\clip (\nX,\nY) rectangle ++(1.1,1.1);
\node[inner sep=0pt, outer sep=0pt, anchor=south west,fill=white] at (0,0) {\theMarmot};
\end{tikzpicture}
\end{xlrbox}
\fi
}
}
\newcommand\MyStatus[2]{\ifnum#1=#2 visible\else invisible\fi}
%initialisations (JavaScript) on page-open (/O <<...>>)
\begingroup
\edef\x{\endgroup
\pdfpageattr{
\the\pdfpageattr
/AA << /O << /S/JavaScript /JS (%
var tile=new Array(4);
var oldTile=new Array(4);
for(var i=0;i<4;i++){
tile[i]=new Array(4);
oldTile[i]=new Array(4);
for(var j=0;j<4;j++){
tile[i][j]=new Array(15);
oldTile[i][j]=i+j*4-1;
}
}
ocg=this.getOCGs(this.pageNum);
for(var i in ocg){
ocgName=ocg[i].name.split('.');
tile[ocgName[0]][ocgName[1]][ocgName[2]-1]=ocg[i];
}%
var swap_i=0; var swap_j=0;
var onButtonClick=function(i,j){
if(swap_i==i && swap_j!=j){
for(var jj=swap_j; jj!=j; swap_j<j ? jj++ : jj--){
if(jj!=swap_j) tile[i][jj][oldTile[i][jj]].state=false;
newTile=oldTile[i][swap_j<j ? jj+1 : jj-1];
oldTile[i][jj]=newTile;
tile[i][jj][newTile].state=true;
}
} else if(swap_j==j && swap_i!=i){
for(var ii=swap_i; ii!=i; swap_i<i ? ii++ : ii--){
if(ii!=swap_i) tile[ii][j][oldTile[ii][j]].state=false;
newTile=oldTile[swap_i<i ? ii+1 : ii-1][j];
oldTile[ii][j]=newTile;
tile[ii][j][newTile].state=true;
}
}
if(swap_i==i || swap_j==j){
if(oldTile[i][j]>-1)
tile[i][j][oldTile[i][j]].state=false;
oldTile[i][j]=-1;
swap_i=i; swap_j=j;
}
};
) >> >>
}
}
\x
\begin{document}
\begin{tikzpicture}
\multido{\nX=0.0+1.1,\iI=0+1}{4}{%
\multido{\nY=3.3+-1.1,\iJ=0+1}{4}{%
%tiles
\multido{\iK=1+1}{15}{%
\begin{scope}[ocg={ref=\iI.\iJ.\iK,status=\MyStatus{\the\numexpr\iI+\iJ*4\relax}{\iK}}]%
\node[inner sep=0pt, outer sep=0pt, anchor=south west] at (\nX,\nY) {\xusebox{Marmot.\iK}};
\end{scope}
}
%button array
\draw[
line width=0,
postaction={
path picture={
\path (path picture bounding box.south west) coordinate (p1)
(path picture bounding box.north east) coordinate (p2)
(p1) node[inner sep=0pt,anchor=south west,outer sep=0pt] {%
\mediabutton[jsaction={try{onButtonClick(\iI,\iJ);}catch(e){}}]{%
\tikz \useasboundingbox (p1) rectangle (p2);%
}%
};
}
}
] (\nX,\nY) rectangle ++(1.1,1.1);
}
}
\end{tikzpicture}
\end{document}
Original Answer
For this kind of games based on a pixel array using a given set of pre-defined colours, the TikZ-part is straightforward to be implemented; making use of package ocgx2
, we simply put each pixel colour on an individual PDF Layer.
The whole logic of the game can then be implemented in JavaScript. It programmatically controls the visibility of each pixel colour.
As a trivial example, we implement a "game" whose sole purpose is to shuffle the pixel colours by pressing a button and to enjoy the result (Acrobat Reader or Foxit required for viewing):
\documentclass[tikz,margin=1mm]{standalone}
\usepackage[tikz]{ocgx2}
\usepackage{media9} % \mediabutton
\newcommand{\mycolour}[1]{\ifcase#1
white%0
\or
black%1
\or
red%2
\or
orange%3
\or
blue%4
\or
brown%5
\or
yellow%6
\fi}
\begin{document}
%set up canvas (16x16 pixels^2) x (7 colours)
\begin{tikzpicture}
\foreach \X in {0,1,...,15} {
\foreach \Y in {0,1,...,15} {
\foreach \Z in {0,1,...,6} {
\begin{scope}[ocg={ref=\X.\Y.\Z,status=invisible}]
\node[minimum size=0.5cm,draw,fill=\mycolour{\Z}] at (\X/2,-\Y/2){};
\end{scope}
}
}
}
\node at (3.75,-8) {%
\mediabutton[
jsaction={
%initialisation
if(typeof pixelcolour==='undefined'){
var pixelcolour=new Array(16);
var oldcolour=new Array(16);
for(var i=0;i<16;i++){
pixelcolour[i]=new Array(16);
oldcolour[i]=new Array(16);
for(var j=0;j<16;j++){
pixelcolour[i][j]=new Array(7);
oldcolour[i][j]=0;
}
}
ocg=this.getOCGs(this.pageNum);
for(i in ocg){
ocgName=ocg[i].name.split('.');
pixelcolour[ocgName[0]][ocgName[1]][ocgName[2]]=ocg[i];
}
}
%randomise colours
for(i=0;i<16;i++){
for(j=0;j<16;j++){
newcolour=Math.floor(Math.random()*7);
pixelcolour[i][j][oldcolour[i][j]].state=false;
pixelcolour[i][j][newcolour].state=true;
oldcolour[i][j]=newcolour;
}
}
}
]{\fbox{Random}}
};
\end{tikzpicture}
\end{document}
Four steps with Super Mario.
\documentclass{article}
\usepackage{tikz}
\usepackage{animate}
\newcommand{\MarioStop}{
\fill[red] (2.5,8) -- (5,8) -- (5,7.5) -- (6.5,7.5) -- (6.5,7) -- (2,7) -- (2,7.5) -- (2.5,7.5);
\fill[brown!70!black] (2,7) -- (3.5,7) -- (3.5,6.5) -- (3,6.5) -- (3,6) -- (3.5,6) -- (3.5,5.5) -- (2.5,5.5) -- (2.5,6.5) -- (2,6.5);
\fill[brown!70!black] (4.5,7) -- (4.5,6) -- (5,6) -- (5,7);
\fill[brown!70!black](5,6) -- (5.5,6) -- (5.5,5.5) -- (6.5,5.5) -- (6.5,5) -- (4.5,5) -- (4.5,5.5) -- (5,5.5);
\fill[brown!70!black] (1.5,6.5) -- (2,6.5) -- (2,5.5) -- (2.5,5.5) -- (2.5,5) -- (1.5,5);
\fill[yellow!50!orange] (3.5,7) -- (4.5,7) -- (4.5,6) -- (5,6) -- (5,5.5) -- (4.5,5.5) -- (4.5,5) -- (6,5) -- (6,4.5) -- (2.5,4.5) -- (2.5,5.5) -- (3.5,5.5) -- (3.5,6) -- (3,6) -- (3,6.5) -- (3.5,6.5);
\fill[yellow!50!orange] (2,6.5) -- (2.5,6.5) -- (2.5,5.5) -- (2,5.5);
\fill[yellow!50!orange] (5,7) -- (5.5,7) -- (5.5,6.5) -- (6.5,6.5) -- (6.5,6) -- (7,6) -- (7,5.5) -- (5.5,5.5) -- (5.5,6) -- (5,6);
\fill[red] (2,4.5) -- (3,4.5) -- (3,3) -- (2.5,3) -- (2.5,2.5) -- (2,2.5) -- (2,3) -- (1,3) -- (1,3.5) -- (1.5,3.5) -- (1.5,4) -- (2,4);
\fill[red] (3.5,4.5) -- (5.5,4.5) -- (5.5,4) -- (6.5,4) -- (6.5,3.5) -- (7,3.5) -- (7,3) -- (6,3) -- (6,2.5) -- (5.5,2.5) -- (5.5,3) -- (5,3) -- (5,4) -- (4.5,4) -- (4.5,3.5) -- (3.5,3.5);
\fill[blue] (3,4.5) -- (3.5,4.5) -- (3.5,3.5) -- (4.5,3.5) -- (4.5,4) -- (5,4) -- (5,3) -- (5.5,3) -- (5.5,2) -- (6,2) -- (6,1) -- (4.5,1) -- (4.5,1.5) -- (3.5,1.5) -- (3.5,1) -- (2,1) -- (2,2) -- (2.5,2) -- (2.5,3) -- (3,3);
\fill[yellow!50!orange] (3,3) -- (3.5,3) -- (3.5,2.5) -- (3,2.5);
\fill[yellow!50!orange] (4.5,3) -- (5,3) -- (5,2.5) -- (4.5,2.5);
\fill[yellow!50!orange] (6,3) -- (7,3) -- (7,1.5) -- (6,1.5) -- (6,2) -- (5.5,2) -- (5.5,2.5) -- (6,2.5);
\fill[yellow!50!orange] (1,3) -- (2,3) -- (2,2.5) -- (2.5,2.5) -- (2.5,2) -- (2,2) -- (2,1.5) -- (1,1.5);
\fill[brown!70!black] (1.5,1) -- (3,1) -- (3,0) -- (1,0) -- (1,0.5) -- (1.5,0.5);
\fill[brown!70!black] (5,1) -- (6.5,1) -- (6.5,0.5) -- (7,0.5) -- (7,0) -- (5,0);
}
\newcommand{\MarioFStep}{
\fill[red] (3,7.5) -- (5.5,7.5) -- (5.5,7) -- (7,7) -- (7,6.5) -- (2.5,6.5) -- (2.5,7) -- (3,7);
\fill[brown!70!black] (2.5,6.5) -- (4,6.5) -- (4,6) -- (3.5,6) -- (3.5,5.5) -- (4,5.5) -- (4,5) -- (3,5) -- (3,6) -- (2.5,6);
\fill[brown!70!black] (2,6) -- (2.5,6) -- (2.5,5) -- (3,5) -- (3,4.5) -- (2,4.5);
\fill[brown!70!black] (5,6.5) -- (5,5.5) -- (5.5,5.5) -- (5.5,6.5);
\fill[brown!70!black] (5.5,5.5) -- (6,5.5) -- (6,5) -- (7,5) -- (7,4.5) -- (5,4.5) -- (5,5) -- (5.5,5);
\fill[brown!70!black] (1.5,2.5) -- (2.5,2.5) -- (2.5,2) -- (2,2) -- (2,1) -- (1.5,1) -- (1.5,0.5) -- (1,0.5) -- (1,1.5) -- (1.5,1.5);
\fill[brown!70!black] (3.5,1) -- (5,1) -- (5,0.5) -- (5.5,0.5) -- (5.5,0) -- (3.5,0);
\fill[yellow!50!orange] (2.5,6) -- (3,6) -- (3,5) -- (2.5,5);
\fill[yellow!50!orange] (4,6.5) -- (5,6.5) -- (5,5.5) -- (5.5,5.5) -- (5.5,5) -- (5,5) -- (5,4.5) -- (6.5,4.5) -- (6.5,3.5) -- (7,3.5) -- (7,3) -- (6.5,3) -- (6.5,2.5) -- (5.5,2.5) -- (5.5,3.5) -- (6,3.5) -- (6,4) -- (3,4) -- (3,5) -- (4,5) -- (4,5.5) -- (3.5,5.5) -- (3.5,6) -- (4,6);
\fill[yellow!50!orange] (5.5,6.5) -- (6,6.5) -- (6,6) -- (7,6) -- (7,5.5) -- (7.5,5.5) -- (7.5,5) -- (6,5) -- (6,5.5) -- (5.5,5.5);
\fill[yellow!50!orange] (2,3.5) -- (2.5,3.5) -- (2.5,2.5) -- (1.5,2.5) -- (1.5,3) -- (2,3);
\fill[red] (2.5,4) -- (4.5,4) -- (4.5,3.5) -- (5,3.5) -- (5,4) -- (5.5,4) -- (5.5,2.5) -- (3,2.5) -- (3,3) -- (2.5,3);
\fill[blue] (4.5,4) -- (4.5,3.5) -- (5,3.5) -- (5,4);
\fill[blue] (2.5,3) -- (3,3) -- (3,2.5) -- (6,2.5) -- (6,1.5) -- (5.5,1.5) -- (5.5,1) -- (4,1) -- (4,1.5) -- (3.5,1.5) -- (3.5,1) -- (2,1) -- (2,2) -- (2.5,2);
}
\newcommand{\MarioSStep}{
\fill[red] (3,8) -- (5.5,8) -- (5.5,7.5) -- (7,7.5) -- (7,7) -- (2.5,7) -- (2.5,7.5) -- (3,7.5);
\fill[red] (1.5,4.5) -- (3.5,4.5) -- (3.5,3) -- (2.5,3) -- (2.5,3.5) -- (1.5,3.5);
\fill[red] (4.5,4.5) -- (5.5,4.5) -- (5.5,4) -- (6.5,4) -- (6.5,3.5) -- (7,3.5) -- (7,3) -- (6,3) -- (6,3.5) -- (5,3.5) -- (5,4) -- (4.5,4);
\fill[brown!70!black] (2.5,7) -- (4,7) -- (4,6.5) -- (3.5,6.5) -- (3.5,6) -- (4,6) -- (4,5.5) -- (3,5.5) -- (3,6.5) -- (2.5,6.5);
\fill[brown!70!black] (2,6.5) -- (2.5,6.5) -- (2.5,5.5) -- (3,5.5) -- (3,5) -- (2,5);
\fill[brown!70!black] (5,7) -- (5,6) -- (5.5,6) -- (5.5,7);
\fill[brown!70!black] (5.5,6) -- (6,6) -- (6,5.5) -- (7,5.5) -- (7,5) -- (5,5) -- (5,5.5) -- (5.5,5.5);
\fill[brown!70!black] (1,1.5) -- (2,1.5) -- (2,1) -- (2.5,1) -- (2.5,0.5) -- (3,0.5) -- (3,0) -- (1.5,0) -- (1.5,0.5) -- (1,0.5);
\fill[brown!70!black] (6.5,2.5) -- (7,2.5) -- (7,3) -- (7.5,3) -- (7.5,1) -- (6.5,1);
\fill[yellow!50!orange] (4,7) -- (5,7) -- (5,6) -- (5.5,6) -- (5.5,5.5) -- (5,5.5) -- (5,5) -- (6.5,5) -- (6.5,4.5) -- (3,4.5) -- (3,5.5) -- (4,5.5) -- (4,6) -- (3.5,6) -- (3.5,6.5) -- (4,6.5);
\fill[yellow!50!orange] (2.5,6.5) -- (3,6.5) -- (3,5.5) -- (2.5,5.5);
\fill[yellow!50!orange] (5.5,7) -- (6,7) -- (6,6.5) -- (7,6.5) -- (7,6) -- (7.5,6) -- (7.5,5.5) -- (6,5.5) -- (6,6) -- (5.5,6);
\fill[yellow!50!orange] (0.5,4) -- (1.5,4) -- (1.5,3.5) -- (2,3.5) -- (2,3) -- (1.5,3) -- (1.5,2.5) -- (0.5,2.5);
\fill[yellow!50!orange] (6.5,4) -- (8,4) -- (8,3) -- (7,3) -- (7,3.5) -- (6.5,3.5);
\fill[blue] (3.5,4.5) -- (4.5,4.5) -- (4.5,4) -- (5,4) -- (5,3.5) -- (6,3.5) -- (6,2.5) -- (6.5,2.5) -- (6.5,1) -- (5,1) -- (5,1.5) -- (3.5,1.5) -- (3.5,1) -- (2,1) -- (2,1.5) -- (1.5,1.5) -- (1.5,2) -- (2,2) -- (2,2.5) -- (2.5,2.5) -- (2.5,3) -- (3.5,3);
\fill[yellow!50!orange] (4,3.5) -- (4.5,3.5) -- (4.5,3) -- (4,3);
}
\begin{document}
\begin{animateinline}[autoplay,loop,
begin={\begin{tikzpicture}},
end={\end{tikzpicture}}]{2}
\MarioStop\newframe
\MarioFStep\newframe
\MarioSStep\newframe
\MarioFStep\newframe
\MarioSStep\newframe
\MarioFStep\newframe
\MarioSStep\newframe
\MarioFStep\newframe
\MarioSStep\newframe
\MarioStop
\end{animateinline}
\end{document}