//************************************************************************ // You are free to copy this source and use it in any way you deem fit //************************************************************************ // For description of classes see 'SaveTheCats.java' //************************************************************************ // Program: Display class file for Damsels in Distress // Programmer: H K Gupta // Version: 1.0 dated 07/30/97 //************************************************************************ import java.awt.*; import java.lang.Math; import java.lang.Integer; public class SaveTheCatsDisplay extends Canvas { int[][] bGrid; int[][] sGrid; // Shadow grid for drawing SaveTheCatsHoles holes;// for fall through bricks SaveTheCatsItem[] items; // You, 5 Cats, 5 Dogs int noOfItems = 11; SaveTheCatsItem you; private int iconW = 12; private int iconH = 20; int gridW = 36; int gridH = 14; // the icon movement is four times finer than the grid int f = 4; // animation factor in a grid int fBy2 = f / 2; int itemBrick = 1; // grid items int itemLadder = 2; int itemEmpty = 0; int typeYou = 0; // moving items int typeDog = 1; int typeCat = 2; private int xOff = 0; private int yOff = 0; private boolean flagRefresh = true; // repaint the entire grid private boolean flagUpdate = false; // repaint only the cells indicated by shadow grid Image offImage; // for double buffering private Graphics o; private boolean bufferSet = false; // Icon File related variables private Image[] icons; private Graphics iconG; private boolean iconsCopied = false; private int noOfIcons = 19; public SaveTheCatsDisplay(Image iconImages) { super(); this.iconImages = iconImages; tracker = new MediaTracker(this); tracker.addImage(this.iconImages, 0); recorder = new SaveTheCatsRecorder(); bGrid = new int[gridW][gridH]; sGrid = new int[gridW][gridH]; holes = new SaveTheCatsHoles(this); } // ********************** Painting functions **************************** public void refresh() { flagRefresh = true; repaint(); } public void updateGrid() { flagUpdate = true; if(!flagRefresh) repaint(); } public void iconsCopy(int row) { icons = new Image[noOfIcons]; for(int i = 0; i < noOfIcons; i++) { icons[i] = createImage(iconW, iconH); iconG = icons[i].getGraphics(); iconG.drawImage(iconImages, - ((iconW + 2) * i), - row * (iconH + 2), this); } } public void update(Graphics g) { Dimension d = size(); if(!bufferSet) { offImage = createImage(d.width, d.height); o = offImage.getGraphics(); bufferSet = true; } if (!tracker.checkAll()) { return; // Wait for image to load } else { // copy icons if(!iconsCopied) { iconsCopy(0); iconsCopied = true; } } if(!flagUpdate) flagRefresh = true; if(flagRefresh) { xOff = (d.width - gridW * iconW) / 2; yOff = (d.height - gridH * iconH) / 2; o.setColor(Color.blue); o.fillRect(0, 0, d.width, d.height); for(int i = 0; i < gridW; i++) { for(int j = 0; j < gridH; j++) { drawGrid(i, j); } } flagRefresh = false; flagUpdate = false; } if(flagUpdate) { for(int i = 0; i < gridW; i++) { for(int j = 0; j < gridH; j++) { if(sGrid[i][j] != bGrid[i][j]) drawGrid(i, j); } } flagUpdate = false; } holes.drawItself(); for(int i = 0; i < noOfItems; i++) items[i].drawItself(); if((playType == playEditor) && flagMouseStart) { o.setColor(Color.black); int x1, y1, w, h; if(editX1 < editX2) { x1 = editX1; w = editX2 - editX1; } else { x1 = editX2; w = editX1 - editX2; } if(editY1 < editY2) { y1 = editY1; h = editY2 - editY1; } else { y1 = editY2; h = editY1 - editY2; } o.drawRect(x1, y1, w, h); } maskBuffer(o); g.drawImage(offImage, 0, 0, this); flagMouseStart = false; } void maskBuffer(Graphics o) { // Method to be overridden by classes desiring to use animation as backdrop // desiring to use Monstermania as // a backdrop } void drawOne(int type, int i, int j) { i *= (iconW / f); j *= (iconH / f); int t; switch (type) { case -1: t = 0; break; case 0: t = 3; break; case 1: t = 4; break; case 2: t = 2; break; case 3: t = 5; break; case 4: t = 6; break; default: t = type; } o.drawImage(icons[t], xOff + i - iconW / 2, yOff + j - iconH / 2, this); } void drawFallThroughBrick(int type, int i, int j) { if(playType == playEditor) { // blue color o.setColor(Color.blue); o.fillRect(xOff + i * iconW / f - iconW / 2, yOff + j * iconH / f - iconH / 2, iconW, iconH); } else { drawOne(type, i, j); } } private void drawGrid(int x, int y) { sGrid[x][y] = bGrid[x][y]; int dx1 = xOff + x * iconW; int dy1 = yOff + y * iconH; int type; switch(sGrid[x][y]) { case 0: type = 5; break; // open space case 1: type = 0; break; // brick case 2: type = 1; break; // ladder default: type = 5; } o.drawImage(icons[type], dx1, dy1, this); } public void paint(Graphics g) { update(g); } // ************************* Game logic ********************************** boolean isClash(SaveTheCatsItem item, int x, int y) { for(int i = 6; i < noOfItems; i++) { if(items[i].type == typeDog) { if(items[i].isAlive) { if((abs(items[i].posX - x) < f) && (abs(items[i].posY - y) < f)) { if(items[i] != item) return true; // we do not want clash } } } } return false; } private int abs(int i) { // Somehow javac is not finding the abs(int) if(i < 0) return -i; return i; } void eatCat(SaveTheCatsItem item, int x, int y) { if(item.eaten != -1) return; // Already eaten one cat if(item.eatenTime > 0) return; // Had lunch recently for(int i = 1; i < 6; i++) { if(items[i].isAlive) { if((abs(items[i].posX - x) < f) && (abs(items[i].posY - y) < f)) { items[i].isAlive = false; invalidate(items[i].posX, items[i].posY); if(item.type == typeYou) { // saved the kitty. Pat on the back nScore += 250; updateStats = true; } else { // dog is about to eat cat item.eaten = i; // remember the number item.eatenTime = 60; // drop it after 60 } } } } } void checkGameOver(int x, int y) { for(int i = 6; i < noOfItems; i++) // ensure dogs are not carrying anything if(items[i].eaten != -1) return; for(int i = 1; i < 6; i++) // All kitties are saved if(items[i].isAlive) return; gameRunning = false; } private int iWBy2 = iconW / 2; private int iHBy2 = iconH / 2; void invalidate(int x, int y) { x *= (iconW / f); y *= (iconH / f); sGrid[(x + iWBy2 - 1)/ iconW][(y + iHBy2 - 1) / iconH] = -1; sGrid[(x - iWBy2)/ iconW][(y - iHBy2) / iconH] = -1; sGrid[(x + iWBy2 - 1)/ iconW][(y + iHBy2 - 1) / iconH] = -1; sGrid[(x - iWBy2)/ iconW][(y - iHBy2) / iconH] = -1; } boolean onBrick(SaveTheCatsItem item) { int posX = item.posX; int posY = item.posY; if(bGrid[(posX - fBy2) / f][(posY - fBy2) / f] == itemBrick) return true; if(bGrid[(posX - fBy2) / f][(posY + fBy2 - 1) / f] == itemBrick) return true; if(bGrid[(posX + fBy2 - 1) / f][(posY - fBy2) / f] == itemBrick) return true; if(bGrid[(posX + fBy2 - 1) / f][(posY + fBy2 - 1) / f] == itemBrick) return true; return false; } void loadLevel(int lev) { String s = recorder.sLevels[lev - 1]; int ch = 0; int div = 1; for(int j = 0; j < gridH; j++) { for(int i = 0; i < gridW; i++) { if((i % 4) == 0) { ch = (int)(s.charAt(j * gridW / 4 + i / 4)) - 35; div = 27; } bGrid[i][j] = ch / div; sGrid[i][j] = -1; ch = ch - div * bGrid[i][j]; div = div / 3; } } int t, x, y; items = new SaveTheCatsItem[noOfItems]; for(int i = 0; i < noOfItems; i++) { if(i == 0) { t = typeYou;} else { if(i > 5) { t = typeDog; } else { t = typeCat;} } x = (int)(s.charAt(126 + 2 * i)) - 35; y = (int)(s.charAt(127 + 2 * i)) - 35; items[i] = new SaveTheCatsItem(this, t, x, y); if(x >= gridW) { // user did not create an item items[i].isAlive = false; items[i].posX = 0; items[i].posY = 0; } } you = items[0]; holes.newGame(); for(int i = 0; i < holes.maxHoles / 2; i++) { x = (int)(s.charAt(126 + 22 + 2 * i)) - 35; y = (int)(s.charAt(127 + 22 + 2 * i)) - 35; if(x < gridW) holes.addBrick(x, y); } } // ******* Game Status *************** int nCurrOption = 1; SaveTheCatsRecorder recorder; int nLevel = 1; int nMenLeft = 5; int nScore = 0; int playActual = 0; int playReplay = 1; int playDemo = 2; int playEditor = 3; int playType = playActual; boolean gameRunning = false; private Image iconImages; MediaTracker tracker; boolean updateStats = true; void startNewGame(boolean flagMyLevel) { gameRunning = false; if(nMenLeft <= 0) return; if((!flagMyLevel) && (playType == playActual)) { if(nLevel > recorder.getMaxLevels()) nLevel = 1; // recycle levels } updateStats = true; loadLevel(nLevel); refresh(); alterMove = false; if(playType == playReplay) { recorder.startReplay(); gameRunning = true; } if(playType == playDemo) { recorder.startDemo(); gameRunning = true; } recorder.initRecording(); } int maxTypesOfIcons = 4; int typeOfIcon = 0; void harakiri() { // commit sucide (non comprendo? learn Japanese) if((playType == playActual) && (nMenLeft > 0)) youDead(); } boolean newGame(int type, int n, boolean flagMyLevel) { playType = type; gameRunning = false; nMenLeft = 5; nScore = 0; nLevel = n; startNewGame(flagMyLevel); return true; } boolean regMotion(int type) { // User action request processing if(playType != playActual) return true; if(nMenLeft > 0) { if(!gameRunning) { gameRunning = true; // start animation recorder.initRecorder(nLevel); alterMove = false; } } if(you.mOne > 4) return true; you.mTwo = you.mOne; you.mOne = type; if(you.mTwo == 0) you.mTwo = you.mOne; return true; } private boolean alterMove = false; // 'You' move twice as fast as the monsters private int maxTypeOfMoves = 8; void move() { // start motion 'You' move every cycle others move alternate cycles if(playType == playActual) { // record move recorder.recordMove(you.mOne * maxTypeOfMoves + you.mTwo); } else { // demo or replay int move = recorder.returnMove(); if(move == -1) { gameRunning = false; return; } you.mOne = move / maxTypeOfMoves; you.mTwo = move % maxTypeOfMoves; } holes.updateHoles(); you.moveItem(); // your move if(alterMove) { for(int i = 6; i < noOfItems; i++) { // find the motion for dogs if(gameRunning) items[i].moveDogs(); } } alterMove = !alterMove; if(isClash(you, you.posX, you.posY)) { youDead(); } else { if(onBrick(you)) { youDead() ; } else { if(!gameRunning) { nMenLeft++; // bonus life nLevel++; playType = playActual; // Demo or replay is over startNewGame(false); return; } } } updateGrid(); } private void youDead() { // Got hit gameRunning = false; nMenLeft--; if(nMenLeft > 0) { playType = playActual; startNewGame(false); } else { updateStats = true; } } // ******************* Editor related functions ************************** void packUserLevel() { // Pack user level info in a string int ch = 0; StringBuffer s = new StringBuffer(200); for(int j = 0; j < gridH; j++) { for(int i = 0; i < gridW; i++) { if((i % 4) == 0) ch = 0; ch = ch * 3 + bGrid[i][j]; if((i % 4) == 3) s.append((char)(35 + ch)); } } for(int i = 0; i < noOfItems; i++) { if(!items[i].isAlive) { s.append((char)(35 + gridW)); // invalidValue s.append((char)(35 + gridW)); } else { s.append((char)(35 + items[i].posX / f)); s.append((char)(35 + items[i].posY / f)); } } for(int i = 0; i < holes.maxHoles / 2; i++) { if(holes.posX[i] == -1) { s.append((char)(35 + gridW)); // invalid value s.append((char)(35 + gridW)); } else { s.append((char)(35 + holes.posX[i])); s.append((char)(35 + holes.posY[i])); } } recorder.setMyLevel(s); } private int editX1, editY1, editX2, editY2; private boolean flagMouseStart = false; public boolean mouseDown(Event event, int x, int y) { if(playType != playEditor) return false; editX1 = x; editY1 = y; editX2 = x; editY2 = y; flagMouseStart = true; updateGrid(); return false; } public boolean mouseDrag(Event event, int x, int y) { if(playType != playEditor) return false; if(flagMouseStart) return true; invalidateRect(); editX2 = x; editY2 = y; flagMouseStart = true; updateGrid(); return false; } public boolean mouseUp(Event event, int x, int y) { if(playType != playEditor) return false; invalidateRect(); editX2 = x; editY2 = y; int x1 = (editX1 - xOff) / iconW; int y1 = (editY1 - yOff) / iconH; int x2 = (editX2 - xOff) / iconW; int y2 = (editY2 - yOff) / iconH; int tmp; if(x1 > x2) { tmp = x1; x1 = x2; x2 = tmp; } if(y1 > y2) { tmp = y1; y1 = y2; y2 = tmp; } // code for filling the current selection for(int i = 0; i < gridW; i++) { for(int j = 0; j < gridH; j++) { if ((i >= x1) && (i <= x2) && (j >= y1) && (j <= y2)) { switch (nCurrOption) { case 0: case 1: case 2: bGrid[i][j] = nCurrOption; holes.removeEditorBrick(i, j); break; case 3: holes.addEditorBrick(i, j); break; case 4: addRemoveItem(typeYou, i, j); break; case 5: addRemoveItem(typeDog, i, j); break; case 6: addRemoveItem(typeCat, i, j); break; default: } sGrid[i][j] = -1; } } } updateGrid(); return false; } private void invalidateRect() { int x1 = (editX1 - xOff) / iconW; int y1 = (editY1 - yOff) / iconH; int x2 = (editX2 - xOff) / iconW; int y2 = (editY2 - yOff) / iconH; for(int i = 0; i < gridW; i++) { for(int j = 0; j < gridH; j++) { if (((i == x1) || (i == x2)) || ((j == y1) || (j == y2))) { sGrid[i][j] = -1; } } } } private void addRemoveItem(int t, int x, int y) { for(int i = 0; i < noOfItems; i++) { if((items[i].isAlive) && (items[i].type == t) && (items[i].posX == (x * f + fBy2)) && (items[i].posY == (y * f + fBy2))) { items[i].isAlive = false; // Items are to be toggled return; } } for(int i = 0; i < noOfItems; i++) { if((items[i].isAlive) && (items[i].posX == (x * f + fBy2)) && (items[i].posY == (y * f + fBy2))) { // Slot already occupied by somebody else return; } } for(int i = 0; i < noOfItems; i++) { if((!items[i].isAlive) && (items[i].type == t)) { items[i].isAlive = true; // Items are to be toggled items[i].posX = x * f + fBy2; items[i].posY = y * f + fBy2; return; } } } } class SaveTheCatsItem { // Store info about a single action item private SaveTheCatsDisplay p; private int f, fBy2; // Copy from the parent private int mW, mH; boolean isAlive = true; private int deadCount; int posX, posY; private int wakeUpX, wakeUpY; // dogs will reappear after death at the original location private int iconMove = -1; // Did it move in last cycle int type; // typeYou, typeDog, typeCat etc. int eaten = -1; // if it has eaten a cat, the cat number comes here int eatenTime = 0; // counter to release the cat int mOne = 0; // preferred motion int mTwo = 0; // secondary motion public SaveTheCatsItem(SaveTheCatsDisplay p, int type, int x, int y) { this.p = p; f = p.f; fBy2 = p.fBy2; mW = p.gridW * f; mH = p.gridH * f; this.type = type; posX = x * f + fBy2; // grid co-ordinate to motion co-ordinates posY = y * f + fBy2; wakeUpX = posX; // save the start up co-ordinates wakeUpY = posY; } public void moveDogs() { // The dogs have their motion dependant on 'YOU' // as they try to follow 'You' dumbly if(type != p.typeDog) return; // Woof I am not a dog if(!isAlive) { // a dead dog. try to revive it. deadCount--; if(deadCount > 0) return; // let it remain dead if(p.isClash(this, wakeUpX, wakeUpY)) { deadCount = 1; // wait for the other dog to leave the spot return; } isAlive = true; // A miracle. The dead has risen mOne = 0; mTwo = 0; posX = wakeUpX; posY = wakeUpY; } else { if(eatenTime > 0) { eatenTime--; if((eaten != -1) && (eatenTime <= 0)) { // release the kitty p.items[eaten].isAlive = true; p.items[eaten].posX = posX; p.items[eaten].posY = posY; eaten = -1; eatenTime = 12; // will not eat anything for some time } } } // Preferred motion is UP or DOWN if(p.you.posY < posY) { // up mOne = 3; } else { if(p.you.posY == posY) { mOne = 0;} else { mOne = 4;} } // Secondary motion is LEFT or RIGHT if(p.you.posX < posX) {// Left mTwo = 1; } else { if(p.you.posX == posX) { mTwo = 0; } else { mTwo = 2;} } moveItem(); } public void moveItem() { int x = posX; int y = posY; // before you move, check to see if there is a brick reappearing if(p.onBrick(this)) { if(type == p.typeDog) { p.invalidate(x, y); isAlive = false; deadCount = 100; } return; } int mType = mOne; iconMove = -1; if(!isFallPossible(x, y)) { if(!isMovePossible(x, y, mType)) { // try the next move mType = mTwo; if(!isMovePossible(x, y, mType)) { mType = 0; mOne = 0; mTwo = 0; } } switch (mType) { case 1: x-- ; break; case 2: x++ ; break; case 3: y-- ; break; case 4: y++ ; break; case 5: p.holes.addHole(x / f - 1, y / f + 1); mType = 0; mOne = 0; mTwo = 0; break; case 6: p.holes.addHole(x / f + 1, y / f + 1); mType = 0; mOne = 0; mTwo = 0; break; default: } } else { // Let the item fall y++; mType = 4; } if(p.isClash(this, x, y)) { if(type != p.typeYou) return; } p.eatCat(this, x, y); p.invalidate(posX, posY); switch (mType) { // Show some animation atleast case 1: iconMove = 0 ; break; // left case 2: iconMove = 4 ; break; // right case 3: iconMove = 2 ; break; // up case 4: iconMove = 2 ; break; // down default: iconMove = -1; // lazy bum did not move } posX = x; posY = y; if(type == p.typeYou) { // If stop requested. stop at center of grid if(mOne == 0) { // even for digging if(((x % f) == fBy2) && ((y % f) == fBy2)) { mTwo = mOne; } } } } private boolean isMovePossible(int x, int y, int mType) { switch(mType) { case 0: return false; case 1: ; // Left if((y % f) != fBy2) return false; if(x <= fBy2) return false; if((x % f) == fBy2) if(p.bGrid[(x - fBy2 - 1)/ f][y / f] == p.itemBrick) return false; x--; break; case 2: ; // Right if((y % f) != fBy2) return false; if(x >= (mW - fBy2)) return false; if((x % f) == fBy2) if(p.bGrid[(x + fBy2)/ f][y / f] == p.itemBrick) return false; x++; break; case 3: ; // Up if((x % f) != fBy2) return false; if(y <= fBy2) { // Check if level is completed if(p.bGrid[x / f][y / f] == p.itemLadder) p.checkGameOver(x, y); return false; } if((p.bGrid[x / f][(y + fBy2 - 1) / f] != p.itemLadder) &&(p.bGrid[x / f][(y - fBy2) / f] != p.itemLadder)) return false; if((y % f) == fBy2) if(p.bGrid[x / f][(y - fBy2 - 1)/ f] == p.itemBrick) return false; y--; break; case 4: ; // down if((x % f) != fBy2) return false; if(y >= (mH - fBy2)) return false; if((y % f) == fBy2) if(p.bGrid[x / f][(y + fBy2)/ f] == p.itemBrick) return false; y++; break; case 5: ; // Dig Left if(((x % f) != fBy2) || ((y % f) != fBy2)) return false; if((x < f) || (y > (mH - f))) return false; if(p.bGrid[x / f - 1][y / f] == p.itemBrick) return false; if(p.bGrid[x / f - 1][y / f + 1] != p.itemBrick) return false; break; case 6: ;// dig Right if(((x % f) != fBy2) || ((y % f) != fBy2)) return false; if((x > (mW - f)) || (y > (mH - f))) return false; if(p.bGrid[x / f + 1][y / f] == p.itemBrick) return false; if(p.bGrid[x / f + 1][y / f + 1] != p.itemBrick) return false; break; default: return false; } if(p.isClash(this, x, y)) return false; return true; } private boolean isFallPossible(int x, int y) { if((x % f) == fBy2) { if(p.bGrid[x / f][(y + fBy2 - 1)/ f] == p.itemEmpty) { if(y < (mH - fBy2)) { if(p.bGrid[x / f][(y + fBy2)/ f] == p.itemEmpty) { return true; } } } } return false; } void drawItself() { int t; if(isAlive) { t = type; if(iconMove != -1) { // If icon has moved in last cycle if(t == p.typeYou) t = 7 + iconMove; if(t == p.typeDog) t = 13 + iconMove; switch(iconMove) { case 0: case 4: if((posX % 2) == 0) t++; break; case 2: if((posY % 2) == 0) t++; break; default: } } p.drawOne(t, posX, posY); } } } class SaveTheCatsHoles { // process fall through bricks and user created holes private SaveTheCatsDisplay p; int maxHoles = 50; int[] posX; int[] posY; private int[] count; // a counter for filling the holes private int[] type; // 0 hole, 1 fall through brick public SaveTheCatsHoles(SaveTheCatsDisplay p) { this.p = p; posX = new int[maxHoles]; posY = new int[maxHoles]; count = new int[maxHoles]; type = new int[maxHoles]; newGame(); } void updateHoles() { // update the state of holes for(int i = 0; i < maxHoles; i++) { if(posX[i] != -1) { if(type[i] == 0) { count[i]--; if(count[i] <= 0) { p.bGrid[posX[i]][posY[i]] = p.itemBrick; p.sGrid[posX[i]][posY[i]] = -1; posX[i] = -1; } } } } } void newGame() { for(int i = 0; i < maxHoles; i++) { posX[i] = -1; } } void addHole(int x, int y) { addItem(0, x, y); } void addBrick(int x, int y) { addItem(1, x, y); } private void addItem(int t, int x, int y) { if((t == 0) && (p.bGrid[x][y] != p.itemBrick)) return; for(int i = 0; i < maxHoles; i++) { if(posX[i] == -1) { posX[i] = x; posY[i] = y; type[i] = t; count[i] = 60; p.bGrid[posX[i]][posY[i]] = p.itemEmpty; p.sGrid[posX[i]][posY[i]] = -1; return; } } } void addEditorBrick(int x, int y) { // for editor only for(int i = 0; i < (maxHoles / 2); i++) { if(posX[i] == -1) { posX[i] = x; posY[i] = y; type[i] = 1; p.bGrid[posX[i]][posY[i]] = p.itemEmpty; return; } } } void removeEditorBrick(int x, int y) { // for editor only for(int i = 0; i < maxHoles; i++) { if((type[i] == 1) && (posX[i] == x) && (posY[i] == y)) { posX[i] = -1; return; } } } void drawItself() { int t; for(int i = 0; i < maxHoles; i++) { if(posX[i] != -1) { if(type[i] == 0) { t = 3; if(count[i] < 20) t = 4; } else { t = -1; } p.drawFallThroughBrick(t, posX[i] * p.f + p.fBy2, posY[i] * p.f + p.fBy2); } } } }