1

Snake Games

Module[
{dir = {-1, 0}, x = -1., y = 0., snake = Table[{i, 0}, {i, 0, 4}],
gameover = False,
target = {RandomInteger[17] - 9, RandomInteger[17] - 9},
timecount = 0,
score = 0},
Manipulate[
If[
 Mod[timecount, 5] == 0 && u != {-1, -1} && gameover == False,
x = u[[1]];
y = u[[2]];
dir = If[
 y >= x,
 If[y >= -x, {0, 1}, {-1, 0}],
 If[y >= -x, {1, 0}, {0, -1}]
 ];
 If[
  MemberQ[snake, 
    snake[[1]] + dir] || (snake[[1]] + dir)[[
     1]] < -10 || (snake[[1]] + dir)[[1]] > 
    9 || (snake[[1]] + dir)[[2]] < -10 || (snake[[1]] + dir)[[2]] > 
    9,
  gameover = True;
  score = Length[snake] - 5
  ];
 If[
  (snake[[1]] + dir) == target,
  target = {RandomInteger[17] - 9, RandomInteger[17] - 9},
  snake = Most[snake]
  ];
 PrependTo[snake, snake[[1]] + dir]
 ];
timecount++;
Framed[
 Graphics[
 If[
  False == gameover,
  Append[
   Table[Rectangle[i, i + {1, 1}], {i, snake}],
   Rectangle[target, target + {1, 1}]
   ],
  {Rectangle[{-10, -10}, {10, 10}], 
   Text[Style[" Game over! ", FontSize -> 36], {0, 3}, 
    Background -> White], 
   Text[Style[" Score: ", FontSize -> 24], {0, 0}, 
    Background -> White], 
   Text[Style[score, FontSize -> 24], {0, -2}, Background -> White]}
   ],
   Axes -> False, PlotRange -> {{-10, 10}, {-10, 10}}, 
   ImagePadding -> 0
   ],
  FrameMargins -> 0
 ],
{u, {-1, -1}, {1, 1}}, ControlPlacement -> Right
]
]

It is not convenient for the user to play this game in the above form. So how can I rewrite it without using Manipulate, but using other functions that can provide a better user interface?

m_goldberg
  • 107,779
  • 16
  • 103
  • 257

2 Answers2

8

Writing games using dynamic interactivity in mathematica is an amusing subject! I will vote up your post for this. But I agree with others that it is hard to answer your question, you should work out minimal example for us first.

Below I am giving my code for tetris. I recognize, it is a nonminimal answer, but it is working and can be useful for you. Controls are with arrow keys. To make controls working, make the focus on the panel, e.g. click "MatTetris!". Enjoy.

numFigures=6;speed=6;
gLen=16;gWidth=10;bckgColor=Black;bckgColor2=Gray;figColor=Red;patColor=Blue;
fig[1]={{1,-1},{1,0},{0,0},{0,1}};
fig[2]={{-1,-1},{-1,0},{0,0},{0,1}};
fig[3]={{-1,0},{0,0},{1,0},{0,1}};
fig[4]={{-1,0},{0,0},{1,0},{2,0}};fig[5]={{-1,1},{0,1},{0,0},{0,-1}};
fig[6]={{1,1},{0,1},{0,0},{0,-1}};
mRight={{0,1},{-1,0}};
mLeft={{0,-1},{1,0}};

MovementPossible[where_]:=Block[{altData=Which[
 where=="down",{figData[[1]]+{0,-1},figData[[2]]},
where=="left",{figData[[1]]+{-1,0},figData[[2]]},
where=="right",{figData[[1]]+{1,0},figData[[2]]},
where=="rotleft",{figData[[1]],(mLeft.#)&/@figData[[2]]},
where=="rotright",{figData[[1]],(mRight.#)&/@figData[[2]]}
]}
,(Count[patData,Alternatives@@((altData[[1]]+#)&/@figData[[2]])]==0)&&(Count[altData[[1,2]]+altData[[2,All,2]],x_/;x<1]==0)&&(Count[altData[[1,1]]+altData[[2,All,1]],x_/;((x<1)||(x>gWidth))]==0)];
Move[where_]:=(figData=Which[
 where=="down",{figData[[1]]+{0,-1},figData[[2]]},
where=="left",{figData[[1]]+{-1,0},figData[[2]]},
where=="right",{figData[[1]]+{1,0},figData[[2]]},
where=="rotleft",{figData[[1]],(mLeft.#)&/@figData[[2]]},
where=="rotright",{figData[[1]],(mRight.#)&/@figData[[2]]}
]);
AddFigureToPattern:=(patData=Union[patData,(figData[[1]]+#)&/@figData[[2]]];figData={{0,0},{}});
AddingFigurePossible:=(Count[patData,Alternatives@@(({Round[gWidth/2],gLen-1}+#)&/@prefigData)]==0);
AddFigure:=(figData={{Round[gWidth/2],gLen-1},prefigData};prefigData=fig[RandomInteger[{1,numFigures}]]);
DeleteLines:=Block[{lineCount=0,ic=1},
        While[ic<=gLen,
                If[Count[patData,{_,ic}]==gWidth,
                patData=DeleteCases[patData,{_,ic}];patData=((#/.{xx_,yy_/;yy>ic}:>{xx,yy-1})&/@patData);++lineCount;--ic,
                ++ic];
                ]
            Score[lineCount]];
Score[lC_]:=Which[lC==1,totScore+=1,
                lC==2,totScore+=3,
                lC==3,totScore+=6,
                lC==4,totScore+=10];
GameOver:=(CreateDialog[{Column[{Style["Game Over!",FontColor->Red,FontSize->18],
Row[{Style["Your score is: ",FontColor->Blue,FontSize->18],Style[totScore,FontColor->Blue,FontSize->18]}]
}],DefaultButton[ResetGame;DialogReturn[]]}];)
ResetGame:=(patData={};prefigData=fig[RandomInteger[{1,numFigures}]];FigureActive=False;totScore=0;notGamingOver=True)
MainCycle:=If[FigureActive,
    If[MovementPossible["down"],
    Move["down"];,
    FigureActive=False;AddFigureToPattern;DeleteLines;],
    If[AddingFigurePossible,
    AddFigure;FigureActive=True;,
    If[notGamingOver,notGamingOver=False;GameOver];]
];
gameBoard:=EventHandler[
Panel[Row[
{Dynamic@Graphics[{{bckgColor,Rectangle[{0,0},{gWidth,gLen}]},{figColor,Rectangle[#-{1,1},#]&/@((figData[[1]]+#)&/@figData[[2]])},{patColor,Rectangle[#-{1,1},#]&/@patData}},ImageSize->300],
Column[{
Style["MatTetris!",FontColor->Blue,FontSize->18],,Row[{Style["Score:  ",FontColor->Red,FontSize->18],Dynamic@Style[totScore,FontColor->Red,FontSize->18]}],,,Dynamic@Graphics[{{bckgColor2,Rectangle[{-2,-2},{2,1}]},{figColor,Rectangle[#-{1,1},#]&/@prefigData}}],,,,,,,,,,,,,,,,""
},Center]
}]],
{"LeftArrowKeyDown":>If[MovementPossible["left"],Move["left"]],"RightArrowKeyDown":>If[MovementPossible["right"],Move["right"]],"DownArrowKeyDown":>If[MovementPossible["down"],Move["down"]],"UpArrowKeyDown":>If[MovementPossible["rotleft"],Move["rotleft"]]}];

LaunchGame:=(ResetGame;MainCycle;gameBoard);

LaunchGame
While[True, MainCycle; Pause[1 - speed/10]]
Vel
  • 383
  • 2
  • 6
6

I think you've actually done a good job considering you are only using Manipulate. Here's a simplified snake game using Dynamic:

snake = {{0, 0}, {0, 1}, {0, 2}};
dir = {0, -1};

directions = {
   "UpArrowKeyDown" :> (dir = {0, 1}),
   "DownArrowKeyDown" :> (dir = {0, -1}),
   "LeftArrowKeyDown" :> (dir = {-1, 0}),
   "RightArrowKeyDown" :> (dir = {1, 0})};

EventHandler[
 Graphics[
  Dynamic[Rectangle /@ snake],
  PlotRange -> 5],
 directions]

While[True, Pause[.5];
 snake = Most[snake]~Prepend~(snake[[1]] + dir);]

Here I put the Dynamic inside the Graphics expression, rather than wrapping the whole Graphics with a Dynamic. This works because Graphics is designed to let it work, and it does make a difference (in general) if you need performance.

I recommend you read the documentation for Dynamic (especially the "Details") and the Advanced Dynamic Functionality tutorial. It's kinda boring but it's useful.

Another thing I should point out, that wasn't obvious to me at first, is that Dynamic uses Set, quite explicitly. What this means in practice is that Dynamic works, for example, with array locations:

array = {1, 2, 3};
Dynamic[array]
Slider[Dynamic[array[[2]]]]
While[True, Pause[.5]; array[[2]] = RandomReal[]]

As well as for function downvalues such as f[2]. This makes the programming easier for larger controls, such as matrix controls and the like.

amr
  • 5,487
  • 1
  • 22
  • 32