So I came up with this little nice game... I started out with 4*4-1 pieces, and had it - with bare UI - up to 9*9-1 - just displaying the tiles area and just having logic for creating the 'orderly' board and moving tiles. My UI enhancements then cut me short with Out of Memory to a max of 5x5-1... even after getting rid of some nice code commenting. I will bring some of the comments back in this post.
Two conclusions:
Arrays were killers 40+ years ago, and still are today!
Viva Pico! *I'm looking very much forward to a chip with more memory, seriously...
There are good news though, despite the OutOfMemory incident: The TOUCHR module got hardened: n = n - 1 with n = number of bugs (n closing in on 0).
Moving tiles around sometimes still freezes the system... no clue why, even after noticing that sometimes my TOUCHR module was not listening anymore (after telling twice to listen... fixed the code in the listen() method. If you use my code from the other post, fix it like this to ignore a listen() command while already listening(). (The faulty code did a partial toggle and left touch detection in a weird state):
To get a better handle on discovering a freeze, I put a setBusyIndicator(LED3); into the code to see if it is my listening or something else. I noticed, that when the system is frozen and needs either a hard-reset or power-cycle, it is busy and never goes to sleep again. It could be that some unchecked run away in array or most likely the graphics is messing with me... (after me messing with boundaries... sooooorrrrrrry! - Tschumpuhung - find the translation yourself).
Back into the - Puzzle 16 - game: this is the code:
// puzzle16_inlineRC.js (c) muet.com
// puzzle 16 game
// ********* following inline TOUCHR code is only part of attached file
// var touchrModule = { connect: function(xn,xp,yn,yp,callback,C) {
// ...
var lcd, touchr, p16;
SPI1.setup({sck:B3, miso:B4, mosi:B5, baud: 1000000});
var lcd = require("ILI9341").connect(SPI1, B6, B8, B7, function() {
lcd.clear();
p16 =
{ t: 0
, ts: []
, c: 0
, cs: []
, X: 240
, Y: 320
, x0: 0
, y0: 0
, xp: 240
, yp: 320
, x: 4
, y: 4
, xn: 4
, yn: 4
, xns: []
, yns: []
, m: 15
, bl: 2
, bt: 40
, br: 2
, bb: 40
, xe: 0
, ye: 0
, d: 60
, clrs: [[0,0,0],[1,1,1],[1,1,0],[0,0,1]]
, ps: []
, ls: []
, tpd: function(xt,yt) { var i,xr,yr,xo,yo,dp=this.d;
if ((i = (xt>=this.x0) && (xt<this.x0+this.xp)
&& (yt>=this.y0) && (yt<this.y0+this.yp) )) {
this.upd(Math.floor(getTime() - this.t),this.ts);
var x = Math.floor((xr=(xt-(xo=this.x0+this.bl-1)))/dp);
var y = Math.floor((yr=(yt-(yo=this.y0+this.bt-1)))/dp);
if (y < 0) {
} else if (y < this.y) {
if ((x >=0) && (x < this.x)) {
var p = this.ps[y][x], n = p[0];
if ( n >= 0) {
var ey = this.ls[this.m][0], ex = this.ls[this.m][1];
var e = this.ps[ey][ex];
if (ey == y) { this.c++;
this.mvx(y,x,e,ex,(ex<x) ? +1 : -1,yo,xo,dp);
} else if (ex == x) {
this.mvy(y,x,e,ey,(ey<y) ? +1 : -1,yo,xo,dp);
}
}
}
} else { x = Math.floor(xr/((this.xp-2*2)/8));
if ((x === 0) && (this.xn>3)) {
this.xn--; this.upd(this.xn,this.xns);
} else if ((x == 1) && (this.xn<5)) {
this.xn++; this.upd(this.xn,this.xns);
} else if ((x == 2) && (this.yn>3)) {
this.yn--; this.upd(this.yn,this.yns);
} else if ((x == 3) && (this.yn<5)) {
this.yn++; this.upd(this.yn,this.yns);
} else if (x>3&&x<6) {
this.scr(this.xn,this.yn);
} else if (x>5&&x<8) {
this.lup(this.xn,this.yn);
}
}
}
return i;
}
// move horizontally +-x
, mvx: function(y,x,e,ex,d,yo,xo,dp) {
var p, xp = xo + ex * dp, yp = yo + y * dp;
this.c++; this.upd(this.c,this.cs);
lcd.setFontVector(dp*0.6);
while (ex != x + d) {
if (ex == x) {
p = this.ps[y][ex] = e;
this.ls[this.m] = [y,ex];
} else {
p = this.ps[y][ex] = this.ps[y][ex + d];
this.ls[p[0]] = [y,ex];
}
this.drp(p,xp,yp,xp+dp-1,yp+dp-1);
ex += d; xp += d * dp;
}
}
// move vertically +-y
, mvy: function(y,x,e,ey,d,yo,xo,dp) {
var p, xp = xo + x * dp, yp = yo + ey * dp;
this.c++; this.upd(this.c,this.cs);
lcd.setFontVector(dp*0.6);
while (ey != y + d) {
if (ey == y) {
p = this.ps[ey][x] = e;
this.ls[this.m] = [ey,x];
} else {
p = this.ps[ey][x] = this.ps[ey + d][x];
this.ls[p[0]] = [ey,x];
}
this.drp(p,xp,yp,xp+dp-1,yp+dp-1);
ey += d; yp += d * dp;
}
}
// draw pieces
, drw: function() {
var p,x=this.x,y=this.y,d=this.d;
var x1=this.x0+this.bl-1,y1=this.y0+this.bt-1;
var x2=this.x0+this.xp-this.br,y2=this.y0+this.yp-this.bb;
this.clr(0); lcd.fillRect(x1,y1,x2,y2);
y1=y1-d; y2=y1+d-1; lcd.setFontVector(d*0.6);
for (var yx = 0; yx<y; yx++) {
y1 += d; y2 += d; x1=this.x0+this.bl-1-d; x2=x1+d-1;
for (var xx = 0; xx<x; xx++) {
x1 += this.d; x2 += d; p = this.ps[yx][xx];
this.drp(p,x1,y1,x2,y2);
}
}
}
// draw piece - 1..m*n-1(0..this.m-1=x*y-2) - empty: -1
, drp: function(p,x1,y1,x2,y2) {
var n=p[0],rgb = this.clr(1 + ((n<0) ? 0 : n % 2 + 1));
if (n>=0) {
lcd.fillRect(x1,y1,x2-1,y2-1);
var c = (rgb[0]*4 + rgb[1]*2 + rgb[0])^7;
lcd.setColor(c&4,c&2,c&1);
var o = this.d * 0.0125;
lcd.drawString(n+1,x1+o*((n>8) ? 1 : 20),y1+o*14);
} else {
this.clr(0);lcd.fillRect(x1,y1,x2-1,y2-1);
}
}
// line up pieces (value pair optional)
, lup: function(x,y) {
this.ein(false);
this.inp(x,y);
this.drw();
this.ein(true);
}
// scramble pieces
, scr: function(x,y) {
this.ein(false);
this.inp(x,y);
var c,p1,p2,r1,r2,s=(this.m+1)*3;
for (c=0; c<s; c++) {
p1 = this.ls[r1 = Math.floor(Math.random() * this.m)];
p2 = this.ls[r2 = Math.floor(Math.random() * this.m)];
this.ls[r2] = p1; this.ls[r1] = p2;
}
for (c=0; c<=this.m; c++) {
this.ps[this.ls[c][0]][this.ls[c][1]][0]
= (c<this.m) ? c : -1;
}
this.drw();
this.ein(true);
}
// init pieces (value pair optional)
, inp: function(x,y) {
this.spl(x,y);
var n = -1;
this.ps = new Array(this.y);
this.ls = new Array(this.m+1);
for (var yx=0; yx<this.y; yx++) {
this.ps[yx] = new Array(this.x);
for (var xx=0; xx<this.x; xx++) { n++;
if (n < this.m) {
this.ps[yx][xx] = [n];
this.ls[n] = [yx,xx];
} else {
this.ps[yx][xx] = [-1];
this.ls[n] = [yx,xx];
}
}
}
}
// set color
, clr: function(iorgb) {
var rgb=(isNaN(iorgb)) ? iorgb : this.clrs[iorgb];
lcd.setColor(rgb[0],rgb[1],rgb[2]);
return(rgb);
}
// update value as specified
, upd: function(v,s) {
lcd.setFontVector(s[0]); var xd = lcd.stringWidth(v);
this.clr(0); lcd.fillRect(s[1],s[2],s[1]+xd,s[2]+s[0]);
this.clr(1); lcd.drawString(v,s[1],s[2]);
}
// set pieces x*y, @ x0,y0 size area xp*yp px
// paddings left,top,right,bot, lcd X*Y px
, spl: function(x,y,xp,yp,x0,y0,bl,bt,br,bb,X,Y) {
if (X && Y) { this.X = X; this.Y = Y; }
if (x && y) { this.x=this.xn=x; this.y=this.yn=y; }
if (xp && yp) { this.xp = xp; this.yp = yp; }
if (!isNaN(x0)) { this.x0=x0; }
if (!isNaN(y0)) { this.y0=y0; }
if (!isNaN(bl)) { this.bl=bl; }
if (!isNaN(bt)) { this.bt=bt; }
if (!isNaN(br)) { this.br=br; }
if (!isNaN(bb)) { this.bb=bb; }
var dxm = (this.xp - this.bl - this.br) / this.x;
var dym = (this.yp - this.bt - this.bb) / this.y;
this.d = Math.floor((dxm < dym) ? dxm : dym);
this.m = this.x * this.y - 1;
}
// init
// x*y pieces, xp*yp display area
, ini: function(x,y,xp,yp,x0,y0,bl,bt,br,bb,X,Y) {
this.spl(x,y,xp,yp,x0,y0,bl,bt,br,bb,X,Y);
this.inf();
this.inp();
this.drw();
this.ein(true);
}
// init frame
, inf: function() {
var x1=this.x0,y1=this.y0;
var x2=x1+this.xp-1,y2=y1+this.yp-1;
this.clr(0); lcd.fillRect(x1,y1,x2,y2);
this.clr(1); lcd.drawRect(x1,y1,x2,y2);
var x=x1+2, y=y1+this.yp-this.bb+1;
var s=Math.floor((x2-x1-2*2)/4), c = Math.floor(s/3);
var f=(c<this.bb-2)?c:this.bb-2; lcd.setFontVector(f);
lcd.drawString("-",x,y); x+=c;
lcd.drawString(this.xn,x,y); this.xns=[f,x,y]; x+=c;
lcd.drawString("+",x,y); x = x1+2+s;
lcd.drawString("-",x,y); x+=c;
lcd.drawString(this.yn,x,y); this.yns=[f,x,y]; x+=c;
lcd.drawString("+",x,y); x = x1+2+s*2;
lcd.drawString("SCR",x,y); x += s;
lcd.drawString("ORD",x,y);
x=x1+2; y=this.y0+2;
f=(c<this.bt-2)?c:this.bt-2;
lcd.drawString("Time:",x,y); x+=s;
this.ts=[f,x,y]; x+=s;
lcd.drawString("Mvs:",x,y); x+=s;
this.cs=[f,x,y];
}
// enable input
, ein: function(b) {
touchr.listen(b);
if (b) { var s="0 ";
this.upd(s,this.ts); this.upd(s,this.cs);
this.c=0; this.t=getTime();
}
}
};
touchr = touchrModule.connect(C0,C1,C2,C3,function(x,y,tr){
if (!tr.t) {
if (p16.tpd(tr.x,tr.y)) { return; }
}
});
setBusyIndicator(LED3);
// pieces 4*4, in 220*300 at 10,10
// paddings left 3,top 40,right 3,bot 40, lcd X*Y
p16.ini(4,4,220,300,10,10, 3,40, 3,40);
});
The UI allows you to start over with a SCRambled or ORDerly game, starting with time 0 [seconds] and 0 moves. The top shows time in seconds and number of moves since game started. The values are updated on every move and any other tap in the board area. Tapping the 'empty tile' tapping the empty spot or a tile not sharing x or y axis with the empty spot does nothing but update time.
Updating shows that a faster interface to the display controller is really a must... Some relieve is possible with using a faster - less nice - font, but may be nicer for small fonts on low resolution as shown in the 4th attached image. I'll sure look into both faster interfacing and faster font at one time.
I have not developed another input option yet to use when touch not available. But providing the x and y coordinates on a touch or click (with a mouse type cursor) gets you going. I was thinking of a 4-way five-button or joy-stick-with-click option. Moving around on the board could be done with a cursor - which uses save/restore curser image area (see )...speed - or more precisely - no-speed become though even more a drag for the user experience).
All parameters can be passed at initialization. Only font-size is safe and parameter calculated. The 'safety' of the other parameters are up to you. I had started to implement constraints, but with running out of memory already for the UI, I dropped it.
The board configuration has not to be square: everything from 3..5 x 3..5 goes.
More in a following post.
The attachment are:
Ready to run code (puzzle16_inlineRC.js - ReducedComments)
Clip with moves of tiles (P16moiving.mp4 - need to get a ...pod for my phone)
1st Image: showing 4x4 after startup
2nd Image: showing modified board - Puzzle25 - after tapping the +s to increase x and y each to 5 and SCR(amble)
3rd Image: showing a 4x4 in game
4th Image: a low resolution 3x5 in part of the display
Espruino is a JavaScript interpreter for low-power Microcontrollers. This site is both a support community for Espruino and a place to share what you are working on.
My initial project with Color TFT LCD w/ Touch Screen - PacMan game - did not move much forward, nor did my second one - DIY Marine GPS, see http://forum.espruino.com/conversations/127039 and http://forum.espruino.com/conversations/255759.
First I wanted to get more field testing going for my direct implementation for resistive touch screen (see http://forum.espruino.com/conversations/256122).
So I came up with this little nice game... I started out with 4*4-1 pieces, and had it - with bare UI - up to 9*9-1 - just displaying the tiles area and just having logic for creating the 'orderly' board and moving tiles. My UI enhancements then cut me short with Out of Memory to a max of 5x5-1... even after getting rid of some nice code commenting. I will bring some of the comments back in this post.
Two conclusions:
There are good news though, despite the OutOfMemory incident: The TOUCHR module got hardened:
n = n - 1
withn = number of bugs
(n closing in on 0).Moving tiles around sometimes still freezes the system... no clue why, even after noticing that sometimes my TOUCHR module was not listening anymore (after telling twice to listen... fixed the code in the listen() method. If you use my code from the other post, fix it like this to ignore a listen() command while already listening(). (The faulty code did a partial toggle and left touch detection in a weird state):
To get a better handle on discovering a freeze, I put a setBusyIndicator(LED3); into the code to see if it is my listening or something else. I noticed, that when the system is frozen and needs either a hard-reset or power-cycle, it is busy and never goes to sleep again. It could be that some unchecked run away in array or most likely the graphics is messing with me... (after me messing with boundaries... sooooorrrrrrry! - Tschumpuhung - find the translation yourself).
Back into the - Puzzle 16 - game: this is the code:
The UI allows you to start over with a SCRambled or ORDerly game, starting with time 0 [seconds] and 0 moves. The top shows time in seconds and number of moves since game started. The values are updated on every move and any other tap in the board area. Tapping the 'empty tile' tapping the empty spot or a tile not sharing x or y axis with the empty spot does nothing but update time.
Updating shows that a faster interface to the display controller is really a must... Some relieve is possible with using a faster - less nice - font, but may be nicer for small fonts on low resolution as shown in the 4th attached image. I'll sure look into both faster interfacing and faster font at one time.
The game is highly configurable... you can run it down to a Nokia display... looking forward to see some one picking that up. http://forum.espruino.com/conversations/257064
I have not developed another input option yet to use when touch not available. But providing the x and y coordinates on a touch or click (with a mouse type cursor) gets you going. I was thinking of a 4-way five-button or joy-stick-with-click option. Moving around on the board could be done with a cursor - which uses save/restore curser image area (see )...speed - or more precisely - no-speed become though even more a drag for the user experience).
All parameters can be passed at initialization. Only font-size is safe and parameter calculated. The 'safety' of the other parameters are up to you. I had started to implement constraints, but with running out of memory already for the UI, I dropped it.
The board configuration has not to be square: everything from 3..5 x 3..5 goes.
More in a following post.
The attachment are:
Enjoy!
6 Attachments