TikZ-PGF – The TikZ Game Package: A TeX.SX Project

funscreenplaytikz-pgf

I was wondering if it is possible to write a very simple game in TikZ; e.g. (very simple) Super Mario Bros. Here is Mario:

\documentclass[border=5pt,tikz]{standalone}
\begin{document}
    \begin{tikzpicture}
        \draw[step=.5cm] (0,0) grid (8,8);
        \foreach \x in {2.5,3,...,4.5}
        {
            \fill[red] (\x,7.5) rectangle +(.5,.5);
        }
        \foreach \x in {2,2.5,...,6}
        {
            \fill[red] (\x,7) rectangle +(.5,.5);
        }
        \foreach \x in {2,2.5,3}
        {
            \fill[brown!70!black] (\x,6.5) rectangle +(.5,.5);
        }
        \foreach \x in {3.5,4}
        {
            \fill[yellow!50!orange] (\x,6.5) rectangle +(.5,.5);
        }
        \fill[black] (4.5,6.5) rectangle +(.5,.5);
        \fill[yellow!50!orange] (5,6.5) rectangle +(.5,.5);
            \fill[brown!70!black] (1.5,6) rectangle +(.5,.5);
            \fill[yellow!50!orange] (2,6) rectangle +(.5,.5);
            \fill[brown!70!black] (2.5,6) rectangle +(.5,.5);
            \fill[yellow!50!orange] (3,6) rectangle +(.5,.5);
            \fill[yellow!50!orange] (3.5,6) rectangle +(.5,.5);
            \fill[yellow!50!orange] (4,6) rectangle +(.5,.5);
                \fill[black] (4.5,6) rectangle +(.5,.5);
                \fill[yellow!50!orange] (5,6) rectangle +(.5,.5);
                \fill[yellow!50!orange] (5.5,6) rectangle +(.5,.5);
                \fill[yellow!50!orange] (6,6) rectangle +(.5,.5);
        \begin{scope}[shift={(0,-.5)}]
            \fill[brown!70!black] (1.5,6) rectangle +(.5,.5);
            \fill[yellow!50!orange] (2,6) rectangle +(.5,.5);
            \fill[brown!70!black] (2.5,6) rectangle +(.5,.5);
            \fill[brown!70!black] (3,6) rectangle +(.5,.5);
            \fill[yellow!50!orange] (3.5,6) rectangle +(.5,.5);
            \fill[yellow!50!orange] (4,6) rectangle +(.5,.5);
                \fill[yellow!50!orange] (4.5,6) rectangle +(.5,.5);
                \fill[black] (5,6) rectangle +(.5,.5);
                \fill[yellow!50!orange] (5.5,6) rectangle +(.5,.5);
                \fill[yellow!50!orange] (6,6) rectangle +(.5,.5);
                \fill[yellow!50!orange] (6.5,6) rectangle +(.5,.5);
        \end{scope}
        \fill[brown!70!black] (2,5) rectangle +(.5,.5);
        \foreach \x in {2.5,3,...,4}
        {
            \fill[yellow!50!orange] (\x,5) rectangle +(.5,.5);
        }
        \foreach \x in {4.5,5,...,6}
        {
            \fill[black] (\x,5) rectangle +(.5,.5);
        }
        \foreach \x in {2.5,3,...,5}
        {
            \fill[yellow!50!orange] (\x,4.5) rectangle +(.5,.5);
        }
        \foreach \x in {2,2.5,...,5.5}
        {
            \fill[red] (\x,4) rectangle +(.5,.5);
        }
        \fill[blue] (3,4) rectangle +(.5,.5);
        \fill[blue] (4.5,4) rectangle +(.5,.5);
        \begin{scope}[shift={(0,-.5)}]
            \foreach \x in {1.5,2,...,6}
            {
                \fill[red] (\x,4) rectangle +(.5,.5);
            }
            \fill[blue] (3,4) rectangle +(.5,.5);
            \fill[blue] (4.5,4) rectangle +(.5,.5);
        \end{scope}
        \begin{scope}[shift={(0,-1)}]
            \foreach \x in {1,1.5,2,...,6.5}
            {
                \fill[red] (\x,4) rectangle +(.5,.5);
            }
            \fill[blue] (3,4) rectangle +(.5,.5);
                \fill[blue] (3.5,4) rectangle +(.5,.5);
                \fill[blue] (4,4) rectangle +(.5,.5);
            \fill[blue] (4.5,4) rectangle +(.5,.5);
        \end{scope}
        \fill[yellow!50!orange] (1,2.5) rectangle +(.5,.5);
        \fill[yellow!50!orange] (1.5,2.5) rectangle +(.5,.5);
            \fill[red] (2,2.5) rectangle +(.5,.5);
            \fill[blue] (2.5,2.5) rectangle +(.5,.5);
                \fill[yellow!50!orange] (3,2.5) rectangle +(.5,.5);
            \fill[blue] (3.5,2.5) rectangle +(.5,.5);
            \fill[blue] (4,2.5) rectangle +(.5,.5);
                \fill[yellow!50!orange] (4.5,2.5) rectangle +(.5,.5);
            \fill[blue] (5,2.5) rectangle +(.5,.5);
            \fill[red] (5.5,2.5) rectangle +(.5,.5);
                \fill[yellow!50!orange] (6,2.5) rectangle +(.5,.5);
                \fill[yellow!50!orange] (6.5,2.5) rectangle +(.5,.5);
        \foreach \x in {1,1.5,2}
        {
            \fill[yellow!50!orange] (\x,2) rectangle +(.5,.5);
        }
        \foreach \x in {2.5,3,...,5}
        {
            \fill[blue] (\x,2) rectangle +(.5,.5);
        }
        \fill[yellow!50!orange] (5.5,2) rectangle +(.5,.5);
        \fill[yellow!50!orange] (6,2) rectangle +(.5,.5);
        \fill[yellow!50!orange] (6.5,2) rectangle +(.5,.5);
        \begin{scope}[shift={(0,-1)}]
            \fill[yellow!50!orange] (1,2.5) rectangle +(.5,.5);
            \fill[yellow!50!orange] (1.5,2.5) rectangle +(.5,.5);
                \fill[blue] (2,2.5) rectangle +(.5,.5);
                \fill[blue] (2.5,2.5) rectangle +(.5,.5);
                    \fill[blue] (3,2.5) rectangle +(.5,.5);
                \fill[blue] (3.5,2.5) rectangle +(.5,.5);
                \fill[blue] (4,2.5) rectangle +(.5,.5);
                    \fill[blue] (4.5,2.5) rectangle +(.5,.5);
                \fill[blue] (5,2.5) rectangle +(.5,.5);
                \fill[blue] (5.5,2.5) rectangle +(.5,.5);
                    \fill[yellow!50!orange] (6,2.5) rectangle +(.5,.5);
                    \fill[yellow!50!orange] (6.5,2.5) rectangle +(.5,.5);
        \end{scope}
            \foreach \x in {2,2.5,3}
            {
                \fill[blue] (\x,1) rectangle +(.5,.5);
            }
        \begin{scope}[shift={(2.5,0)}]
            \foreach \x in {2,2.5,3}
            {
                \fill[blue] (\x,1) rectangle +(.5,.5);
            }
        \end{scope}
        \foreach \x in {1.5,2,2.5}
        {
            \fill[brown!70!black] (\x,.5) rectangle +(.5,.5);
        }
        \begin{scope}[shift={(3.5,0)}]
            \foreach \x in {1.5,2,2.5}
            {
                \fill[brown!70!black] (\x,.5) rectangle +(.5,.5);
            }
        \end{scope}
        \begin{scope}[shift={(0,-.5)}]
            \foreach \x in {1,1.5,2,2.5}
            {
                \fill[brown!70!black] (\x,.5) rectangle +(.5,.5);
            }
            \begin{scope}[shift={(4,0)}]
                \foreach \x in {1,1.5,2,2.5}
                {
                    \fill[brown!70!black] (\x,.5) rectangle +(.5,.5);
                }
            \end{scope}
        \end{scope}
    \end{tikzpicture}
\end{document}

Here is the output:

Screenshot

And here is the original:

Screenshot

Because you can create simple animations and controls in TikZ (see the Rubiks thread), it is maybe possible to generate a world (e.g. from Mario) , which is refreshed by some buttons.

For the beginn the pong game would be absolutely enough I think, because it is really hard to implement such things especially in TikZ.

As the title says, it would sound great if something like that would exist, so my question is: Does anybody have an idea how to do that? This is not a do-it-for-me – question, rather collecting ideas.

Best Answer

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.

enter image description here

\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):

enter image description here

\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}