Putting Coder for Raspberry Pi through its paces
Coding Coders
The development environment is divided into five parts. The four text-editing tabs, from left to right, are: HTML for your app's web page markup, CSS for the style sheets, JS for your app's front-end programming logic, and Node, which is where you can play with the Node.js-based web server and create server-side logic (e.g., connections to databases and so on).
The folder icon opens the Media panel (Figure 5). You can upload snippets, such as sounds, icons, and sprites, from here to use later in your app. Unfortunately, Coder does not offer a preview of any of the uploaded files, so you'll have to give them really descriptive names to avoid getting confused later on.
The eye icon shows your app running alongside the code you are developing (Figure 6). The app is reloaded every time you change something in your code, so you can test your modifications immediately.
Finally, the gear icon lets you configure the name, author, and color scheme of your app. You can also delete and export your app from here – I'll talk more about that last feature later.
Mine Coder
Because Coder is meant to help you code, let's code! I already wrote a version of the Game of Life in JavaScript elsewhere [4], so I decided to go with another of my favorite time-wasters: Minesweeper! Yes, I am a man of simple tastes.
The Game of Life canvas I used for Life is very HTML5, so I decided not to do that again; instead, I chose to go low-tech and use straight bitmaps (Figure 7) within HTML and use jQuery to manipulate all the changes to the bits and pieces. I also went for a very minimalistic approach to keep things as clear as possible.
The first step in creating a new app is to click on the rectangle with the plus sign on the main screen. You will prompted to give your app a name and a color theme. Once you click Create, Coder sets up minimal skeletons for HTML, CSS, and JavaScript (you will not need Node.js for this project).
I designed the tiles in Inkscape, exported each one as a 20x20-pixel PNG image, and uploaded them to the project via the Media panel. In Figure 7 you can see all the tiles.From left to right and top to bottom, you can see covered.png
, which represents a covered square in the minefield grid. Next, the files 0_uncovered.png
through 8_uncovered.png
represent uncovered squares and are used to show the number of neighboring mines in each square as the player uncovers them. Then, there's an explosion tile and, finally, a square with an uncovered mine, which is used when the whole minefield is shown at game's end.
The HTML file (Listing 1) is very barebones. The <head>...</head>
section has not been modified in any way, but the lines
<link href="/static/apps/coderlib/css/index.css" media="screen" rel="stylesheet" type="text/css"/>
and
<script src="/static/apps/coderlib/js/index.js"></script>
are worth pointing out. These lines load in the CSS and JavaScript files that you are able to modify for your app from within Coder.
Listing 1
Mine Coder HTML
01 <!DOCTYPE html> 02 <html> 03 <head > 04 <title>Coder</title> 05 <meta charset="utf-8"> 06 <!-- Standard Coder Includes --> 07 <script> 08 var appname = "{{app_name}}"; //app name (id) of this app 09 var appurl = "{{&app_url}}"; 10 var staticurl = "{{&static_url}}"; //base path to your static files /static/apps/yourapp 11 </script> 12 <link href="/static/apps/coderlib/css/index.css" media="screen" rel="stylesheet" type="text/css"/> 13 <script src="/static/common/js/jquery.min.js"></script> 14 <script src="/static/common/ace-min/ace.js" type="text/JavaScript" charset="utf-8"></script> 15 <script src="/static/apps/coderlib/js/index.js"></script> 16 <script> 17 Coder.addBasicNav(); 18 </script> 19 <!-- End Coder Includes --> 20 21 <!-- This app's includes --> 22 <link href="{{&static_url}}/css/index.css" media="screen" rel="stylesheet" type="text/css"/> 23 <script src="{{&static_url}}/js/index.js"></script> 24 <!-- End apps includes --> 25 </head> 26 <body class=""> 27 <div class="pagecontent"> 28 <div id="boardsection"> 29 <div id="board"> 30 </div> 31 </div> 32 33 <div id="splash"> 34 <button id="start">Start</button> 35 </div> 36 </div> 37 38 </body> 39 </html>
The <body>...</body>
section is split into two sections: <div id="board">...</div>
will contain the board generated by the JavaScript, and <div id="splash">...</div>
contains a button that starts a new game when clicked.
The CSS (Listing 2) contains the default layout for .pagecontent
provided by Coder. I want to make sure the div #boardsection
is hidden by default until I'm ready to show a board. To stack the tiles next to one another without any spaces between them, I use the float: left;
attribute on the .cell
class. (Quick explanatory detour: I haven't shown the HTML .cell
yet because it is generated dynamically by the JavaScript, so I'll get to it a bit later. The same goes for rows … .)
Listing 2
Mine Coder CSS
01 .pagecontent { 02 padding: 24px; 03 } 04 05 #boardsection { 06 display: none; 07 } 08 09 .cell { 10 float: left; 11 } 12 13 .row { 14 clear: both; 15 } 16 17 #splash { 18 clear: both; 19 }
The exact opposite, however, goes for stacking rows: To stack each row of cells one on top of the other, I have to clear any float parameter, as well as the start button, which I will want to show below the grid when a game has finished.
Next up is the JavaScript (Listing 3), where most of the magic happens. Lines 1 to 15 are the main routine. For those unfamiliar with jQuery, using a $(document).ready(function() { ... });
function is a standard way of executing JavaScript code when – and only when – the web page has completely loaded.
Listing 3
Mine Coder JavaScript
001 $(document).ready(function() { 002 var board_o = new board(10, 10, 15); 003 004 $("#start").click(function(){ 005 $("#splash").hide(); 006 $("#boardsection").show(); 007 008 board_o.initiate(); 009 $("#board").replaceWith(board_o.draw()); 010 011 $(".cell").on("click", function(){ 012 $(this).replaceWith(board_o.flip($(this).attr('id'))); 013 }); 014 }); 015 }); 016 017 var cell = function (x, y){ 018 this.pos=[x, y]; 019 this.state="covered"; 020 this.mine=false; 021 this.neighbours=0; 022 }; 023 024 var board = function (sizex, sizey, mines){ 025 this.size=[sizex, sizey]; 026 this.mines=mines; 027 this.cell_o=[]; 028 029 this.initiate = function(){ 030 for (i=0; i<this.size[0]; i++){ 031 this.cell_o[i]=[]; 032 for(j=0; j<this.size[1]; j++){ 033 this.cell_o[i][j]=new cell(i,j); 034 } 035 } 036 this.placeMines(); 037 this.countNeighbours(); 038 this.uncovered=0; 039 }; 040 041 this.draw = function(){ 042 var web_string="<div id='board'>"; 043 for (j=0; j<this.size[1]; j++){ 044 web_string = web_string + "<div class='row'>"; 045 for (i=0; i<this.size[0]; i++){ 046 web_string = web_string + "<img class='cell' id='" + i + "_" + j + "' src='/static/apps/mine_coder/media/"+this.cell_o[i][j].state+".png'>"; 047 } 048 web_string=web_string + "</div>"; 049 } 050 return web_string + "</div>"; 051 }; 052 053 this.convertId = function(my_id){ 054 return (my_id.split("_")); 055 }; 056 057 this.flip = function(my_id){ 058 var pos=my_id.split("_"); 059 var x=parseInt(pos[0]); var y=parseInt(pos[1]); 060 061 if (this.cell_o[x][y].mine) { 062 this.cell_o[x][y].state="explosion_uncovered"; 063 this.showBoard(x,y); 064 } 065 066 else { 067 this.cell_o[x][y].state=this.cell_o[x][y].neighbours + "_uncovered"; 068 this.uncovered++; 069 if(this.uncovered==(this.size[0]*this.size[1])-this.mines){ 070 this.showBoard(this.size[0], this.size[1]); 071 alert("You won!") 072 } 073 } 074 return ("<img class='cell' id='" + my_id + "' src='/static/apps/mine_coder/media/ "+this.cell_o[x][y].state+".png'>"); 075 }; 076 077 078 this.showBoard = function(x,y){ 079 for (i=0; i<this.size[0]; i++){ 080 for (j=0; j<this.size[1]; j++){ 081 if(i!=x || j!=y){ 082 this.cell_o[i][j].state=this.cell_o[i][j].neighbours + "_uncovered"; 083 if (this.cell_o[i][j].mine){this.cell_o[i][j].state="mine_uncovered";} 084 } 085 } 086 } 087 $("#board").replaceWith(this.draw()); 088 $("#splash").show(); 089 }; 090 091 this.placeMines = function(){ 092 for (i=0; i<this.mines; i++){ 093 x=Math.ceil((Math.random()*this.size[0]-1)); 094 y=Math.ceil((Math.random()*this.size[1]-1)); 095 if (this.cell_o[x][y].mine===false) {this.cell_o[x][y].mine=true;} 096 } 097 }; 098 099 // Count number of neighbouring mines 100 this.countNeighbours = function(){ 101 for (i=0; i<this.size[0]; i++){ 102 for (j=0; j<this.size[1]; j++){ 103 if (this.cell_o[i][j].mine===false){ 104 for(m=i-1; m<i+2; m++){ 105 for(n=j-1; n<j+2; n++){ 106 if (m>-1 && n>-1 && m<this.size[0] && n<this.size[1] && !(m==i && n==j)){ 107 if (this.cell_o[m][n].mine){this.cell_o[i][j].neighbours++;} 108 } 109 } 110 } 111 } 112 } 113 } 114 }; 115 };
In this case, I create a board
object, set its size (10x10 squares) and the number of mines it contains (15), then start running the game when the #start
button is clicked. I hide the button, show the div that contains the board, create an instance, and initiate the board
object. The board
object (lines 24-115) is a bit of a sprawling mess, but, in my defense, I will say it contains a two-dimensional array of cell
objects (lines 17-22), which is a rather complex thing to do in JavaScript.
First, to create a bi-dimensional array, you have to define a single-dimension array (line 27) and then loop through every element and jam a new array into each element of the original array (line 31). The effort is worth it, however, because you can then use the array of arrays to set up the playing field grid and jam a cell object into each item with its x, y coordinates. That's what the loops in lines 30 to 35 do, setting all squares as covered and empty (i.e., containing no mines).
Line 36 calls a method that randomly scatters mines throughout the playing field (lines 91-97). To save time later on during game play, the script then works out how many neighboring mines the empty squares have (lines 100-114) and stores that number in the cell's neighbours
attribute. I also set the counter that keeps track of the uncovered square to 0
(line 38).
Back in the main routine, I use board
's draw
method and jQuery's replaceWith
method to push the board to the app (line 9). The draw
method (line 41) builds a long HTML string containing all the rows and the img
elements (cells) by looking at the state
attribute of each cell
object in the grid.
At this stage in the game, draw
returns a 10x10 grid of covered squares.
Back at the main routine, line 11 listens for a click event on each and every square (all belonging to the CSS .cell
class I talked about earlier) and, when it comes along, it calls the flip
method to decide what happens next (Figure 8). The flip
method (lines 57-75) looks at the clicked square's state
attribute and decides what to show. This step is quite easy to do, because each square is an HTML independent img
element with its own generated id
attribute (line 74) that includes its coordinates on the board. This allows me to change that image and only that image using, again, the jQuery replaceWith
method (line 12). So clever.
There are two exceptions to the above process. When a player clicks on a square containing a mine, flip
pushes an explosion_uncovered
state to the cell, and then all the board is uncovered using the showboard
method (lines 78-89). The game is then over (Figure 9).
The second exception is when there are no more empty squares to uncover. The program keeps track of how many squares the player uncovers in line 68. When the player has uncovered all the empty squares (i.e., the total number of squares in the grid minus the number of mines), the player wins. Again, the complete board is shown, and the game ends (Figure 10).
The end result is not pretty, but it works, up to a point. If your board is wider than your browser window, it's going to look broken. Also, there is no autoclear function when you click on an empty square. I figured that I'd leave the pleasures of debugging recursive algorithms to the reader.
« Previous 1 2 3 Next »
Buy this article as PDF
Pages: 7
(incl. VAT)