/* * Copyright © 2012 Nokia Corporation. All rights reserved. * Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation. * Oracle and Java are trademarks or registered trademarks of Oracle and/or its * affiliates. Other product and company names mentioned herein may be trademarks * or trade names of their respective owners. * See LICENSE.TXT for license information. */ package com.nokia.example.sudokumaster; import java.util.Timer; import java.util.TimerTask; import javax.microedition.lcdui.Font; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.game.GameCanvas; import javax.microedition.rms.RecordStore; import javax.microedition.rms.RecordStoreException; public class SudokuCanvas extends GameCanvas { private static final int LEFT_SOFTKEY = -6; private static final int RIGHT_SOFTKEY = -7; private Main main; private Timer timer; private Layout layout; private ImageView title; private Button exit; private Button back; private Button options; private ImageView backX; private SudokuView sudoku; private StatusView empty; private StatusView moves; private StatusView elapsed; private NumberSelector numberSelector; private VictoryDialog victoryDialog; private OptionsDialog optionsDialog; public SudokuCanvas(final Main main) { super(false); setFullScreenMode(true); this.main = main; } protected void showNotify() { if (sudoku == null) { generateLayout(); } loadGameState(); updateEmptyAndMoves(); startTimer(); } protected void hideNotify() { stopTimer(); if (!main.isClosed()) { saveGameState(); } } protected void sizeChanged(int w, int h) { if (timer != null) { stopTimer(); updateLayout(w, h); startTimer(); } } private void startTimer() { final Graphics g = getGraphics(); if (isSmallScreen(getWidth(), getHeight())) { g.setFont(Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL)); } timer = new Timer(); timer.schedule(new TimerTask() { public void run() { render(g); } }, 0, 20); timer.scheduleAtFixedRate(new TimerTask() { public void run() { updateElapsed(); } }, 0, 1000); } private void stopTimer() { timer.cancel(); timer = null; } public void saveGameState() { if (sudoku == null) { return; } try { RecordStore gameState = RecordStore.openRecordStore("GameState", true); if (gameState.getNumRecords() == 0) { gameState.addRecord(null, 0, 0); } byte[] data = sudoku.getState(); gameState.setRecord(1, data, 0, data.length); } catch (RecordStoreException e) { } } public void loadGameState() { if (sudoku == null) { return; } try { RecordStore gameState = RecordStore.openRecordStore("GameState", true); if (gameState.getNumRecords() == 0) { sudoku.newGame(SudokuGenerator.newPuzzle()); } else { sudoku.setState(gameState.getRecord(1)); if (sudoku.isComplete()) { showVictoryDialog(); } } } catch (RecordStoreException e) { } } private void render(Graphics g) { if (layout.needsRendering()) { layout.render(g); flushGraphics(); } layout.update(); } private void updateElapsed() { long s = sudoku.getElapsedSeconds(); long m = s / 60; s = s % 60; StringBuffer text = new StringBuffer(5); if (m < 10) { text.append('0'); } text.append(m); text.append(':'); if (s < 10) { text.append('0'); } text.append(s); elapsed.setText(text.toString()); } private void updateEmptyAndMoves() { empty.setText(String.valueOf(sudoku.getEmpty())); moves.setText(String.valueOf(sudoku.getMoves())); } protected void pointerPressed(int x, int y) { handlePointerEvent(View.POINTER_PRESSED, x, y); } protected void pointerDragged(int x, int y) { handlePointerEvent(View.POINTER_DRAGGED, x, y); } protected void pointerReleased(int x, int y) { handlePointerEvent(View.POINTER_RELEASED, x, y); } private void handlePointerEvent(int type, int x, int y) { options.handlePointerEvent(type, x, y); exit.handlePointerEvent(type, x, y); back.handlePointerEvent(type, x, y); if (optionsDialog.isVisible()) { if (!backX.handlePointerEvent(type, x, y)) { optionsDialog.handlePointerEvent(type, x, y); } } else if (victoryDialog.isVisible()) { return; } else if (numberSelector.isVisible()) { numberSelector.handlePointerEvent(type, x, y); } else { sudoku.handlePointerEvent(type, x, y); } } protected void keyPressed(int i) { handleKeyEvent(View.KEY_PRESSED, i); } protected void keyRepeated(int i) { handleKeyEvent(View.KEY_REPEAT, i); } protected void keyReleased(int i) { handleKeyEvent(View.KEY_RELEASED, i); } private void handleKeyEvent(int type, int i) { if (i == LEFT_SOFTKEY) { options.keyEvent(type); } else if (i == RIGHT_SOFTKEY) { if (exit.isVisible()) { exit.keyEvent(type); } else if (back.isVisible()) { back.keyEvent(type); } } else if (optionsDialog.isVisible()) { if (getNumber(i) < 0) { optionsDialog.keyEvent(type, getViewKey(i)); } } else if (victoryDialog.isVisible()) { return; } else { int n = getNumber(i); if (n >= 0) { if (type == View.KEY_PRESSED) { sudoku.setNumber(n); } } else if (numberSelector.isVisible()) { numberSelector.keyEvent(type, getViewKey(i)); } else { sudoku.keyEvent(type, getViewKey(i)); } } } public int getNumber(int keyCode) { switch (keyCode) { case KEY_NUM0: return 0; case KEY_NUM1: return 1; case KEY_NUM2: return 2; case KEY_NUM3: return 3; case KEY_NUM4: return 4; case KEY_NUM5: return 5; case KEY_NUM6: return 6; case KEY_NUM7: return 7; case KEY_NUM8: return 8; case KEY_NUM9: return 9; default: return -1; } } public int getViewKey(int keyCode) { switch (getGameAction(keyCode)) { case UP: return View.KEY_UP; case DOWN: return View.KEY_DOWN; case LEFT: return View.KEY_LEFT; case RIGHT: return View.KEY_RIGHT; case FIRE: return View.KEY_SELECT; default: return View.KEY_UNKNOWN; } } private boolean isPortrait(int w, int h) { return w < h; } private boolean isSmallScreen(int w, int h) { final int min = Math.min(w, h); final int max = Math.max(w, h); if (min < 240 || max < 320) { return true; } return false; } private void generateLayout() { final int w = getWidth(); final int h = getHeight(); layout = new Layout(w, h); generateTitle(w, h); generateButtons(w, h); generateBoard(w, h); generateStatusViews(w, h); generateNumberSelector(w, h); generateVictoryDialog(w, h); generateOptionsDialog(w, h); updateLayout(w, h); } private synchronized void updateLayout(int w, int h) { updateButtons(w, h); updateBoard(w, h); updateTitle(w, h); updateStatusViews(w, h); updateNumberSelector(w, h); updateVictoryDialog(w, h); updateOptionsDialog(w, h); layout.setSize(w, h); layout.invalidate(); } private void generateTitle(int w, int h) { title = new ImageView(loadImage("/title.png", w, h)); layout.addView(title); } private void updateTitle(int w, int h) { title.setLeft(0); title.setTop(0); title.setWidth(w); title.setHeight(sudoku.top); title.invalidate(); } private int getMinTitleHeight(int w, int h) { return h / 10; } private void generateButtons(int w, int h) { final Button.Listener buttonListener = new Button.Listener() { public void onClick(Button b) { if (b == exit) { main.close(); } else if (b == back) { back(); } else if (b == options) { showOptionsDialog(); } } }; exit = new Button("Exit", buttonListener); layout.addView(exit); back = new Button("Back", buttonListener); back.setVisible(false); layout.addView(back); options = new Button("Options", buttonListener); layout.addView(options); } private void back() { if (optionsDialog.isVisible()) { hideOptionsDialog(); } else { hideNumberSelector(); } } private int getButtonsHeight(int w, int h) { return isPortrait(w, h) ? h / 13 : h / 10; } private void updateButtons(int w, int h) { final int width = isPortrait(w, h) ? w / 3 : w / 4; final int height = getButtonsHeight(w, h); exit.setSize(width, height); exit.setRight(w); exit.setBottom(h); exit.invalidate(); back.setSize(width, height); back.setRight(w); back.setBottom(h); back.invalidate(); options.setSize(width, height); options.setLeft(0); options.setBottom(h); options.invalidate(); } private int getStatusItemHeight(int w, int h) { return h / 10 - 4; } private void generateBoard(int w, int h) { sudoku = new SudokuView(new SudokuView.Listener() { public void onCellSelected() { showNumberSelector(); } public void onSetNumber() { if (numberSelector.isVisible()) { hideNumberSelector(); } updateEmptyAndMoves(); if (sudoku.isComplete()) { showVictoryDialog(); } } }); layout.addView(sudoku); } private void updateBoard(int w, int h) { final int horizontalPadding = 4; Image image = loadImage("/board_tiles.png", w, h); final int maxBoardWidth = isPortrait(w, h) ? w : w * 3 / 4 - horizontalPadding; final int maxBoardHeight = h - getMinTitleHeight(w, h) - getButtonsHeight( w, h) - (isPortrait(w, h) ? getStatusItemHeight(w, h) : 0); final int maxBoardSize = Math.min(maxBoardWidth, maxBoardHeight); int boardSize = image.getHeight() * 9 + 1; if (boardSize > maxBoardSize || boardSize < maxBoardSize) { int newHeight = image.getHeight() * maxBoardSize / boardSize; if (newHeight % 2 == 0) { newHeight -= 1; } if (newHeight != image.getHeight()) { int newWidth = image.getWidth() / image.getHeight() * newHeight; image = scaleImage(image, newWidth, newHeight); boardSize = image.getHeight() * 9 + 1; } } sudoku.setLeft( isPortrait(w, h) ? (w - boardSize) / 2 : (w - boardSize * 4 / 3) / 2); sudoku.setTop(getMinTitleHeight(w, h) + (maxBoardHeight - boardSize) / 2); sudoku.setBoardSize(boardSize); sudoku.setBoardImage(image); } private void generateStatusViews(int w, int h) { empty = new StatusView(loadImage("/empty.png", w, h)); layout.addView(empty); moves = new StatusView(loadImage("/moves.png", w, h)); layout.addView(moves); elapsed = new StatusView(loadImage("/time.png", w, h)); layout.addView(elapsed); } private void updateStatusViews(int w, int h) { int width = sudoku.width / 3; int height = getStatusItemHeight(w, h); empty.setSize(width, height); moves.setSize(width, height); elapsed.setSize(width, height); if (isPortrait(w, h)) { empty.setLeft(sudoku.left); moves.setLeft(empty.right); elapsed.setLeft(moves.right); empty.setTop(sudoku.bottom); moves.setTop(sudoku.bottom); elapsed.setTop(sudoku.bottom); } else { int left = sudoku.right + 2; int top = sudoku.top + sudoku.height / 2 - height * 3 / 2; empty.setLeft(left); moves.setLeft(left); elapsed.setLeft(left); empty.setTop(top); moves.setTop(empty.bottom); elapsed.setTop(moves.bottom); } empty.invalidate(); moves.invalidate(); elapsed.invalidate(); } private void generateNumberSelector(int w, int h) { final Image nButtonImage = loadImage("/dial.png", w, h); final Image cButtonImage = loadImage("/dial_c.png", w, h); final int selectorWidth = nButtonImage.getWidth() / 2 * 3; final int selectorHeight = nButtonImage.getHeight() * 3 + cButtonImage.getHeight(); numberSelector = new NumberSelector(selectorWidth, selectorHeight, nButtonImage, cButtonImage, new NumberSelector.Listener() { public void numberSelected(int n) { sudoku.setNumber(n); } }); numberSelector.setVisible(false); layout.addView(numberSelector); } private void updateNumberSelector(int w, int h) { numberSelector.setLeft((w - numberSelector.width) / 2); numberSelector.setTop( sudoku.top + (sudoku.height - numberSelector.height) / 2); numberSelector.invalidate(); } private void generateVictoryDialog(int w, int h) { victoryDialog = new VictoryDialog(loadImage("/victory.png", w, h)); victoryDialog.setVisible(false); layout.addView(victoryDialog); } private void updateVictoryDialog(int w, int h) { victoryDialog.setLeft((w - victoryDialog.width) / 2); victoryDialog.setTop( sudoku.top + (sudoku.height - victoryDialog.height) / 2); victoryDialog.invalidate(); } private void generateOptionsDialog(int w, int h) { optionsDialog = new OptionsDialog(loadImage("/panel.png", w, h), new OptionsDialog.Listener() { public void onItemClick(int itemIndex) { hideOptionsDialog(); switch (itemIndex) { case 0: sudoku.restart(); hideVictoryDialog(); updateEmptyAndMoves(); updateElapsed(); break; case 1: sudoku.newGame(SudokuGenerator.newPuzzle()); hideVictoryDialog(); updateEmptyAndMoves(); updateElapsed(); break; case 2: main.close(); break; default: break; } } }); optionsDialog.setVisible(false); layout.addView(optionsDialog); backX = new ImageView(loadImage("/close.png", w, h), new ImageView.Listener() { public void onClick() { back(); } }); backX.setVisible(false); layout.addView(backX); } private void updateOptionsDialog(int w, int h) { optionsDialog.setLeft((w - optionsDialog.width) / 2); optionsDialog.setTop( sudoku.top + (sudoku.height - optionsDialog.height) / 2); optionsDialog.invalidate(); backX.setLeft(optionsDialog.right - backX.width / 2); backX.setTop(optionsDialog.top - backX.height / 2); backX.invalidate(); } private void refreshBackButton() { if (numberSelector.isVisible() || optionsDialog.isVisible()) { exit.setVisible(false); back.setVisible(true); } else { back.setVisible(false); exit.setVisible(true); } } private void showNumberSelector() { numberSelector.setVisible(true); if (!hasPointerEvents()) { numberSelector.setKeyPressed(5); } refreshBackButton(); } private void hideNumberSelector() { numberSelector.setVisible(false); refreshBackButton(); } private void showVictoryDialog() { if (numberSelector.isVisible()) { hideNumberSelector(); } victoryDialog.setVisible(true); victoryDialog.moves = sudoku.getMoves(); victoryDialog.seconds = sudoku.getElapsedSeconds(); } private void hideVictoryDialog() { victoryDialog.setVisible(false); } private void showOptionsDialog() { if (numberSelector.isVisible()) { hideNumberSelector(); } optionsDialog.setVisible(true); if (!hasPointerEvents()) { optionsDialog.highlightItem(0); } else { backX.setVisible(true); } refreshBackButton(); } private void hideOptionsDialog() { optionsDialog.setVisible(false); backX.setVisible(false); refreshBackButton(); } private Image loadImage(String fileName, int w, int h) { Image image = null; if (isSmallScreen(w, h)) { image = ImageLoader.getInstance().loadImage("/small" + fileName); } if (image == null) { image = ImageLoader.getInstance().loadImage("/medium" + fileName); } return image; } private Image scaleImage(Image original, int newWidth, int newHeight) { return ImageLoader.scaleImage(original, newWidth, newHeight); } }