//============================================================================= // AI reference book: "玩 Lode Runner 學 C 語言" // http://www.kingstone.com.tw/book/book_page.asp?kmcode=2014710650538 //============================================================================= var STATE_OK_TO_MOVE = 1, STATE_FALLING = 2; function moveRunner() { var x = runner.pos.x; var xOffset = runner.pos.xOffset; var y = runner.pos.y; var yOffset = runner.pos.yOffset; var curState; var curToken, nextToken; curToken = map[x][y].base; if ( curToken == LADDR_T || (curToken == BAR_T && yOffset == 0) ) { //ladder & bar curState = STATE_OK_TO_MOVE; //ok to move (on ladder or bar) } else if (yOffset < 0) { //no ladder && yOffset < 0 ==> falling curState = STATE_FALLING; } else if (y < maxTileY) { //no laddr && y < maxTileY && yOffset >= 0 nextToken = map[x][y+1].act; switch (true) { case (nextToken == EMPTY_T): curState = STATE_FALLING; break; case ( nextToken == BLOCK_T || nextToken == LADDR_T || nextToken == SOLID_T): curState = STATE_OK_TO_MOVE; break; case ( nextToken == GUARD_T): curState = STATE_OK_TO_MOVE; break; default: curState = STATE_FALLING; break; } } else { // no laddr && y == maxTileY curState = STATE_OK_TO_MOVE; } if ( curState == STATE_FALLING ) { stayCurrPos = ( y >= maxTileY || (nextToken = map[x][y+1].act) == BLOCK_T || nextToken == SOLID_T || nextToken == GUARD_T); runnerMoveStep(ACT_FALL, stayCurrPos); return; } /****** Check Key Action ******/ var moveStep = ACT_STOP; var stayCurrPos = 1; switch(keyAction) { case ACT_UP: stayCurrPos = ( y <= 0 || ( nextToken = map[x][y-1].act) == BLOCK_T || nextToken == SOLID_T || nextToken == TRAP_T ); if (y > 0 && map[x][y].base != LADDR_T && yOffset < H4 && yOffset > 0 && map[x][y+1].base == LADDR_T) { stayCurrPos = 1; moveStep = ACT_UP; } else if (!( map[x][y].base != LADDR_T && (yOffset <= 0 || map[x][y+1].base != LADDR_T) || (yOffset <= 0 && stayCurrPos ) ) ) { moveStep = ACT_UP; } break; case ACT_DOWN: stayCurrPos = ( y >= maxTileY || (nextToken = map[x][y+1].act) == BLOCK_T || nextToken == SOLID_T); if (!(yOffset >= 0 && stayCurrPos)) moveStep = ACT_DOWN; break; case ACT_LEFT: stayCurrPos = ( x <= 0 || (nextToken = map[x-1][y].act) == BLOCK_T || nextToken == SOLID_T || nextToken == TRAP_T); if (!(xOffset <= 0 && stayCurrPos)) moveStep = ACT_LEFT; break; case ACT_RIGHT: stayCurrPos = ( x >= maxTileX || (nextToken = map[x+1][y].act) == BLOCK_T || nextToken == SOLID_T || nextToken == TRAP_T); if (!(xOffset >= 0 && stayCurrPos)) moveStep = ACT_RIGHT; break; case ACT_DIG_LEFT: case ACT_DIG_RIGHT: if(ok2Dig(keyAction)) { runnerMoveStep(keyAction, stayCurrPos); digHole(keyAction); } else { runnerMoveStep(ACT_STOP, stayCurrPos); } keyAction = ACT_STOP; return; } runnerMoveStep(moveStep, stayCurrPos); } function runnerMoveStep(action, stayCurrPos ) { var x = runner.pos.x; var xOffset = runner.pos.xOffset; var y = runner.pos.y; var yOffset = runner.pos.yOffset; var curToken, nextToken, centerX, centerY; var curShape, newShape; curShape = newShape = runner.shape; centerX = centerY = ACT_STOP; switch(action) { case ACT_DIG_LEFT: case ACT_DIG_RIGHT: xOffset = 0; yOffset = 0; break; case ACT_UP: case ACT_DOWN: case ACT_FALL: if ( xOffset > 0 ) centerX = ACT_LEFT; else if (xOffset < 0) centerX = ACT_RIGHT; break; case ACT_LEFT: case ACT_RIGHT: if( yOffset > 0 ) centerY = ACT_UP; else if (yOffset < 0) centerY = ACT_DOWN; break; } curToken = map[x][y].base; if ( action == ACT_UP ) { yOffset -= yMove; if(stayCurrPos && yOffset < 0) yOffset = 0; //stay on current position else if(yOffset < -H2) { //move to y-1 position if ( curToken == BLOCK_T || curToken == HLADR_T ) curToken = EMPTY_T; //in hole or hide laddr map[x][y].act = curToken; //runner move to [x][y-1], so set [x][y].act to previous state y--; yOffset = tileH + yOffset; if(map[x][y].act == GUARD_T && guardAlive(x,y)) setRunnerDead(); //collision } newShape = "runUpDn"; } if ( centerY == ACT_UP ) { yOffset -= yMove; if( yOffset < 0) yOffset = 0; //move to center Y } if ( action == ACT_DOWN || action == ACT_FALL) { var holdOnBar = 0; if(curToken == BAR_T) { if( yOffset < 0) holdOnBar = 1; else { //when runner with bar and press down will into falling state // except "laddr" or "guard" at below, 11/25/2016 if(action == ACT_DOWN && y < maxTileY && map[x][y+1].act != LADDR_T && map[x][y+1].act != GUARD_T) { action = ACT_FALL; } } } yOffset += yMove; if(holdOnBar == 1 && yOffset >= 0) { yOffset = 0; //fall and hold on bar action = ACT_FALL_BAR; } if(stayCurrPos && yOffset > 0) yOffset = 0; //stay on current position else if(yOffset > H2) { //move to y+1 position if(curToken == BLOCK_T || curToken == HLADR_T) curToken = EMPTY_T; //in hole or hide laddr map[x][y].act = curToken; //runner move to [x][y+1], so set [x][y].act to previous state y++; yOffset = yOffset - tileH; if(map[x][y].act == GUARD_T && guardAlive(x,y)) setRunnerDead(); //collision } if(action == ACT_DOWN) { newShape = "runUpDn"; } else { //ACT_FALL or ACT_FALL_BAR if (y < maxTileY && map[x][y+1].act == GUARD_T) { //over guard //don't collision var id = getGuardId(x, y+1); if(yOffset > guard[id].pos.yOffset) yOffset = guard[id].pos.yOffset; } if (action == ACT_FALL_BAR) { if(runner.lastLeftRight == ACT_LEFT) newShape = "barLeft"; else newShape = "barRight"; } else { if(runner.lastLeftRight == ACT_LEFT) newShape = "fallLeft"; else newShape = "fallRight"; } } } if ( centerY == ACT_DOWN ) { yOffset += yMove; if ( yOffset > 0 ) yOffset = 0; //move to center Y } if ( action == ACT_LEFT) { xOffset -= xMove; if(stayCurrPos && xOffset < 0) xOffset = 0; //stay on current position else if ( xOffset < -W2) { //move to x-1 position if(curToken == BLOCK_T || curToken == HLADR_T) curToken = EMPTY_T; //in hole or hide laddr map[x][y].act = curToken; //runner move to [x-1][y], so set [x][y].act to previous state x--; xOffset = tileW + xOffset; if(map[x][y].act == GUARD_T && guardAlive(x,y)) setRunnerDead(); //collision } if(curToken == BAR_T) newShape = "barLeft"; else newShape = "runLeft"; } if ( centerX == ACT_LEFT ) { xOffset -= xMove; if ( xOffset < 0) xOffset = 0; //move to center X } if ( action == ACT_RIGHT ) { xOffset += xMove; if(stayCurrPos && xOffset > 0) xOffset = 0; //stay on current position else if ( xOffset > W2) { //move to x+1 position if(curToken == BLOCK_T || curToken == HLADR_T) curToken = EMPTY_T; //in hole or hide laddr map[x][y].act = curToken; //runner move to [x+1][y], so set [x][y].act to previous state x++; xOffset = xOffset - tileW; if(map[x][y].act == GUARD_T && guardAlive(x,y)) setRunnerDead(); //collision } if(curToken == BAR_T) newShape = "barRight"; else newShape = "runRight"; } if ( centerX == ACT_RIGHT ) { xOffset += xMove; if ( xOffset > 0) xOffset = 0; //move to center X } if(action == ACT_STOP ) { if(runner.action == ACT_FALL) { soundStop(soundFall); themeSoundPlay("down"); } if(runner.action != ACT_STOP){ runner.sprite.stop(); runner.action = ACT_STOP; } } else { runner.sprite.x = (x * tileW + xOffset) * tileScale | 0; runner.sprite.y = (y * tileH + yOffset) * tileScale | 0; runner.pos = { x:x, y:y, xOffset:xOffset, yOffset:yOffset}; if(curShape != newShape) { runner.sprite.gotoAndPlay(newShape); runner.shape = newShape; } if(action != runner.action){ if(runner.action == ACT_FALL) { soundStop(soundFall); themeSoundPlay("down"); } else if ( action == ACT_FALL) { soundPlay(soundFall); } runner.sprite.play(); } if(action == ACT_LEFT || action == ACT_RIGHT) runner.lastLeftRight = action; runner.action = action; } map[x][y].act = RUNNER_T; //show trap tile if runner fall into the tile, 9/12/2015 if(map[x][y].base == TRAP_T) map[x][y].bitmap.set({alpha:0.5}); //show trap tile // Check runner to get gold (MAX MOVE MUST < H4 & W4) if( map[x][y].base == GOLD_T && ((!xOffset && yOffset >= 0 && yOffset < H4) || (!yOffset && xOffset >= 0 && xOffset < W4) || (y < maxTileY && map[x][y+1].base == LADDR_T && yOffset < H4) // gold above laddr ) ) { removeGold(x,y); themeSoundPlay("getGold"); decGold(); //debug("gold = " + goldCount); if(playMode == PLAY_CLASSIC || playMode == PLAY_AUTO || playMode == PLAY_DEMO) { drawScore(SCORE_GET_GOLD); } else { //for modern mode , edit mode drawGold(1); //get gold } } //if(!goldCount && !goldComplete) showHideLaddr(); //check collision with guard ! checkCollision(x, y); } //dec gold count function decGold() { if(--goldCount <= 0) { showHideLaddr(); if(runner.pos.y > 0) { if(curTheme == "C64") soundPlay("goldFinish" + ((curLevel-1)%6+1)); //six sounds else soundPlay("goldFinish"); //for all apple2 mode, 9/12/2015 } } } function removeGold(x,y) { map[x][y].base = EMPTY_T; mainStage.removeChild(map[x][y].bitmap); map[x][y].bitmap = null; } function addGold(x, y) { var tile; map[x][y].base = GOLD_T; tile = map[x][y].bitmap = getThemeBitmap("gold"); tile.setTransform(x * tileWScale, y * tileHScale,tileScale, tileScale); //x,y, scaleX, scaleY mainStage.addChild(tile); moveSprite2Top(); //reset runner, guard & fill hole object order } function showHideLaddr() { var haveHLadder = 0; for(var y = 0; y < NO_OF_TILES_Y; y++) { for(var x = 0; x < NO_OF_TILES_X; x++) { if( map[x][y].base == HLADR_T) { haveHLadder = 1; map[x][y].base =LADDR_T; map[x][y].act =LADDR_T; map[x][y].bitmap.set({alpha:1}); //display laddr } } } goldComplete = 1; return haveHLadder; } function checkCollision(runnerX, runnerY) { var x = -1, y = -1; //var dbg = "NO"; switch(true) { case ( runnerY > 0 && map[runnerX][runnerY-1].act == GUARD_T): x = runnerX; y = runnerY-1; //dbg = "UP"; break; case ( runnerY < maxTileY && map[runnerX][runnerY+1].act == GUARD_T): x = runnerX; y = runnerY+1; //dbg = "DN"; break; case ( runnerX > 0 && map[runnerX-1][runnerY].act == GUARD_T): x = runnerX-1; y = runnerY; //dbg = "LF"; break; case ( runnerX < maxTileX && map[runnerX+1][runnerY].act== GUARD_T): x = runnerX+1; y = runnerY; //dbg = "RT"; break; } //if( dbg != "NO") debug(dbg); if( x >= 0) { for(var i = 0; i < guardCount; i++) { if( guard[i].pos.x == x && guard[i].pos.y == y) break; } assert( (i < guardCount), "checkCollision design error !"); if(guard[i].action != ACT_REBORN) { //only guard alive need check collection //var dw = Math.abs(runner.sprite.x - guard[i].sprite.x); //var dh = Math.abs(runner.sprite.y - guard[i].sprite.y); //change detect method ==> don't depend on scale var runnerPosX = runner.pos.x*tileW+runner.pos.xOffset; var runnerPosY = runner.pos.y*tileH+runner.pos.yOffset; var guardPosX = guard[i].pos.x*tileW+guard[i].pos.xOffset; var guardPosY = guard[i].pos.y*tileH+guard[i].pos.yOffset; var dw = Math.abs(runnerPosX - guardPosX); var dh = Math.abs(runnerPosY - guardPosY); if( dw <= W4*3 && dh <= H4*3 ) { setRunnerDead(); //07/04/2014 //debug("runner dead!"); } } } } //Page 276 misc.c (book) function ok2Dig(nextMove) { var x = runner.pos.x; var y = runner.pos.y; var token, rc = 0; switch(nextMove) { case ACT_DIG_LEFT: // debug("[x-1][y+1] = " + map[x-1][y+1].act + " [x-1][y] = " + map[x-1][y].act + // "[x-1][y].base = " + map[x-1][y].base ); if( y < maxTileY && x > 0 && map[x-1][y+1].act == BLOCK_T && map[x-1][y].act == EMPTY_T && map[x-1][y].base != GOLD_T) rc = 1; break; case ACT_DIG_RIGHT: // debug("[x+1][y+1] = " + map[x+1][y+1].act + " [x+1][y] = " + map[x+1][y].act + // "[x+1][y].base = " + map[x+1][y].base ); if( y < maxTileY && x < maxTileX && map[x+1][y+1].act == BLOCK_T && map[x+1][y].act == EMPTY_T && map[x+1][y].base != GOLD_T) rc = 1; break; } return rc; } //======================= // BEGIN NEW DIG METHOD //======================= var digHoleLeft = [ 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7 ]; var digHoleRight = [ 8, 9, 10, 10, 11, 12, 12, 13, 14, 14, 15 ]; function processDigHole() { if(curAiVersion < 3) return; if(++holeObj.curFrameIdx < holeObj.shapeFrame.length) { // change frame holeObj.sprite.gotoAndStop(holeObj.shapeFrame[holeObj.curFrameIdx]); holeObj.sprite.currentAnimationFrame = holeObj.curFrameIdx; } else { //dig complete digComplete(); } } //======================== // END NEW DIG METHOD //======================== var digTimeStart, shakeTimeStart; //for debug var fillHoleTimeStart, rebornTimeStart; //for debug function digHole(action) { var x,y, holeShape; if(action == ACT_DIG_LEFT) { x = runner.pos.x-1; y = runner.pos.y; runner.shape = "digLeft"; holeShape = "digHoleLeft"; } else { //DIG RIGHT x = runner.pos.x+1; y = runner.pos.y; runner.shape = "digRight"; holeShape = "digHoleRight"; } soundPlay(soundDig); map[x][y+1].bitmap.set({alpha:0}); //hide block (replace with digging image) runner.sprite.gotoAndPlay(runner.shape); holeObj.action = ACT_DIGGING; holeObj.pos = { x: x, y: y }; holeObj.sprite.setTransform(x * tileWScale, y * tileHScale,tileScale, tileScale); digTimeStart = recordCount; //for debug if(curAiVersion < 3) { holeObj.sprite.gotoAndPlay(holeShape); holeObj.sprite.on("animationend", digComplete); } else { if(action == ACT_DIG_LEFT) holeShape = digHoleLeft; else holeShape = digHoleRight; holeObj.sprite.gotoAndStop(holeShape[0]); holeObj.shapeFrame = holeShape; holeObj.curFrameIdx = 0; } mainStage.addChild(holeObj.sprite); } var DEBUG_DIG=0; function isDigging() { var rc = 0; if(holeObj.action == ACT_DIGGING) { var x = holeObj.pos.x, y = holeObj.pos.y; if(map[x][y].act == GUARD_T) { //guard come close to the digging hole ! var id = getGuardId(x, y); if(holeObj.sprite.currentAnimationFrame < holeObj.digLimit && guard[id].pos.yOffset > -H4) { if(DEBUG_DIG) loadingTxt.text = "dig : " + holeObj.sprite.currentAnimationFrame + " (X)"; stopDigging(x,y); } else { if(DEBUG_DIG) loadingTxt.text = "dig : " + holeObj.sprite.currentAnimationFrame + " (O)"; if(curAiVersion >= 3) { //This is a bug while AI VERSION < 3 map[x][y+1].act = EMPTY_T; //assume hole complete rc = 1; } } } else { switch( runner.shape ) { case "digLeft": if(holeObj.sprite.currentAnimationFrame > 2 ) { runner.sprite.gotoAndStop("runLeft"); //change shape runner.shape = "runLeft"; runner.action = ACT_STOP; } break; case "digRight": if(holeObj.sprite.currentAnimationFrame > 2) { runner.sprite.gotoAndStop("runRight"); //change shape runner.shape = "runRight"; runner.action = ACT_STOP; } break; } rc = 1; } } return rc; } function stopDigging(x,y) { //(1) remove holeObj holeObj.sprite.removeAllEventListeners ("animationend"); holeObj.action = ACT_STOP; //no digging mainStage.removeChild(holeObj.sprite); //(2) fill hole y++; map[x][y].act = map[x][y].base; //BLOCK_T assert(map[x][y].base == BLOCK_T, "fill hole != BLOCK_T"); map[x][y].bitmap.set({alpha:1}); //display block //(3) change runner shape switch( runner.shape ) { case "digLeft": runner.sprite.gotoAndStop("runLeft"); runner.shape = "runLeft"; runner.action = ACT_STOP; break; case "digRight": runner.sprite.gotoAndStop("runRight"); runner.shape = "runRight"; runner.action = ACT_STOP; break; } soundStop(soundDig); //stop sound of digging } function digComplete() { var x = holeObj.pos.x; var y = holeObj.pos.y + 1; map[x][y].act = EMPTY_T; holeObj.sprite.removeAllEventListeners ("animationend"); holeObj.action = ACT_STOP; //no digging mainStage.removeChild(holeObj.sprite); if(DEBUG_TIME) loadingTxt.text = "DigTime = " + (recordCount - digTimeStart); fillHole(x, y); } var fillHoleObj = []; function fillHole(x, y) { var fillSprite = new createjs.Sprite(holeData, "fillHole"); fillSprite.pos = { x:x, y:y }; //save position 11/18/2014 fillSprite.setTransform(x * tileWScale, y * tileHScale, tileScale, tileScale); if(curAiVersion < 3) { fillSprite.on("animationend", fillComplete, null, false, {obj:fillSprite} ); fillSprite.play(); } else { fillSprite.curFrameIdx = 0; fillSprite.curFrameTime = -1; fillSprite.gotoAndStop(fillHoleFrame[0]); } mainStage.addChild(fillSprite); fillHoleObj.push(fillSprite); fillHoleTimeStart = recordCount; //for debug } function moveFillHoleObj2Top() { for(var i = 0; i < fillHoleObj.length; i++) { moveChild2Top(mainStage, fillHoleObj[i]); } } function fillComplete(evt, data) { //don't use "divide command", it will cause loss of accuracy while scale changed (ex: tileScale = 0.6...) //var x = this.x / tileWScale | 0; //this : scope default to the dispatcher //var y = this.y / tileHScale | 0; var fillObj = data.obj; var x = fillObj.pos.x, y = fillObj.pos.y; //get position map[x][y].bitmap.set({alpha:1}); //display block fillObj.removeAllEventListeners ("animationend"); mainStage.removeChild(fillObj); removeFillHoleObj(fillObj); switch(map[x][y].act) { case RUNNER_T : // runner dead //loadingTxt.text = "RUNNER DEAD"; gameState = GAME_RUNNER_DEAD; runner.sprite.set({alpha:0}); //hidden runner --> dead break; case GUARD_T: //guard dead var id = getGuardId(x,y); if(curAiVersion >= 3 && guard[id].action == ACT_IN_HOLE) removeFromShake(id); if(guard[id].hasGold > 0) { //guard has gold and not fall into the hole decGold(); guard[id].hasGold = 0; guardRemoveRedhat(guard[id]); //9/4/2016 } guardReborn(x,y); if(playMode == PLAY_CLASSIC || playMode == PLAY_AUTO || playMode == PLAY_DEMO) { drawScore(SCORE_GUARD_DEAD); } else { //for modern mode & edit mode drawGuard(1); //guard dead, add count } break; } map[x][y].act = BLOCK_T; if(DEBUG_TIME) loadingTxt.text = "FillHoleTime = " + (recordCount - fillHoleTimeStart); //for debug } function removeFillHoleObj(spriteObj) { for(var i = 0; i < fillHoleObj.length; i++) { if(fillHoleObj[i] == spriteObj) { fillHoleObj.splice(i,1); return; } } error("design error !"); } //======================================= // BEGIN NEW fill Hold (Ai version >= 3) //======================================= var fillHoleFrame = [ 16, 17, 18, 19]; var fillHoleTime = [ 166, 8, 8, 4]; function initFillHoleVariable() { fillHoleObj = []; } function processFillHole() { var curIdx, curFillObj; for(var i = 0; i < fillHoleObj.length;) { curFillObj = fillHoleObj[i]; curIdx = curFillObj.curFrameIdx; if(++curFillObj.curFrameTime >= fillHoleTime[curIdx]) { if(++curFillObj.curFrameIdx < fillHoleFrame.length) { //change frame curFillObj.curFrameTime = 0; curFillObj.gotoAndStop(fillHoleFrame[curFillObj.curFrameIdx]); } else { //fill hole complete fillComplete(null, {obj: curFillObj}); continue; } } i++; } } //==================== // END NEW fill Hold //==================== //================================== // Check guard is alive or not // 2014/10/31 //================================== function guardAlive(x, y) { for(var i = 0; i < guardCount; i++) { if( guard[i].pos.x == x && guard[i].pos.y == y) break; } assert( (i < guardCount), "guardAlive() design error !"); if(guard[i].action != ACT_REBORN) return 1; //alive return 0; //reborn }