帮酷LOGO
0 0 评论
  • 显示原文与译文双语对照的内容
文章标签:Solver  Puzzle  


Puzzles Solver

介紹

遊戲解決方案是一個應用程序,我一段時間前上傳到Android市場。 應用程序的想法來自一個舊的CodeProject文章。 在這篇文章中,我提出了一些已經知的遊戲的Collection,以及電腦解決它們的機制。 謎題被實現為 JavaApplet,因此很容易將它們移植到Android平台上。 應用程序提供以下遊戲:

  • 8 皇后:玩家需要在棋盤上放置皇后,這樣就不會攻擊其他玩家。
  • 在棋盤的所有方格中引導騎士並返回到起點。
  • 獨奏: 跳過銷子來移除它們。 除最後一個釘子外全部除去。

應用程序為所有遊戲提供了變化。 它也能解決你的難題。 未使用預存儲的解決方案。 應用程序通過應用一個簡單的惡意搜索演算法嘗試計算解決方案。

在本文中,我想展示我所做的所有設計決定和我所遵循的模式,同時開發應用程序。 我希望文章讀者會發現他們有用,也許會在自己的應用程序中使用它們。

用戶界面圖

下面的圖表描述了應用程序的activity 框圖。 當開始設計一個新應用程序時,嘗試繪製一下應用程序是一個好主意。 這將幫助你儘早識別用戶界面挑戰。 它還將幫助將可視組件映射到Java類。

Wireframe diagram for Puzzle Solver

在本文中,框架框幫助顯示一些重要的用戶界面模式。 應用程序可以很快啟動,所以不需要啟動屏幕。 應用程序立即啟動主屏幕,它只顯示一行按鈕。 這個屏幕提供了應用程序提供( 開始拼圖,看分數,獲取幫助) 功能的視覺提示,並允許用戶使用一或者兩個觸摸屏訪問它。 當然,你可以將第一個屏幕設置為比一行按鈕更有趣,但是每個移動應用程序都很少使用。 第一個屏幕還提供了菜單,但這不是必需的。 菜單提供的功能與菜單相同。 只有關於框的內容才隱藏在它的中。 你可以在菜單中隱藏這些功能,以節省屏幕空間,而不會分散用戶的注意力。

從主屏幕到謎題和分數( 每個謎題都有不同的得分屏幕)的轉換是通過一個列表完成的。 按下按鈕時,會出現一個帶有可用謎題的列表。 另一種解決方案是使用單獨的屏幕( 由一個新的Activity 實現) 來顯示所有的選擇。 如果有很多遊戲選擇,這將是非常理想的( 比如。 在線遊戲,定時和非計時模式,水平選擇)。

遊戲引擎

遊戲引擎基於一個文章我在CodeProject上寫的文章,就像在 2004中所寫的那樣。 支持同一應用程序中多個難題的想法是創建一個 abstract 類來實現所有通用功能。 對於每個單獨的難題,都有 abstract 類的具體實現。 這是 solve 方法的摘錄,因為這是在 abstract 類( 為了演示目的作了輕微修改) 中實現的:

while(!searched_all && movesMade()!= movesTableSize) {
 if (!findNextMove()) {
 searched_all =!goBack();
 while(!searched_all &&
 movesMade() == 0) {
 searched_all =!goBack();
 }
 }
}

這裡 while 循環是求解器演算法的核心。 各個謎題實現了其中的方法,即 movesMade()goBack()findNextMove()。 這實際上是模板方法設計 Pattern 插件的一種形式。

abstractPuzzle 類還用於為單個 Puzzle 類的調用方提供介面。 只有在初始化謎題時才會出現具體的實現。 下面的類圖描述了應用程序的主要類,即 PuzzleActivityPuzzleViewPuzzle

Puzzles Solver Class Diagram

PuzzleActivityonCreate 方法中,初始化一個 PuzzleView 實例。 同時創建 Puzzle 對象並將它的注入到 PuzzleView 中。

@Overridepublicvoid onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 Intent intent = getIntent();
 Bundle bundle = intent.getExtras();
 gameType = bundle.getInt("GameType", 0);
 switch(gameType) {
 case0:
 puzzle = new Q8Puzzle(this);
 break;
 case1:
 puzzle = new NumberSquarePuzzle(this);
 break;
 case2:
 puzzle = new SoloPuzzle(this);
 break;
 }
 timeCounter = new TimeCounter();
 puzzle.init();
 puzzleView = new PuzzleView(this);
 puzzleView.setPuzzle(puzzle);
 puzzleView.setTimeCounter(timeCounter);
 setContentView(puzzleView);
 timerHandler = new Handler();
 replayHandler = new Handler();
 scoresManager = ScoresManagerFactory.getScoresManager(getApplicationContext());
}

這是唯一的地方,Puzzle的具體實現出現了。 負責服務用戶界面的PuzzleView 通過調用 Puzzle.drawPuzzle.onTouchEvent 方法來執行作業。 如果要添加一個新的拼圖,那麼唯一需要的就是一個新的拼圖 case 語句。

在本文的第二版中,我增加了在Solo中創建定製板的支持。 這導致了一些特殊的處理添加:

case R.id.custom_boards:
 if (puzzle instanceof SoloPuzzle) {
 if (soloPuzzleRepository!= null && 
 soloPuzzleRepository.getCustomBoardsCount()> 0) {
 Intent editIntent = new Intent
 ("gr.sullenart.games.puzzles.SOLO_EDIT_BOARDS");
 startActivity(editIntent); 
 }
 else {
 SoloCustomBoardActivity.showDirections = true;
 Intent addIntent = new Intent
 ("gr.sullenart.games.puzzles.SOLO_ADD_BOARD");
 startActivity(addIntent);
 }
 }

但是,也可以擴展基類以支持自定義板。 畢竟,添加定製板是許多難題的共同特徵。 這樣,特殊的處理就會消失。

支持多個屏幕

Android在各種設備上運行,因此應用程序能夠支持不同的屏幕大小和密度是很重要的。 官方文檔插件提供了屏幕支持過程的全面指導。 這是不斷發展的文檔,每個新版本的SDK新屏幕大小和密度都在增加。

在這裡介紹的實踐中,我將介紹一種使用瓷磚繪製用戶界面的技術。 瓦片是小矩形圖像( 例如 80 x80或者 100 x100像素)。 用戶界面是通過水平和垂直重複這些圖像構建的。 這種技術利用了在Android中你可以輕鬆地動態調整圖像大小的優點。 因這裡,一組瓦片可以支持各種屏幕大小,同時也可以支持 portrait 和 landscape 方向。 然而,我應該注意,這種技術適合於遊戲或者它的他應用程序的拼圖,在用戶界面上只有慢變化。 使用它快速更改用戶界面可能不是有效的做法。 這裡技術還提供了一種在應用程序中支持主題化的方法。

讓我們從使用tile在布局中創建 background 開始。 這就像定義一個可以繪製的XML一樣簡單:

<?xmlversion="1.0"encoding="utf-8"?><bitmapxmlns:android="http://schemas.android.com/apk/res/android"android:src="AndroidPuzzlesSolver/@drawable/bg_tile"android:tileMode="repeat"android:dither="true"/>

然後在布局XML中:

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="fill_parent"android:orientation="vertical"android:background="@drawable/background">

當你從布局創建用戶界面時,這就解決了這個問題。 但是,對於謎題和簡單遊戲,在視圖的onDraw 方法上繪製一切都是很常見的。 下面的方法在 onDraw的開頭調用時,在畫布上繪製 background:

privatevoid drawBackgroundRepeat(Canvas canvas, Bitmap bgTile) {
 float left = 0, top = 0;
 float bgTileWidth = bgTile.getWidth();
 float bgTileHeight = bgTile.getWidth();
 while (left <screenWidth) {
 while (top <screenHeight) {
 canvas.drawBitmap(bgTile, left, top, null);
 top += bgTileHeight;
 }
 left += bgTileWidth;
 top = 0;
 }
}

為了在多個屏幕尺寸中使用圖像來繪製用戶界面,需要調整一些大小。 下面的圖表描述了屏幕的組織。 虛線顯示 background 瓦片。 因為這隻是相同圖像的重複,並且不需要在邊緣顯示完整的圖像,所以不需要調整大小。 實線顯示遊戲牌必須放置在哪裡才能形成遊戲板。

Using tiles for creating the User Interface

作為例子,Solo屏幕是用下面的瓦片繪製的。 所有圖像均使用 Gimp 生成。

TypeWood ThemeMarble主題
棋盤平鋪
孔平鋪
釘在孔上
選定的peg
選擇的孔

首先讓我們看看 ImageResizer 類。

publicclass ImageResizer {
 private Matrix matrix;
 publicvoid init(int oldWidth, int oldHeight, float newWidth, float newHeight) {
 float scaleWidth = newWidth/oldWidth;
 float scaleHeight = newHeight/oldHeight;
 matrix = new Matrix();
 matrix.postScale(scaleWidth, scaleHeight);
 }
 public Bitmap resize(Bitmap bitmap) {
 if (matrix == null) {
 return bitmap;
 }
 int width = bitmap.getWidth();
 int height = bitmap.getHeight();
 Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
 width, height, matrix, true);
 return resizedBitmap;
 }
}

這是一個實用工具類,提供了動態調整點陣圖大小的方法。 需要通過提供原始點陣圖和已經調整大小的點陣圖的尺寸來初始化此類的實例。 這個類被應用在拼圖視圖的onSizeChanged 方法中。 當屏幕的大小改變時,調用 onSizeChanged 方法( 比如。 當發生方向更改或者屏幕第一次顯示時,。 在這裡方法中,從應用程序的資源載入平鋪圖像。 根據主題不同,載入了不同的圖像。 載入的點陣圖然後調整大小以適應現有屏幕並保存在內存中。 圖像在畫布上繪製,每個拼圖的draw 方法。

publicvoid onSizeChanged(int w, int h) {
 super.onSizeChanged(w, h);
 int boardSize = (boardRows> boardColumns)? boardRows : boardColumns;
 if (w <h) {
 tileSize = (w - 10)/boardSize;
 }
 else {
 tileSize = (h - 10)/boardSize;
 }
 offsetX = (screenWidth - tileSize*boardColumns)/2;
 offsetY = (screenHeight - tileSize*boardRows)/2;
 imageResizer = new ImageResizer();
 if (theme.equals("marble")) {
 emptyImage = BitmapFactory.decodeResource(context.getResources(),
 R.drawable.marble_tile);
 tileImage = BitmapFactory.decodeResource(context.getResources(),
 R.drawable.wood_sphere);
 tileSelectedImage = BitmapFactory.decodeResource(context.getResources(),
 R.drawable.golden_sphere);
 }
 else {
 emptyImage = BitmapFactory.decodeResource(context.getResources(),
 R.drawable.wood_tile);
 tileImage = BitmapFactory.decodeResource(context.getResources(),
 R.drawable.glass);
 tileSelectedImage = BitmapFactory.decodeResource(context.getResources(),
 R.drawable.glass_selected);
 }
 freePosImage = BitmapFactory.decodeResource(context.getResources(),
 R.drawable.hole);
 freePosAllowedMoveImage = BitmapFactory.decodeResource(context.getResources(),
 R.drawable.hole_move);
 imageResizer.init(emptyImage.getWidth(), emptyImage.getHeight(), 
 tileSize, tileSize);
 emptyImage = imageResizer.resize(emptyImage);
 freePosImage = imageResizer.resize(freePosImage);
 tileImage = imageResizer.resize(tileImage);
 tileSelectedImage = imageResizer.resize(tileSelectedImage);
 freePosAllowedMoveImage = imageResizer.resize(freePosAllowedMoveImage);
}

彈出窗口

在本文的第三個修訂中,我用一個更好的彈出窗口替換了簡單列表謎題選擇器。 你可以在下圖中比較這兩個選擇器的差異:

Puzzle Selectors

新的彈出窗口更具吸引力。 它也使拼圖選擇更快,因為它看起來更接近點,用戶第一次觸摸並且不灰色。 它也可以輕鬆的動畫。 為了創建彈出窗口,從布局XML文件開始,就像對對話框或者視圖所做的那樣。 為了你的布局定義 background 很重要,因為默認情況下,Android PopupWindow 並沒有提供一個。 在Java代碼中,使 PopupWindow 顯示為以下代碼:

privatevoid showGameSelectPopup(View parentView) {
 LayoutInflater inflater = (LayoutInflater)
 getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 View layout = inflater.inflate(R.layout.puzzle_select, null, false); 
 gameSelectPopupWindow = new PopupWindow(this);
 gameSelectPopupWindow.setTouchInterceptor(new OnTouchListener() {
 @Overridepublicboolean onTouch(View v, MotionEvent event) {
 if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
 gameSelectPopupWindow.dismiss();
 gameSelectPopupWindow = null;
 return true;
 }
 return false;
 }
 });
 gameSelectPopupWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
 gameSelectPopupWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
 gameSelectPopupWindow.setTouchable(true);
 gameSelectPopupWindow.setFocusable(true);
 gameSelectPopupWindow.setOutsideTouchable(true);
 gameSelectPopupWindow.setContentView(layout);
 // Set click listeners by calling layout.findViewById()// layout.findViewById(R.id.queens_select).setOnClickListener();int [] location = newint [] {0, 0};
 parentView.getLocationInWindow(location);
 int width = parentView.getWidth()/2;
 int x = location[0] - width/2;
 int y = location[1] + parentView.getHeight(); 
 gameSelectPopupWindow.setAnimationStyle(R.style.AnimationPopup);
 gameSelectPopupWindow.showAtLocation(layout, Gravity.NO_GRAVITY, x, y); 
}@Overridepublicvoid onStop() {
 if (gameSelectPopupWindow!= null) {
 gameSelectPopupWindow.dismiss();
 }
 super.onStop();
}

show 方法接受作為參數的視圖,視圖是啟動操作的Button 或者其他項。 這允許將 PopupWindow relative 定位到用戶首次單擊的點。 首先,我們通過放大布局來為 PopupWindow 創建視圖。 然後我們創建 PopupWindow 對象並適當配置它。 我們使用父視圖的getLocationInWindow()getWidth()getHeight() 方法來正確定位 Popup。 finally,我們設置動畫樣式並顯示 Popup 窗口。 如果用戶在窗口外單擊,將取消這裡操作。 為了不泄漏窗口,我們需要檢查 Popup 是否可以見,當 Activity 停止並關閉它時。 如果方向改變,而 Popup 可見,則可能發生這種情況。

另一個棘手的一點是動畫風格。 必須在 values styles.xml 中的文件中定義該文件,並具有 below 窗體:

<resources><stylename="AnimationPopup"><itemname="@android:windowEnterAnimation">@anim/popup_show</item><itemname="@android:windowExitAnimation">@anim/popup_hide</item></style></resources>

保持分數

為了存儲用戶的分數,你需要一個持久化機制。 Android平台的持久性選項在文檔中描述。 從這些選項中,最適合存儲分數的是SQLite資料庫。 下面的語句介紹了我使用的table的架構:

privatestaticfinalString DATABASE_CREATE =
 "create table Scores (_id integer primary key autoincrement," +
 "game text not null, category text not null, 
 player text not null, date text," +
 "score integer not null);";

為了從它的余的代碼抽象資料庫,我創建了一個 ScoresManager 類,它提供了訪問分數的方法。

publicclass ScoresManager {
 publicboolean addScore(String game, String group, String player, int score);
 publicboolean isHighScore(String game, int score);
 public List<Score> getScores(String game);
 public List<Score> getScoresByGroup(String group);
}

我應該指出,Android有很多開源的分數庫。 你可以找到經過良好測試的庫,提供豐富的特性,包括與網頁評分系統集成。 如果不要學習與 sql rtc資料庫交互,最好使用這些資料庫中的一個進行評分。

設置

另一個持久性需要是存儲用戶的選擇。 由於每個謎題都有許多變化,如果應用程序記住了,用戶最後一次播放的遊戲。 你可以使用 SharedPreferences 輕鬆地在Android中實現這一點。

SharedPreferences 是在一個單獨的XML文件中為每個謎題定義的。 Puzzleabstract 類定義了 configure 方法。 具體的類實現這裡方法以讀取用戶的設置。

publicboolean configure(SharedPreferences preferences)

Android提供了 PreferenceActivity,你可以很容易地擴展它,以便創建簡單的用戶界面來編輯設置。

publicclass PuzzleOptionsActivity extends PreferenceActivity {
 @Overrideprotectedvoid onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 Intent intent = getIntent();
 Bundle bundle = intent.getExtras();
 int gameResources = bundle.getInt("GameResources", 0);
 addPreferencesFromResource(gameResources);
 }
}

當然,這並不是開發 SharedPreferences的唯一方法。 你可以創建一個漂亮的用戶界面,使用Java代碼讀取和寫入 SharedPreferences。 在解決方案中,我使用了一個 PreferencesScreen,而不是實際顯示任何內容,以便存儲用戶的NAME。 當用戶獲得高分時,要求她輸入 NAME的祝賀屏幕會預先填充以前使用過的NAME。

本地化

應用在英語,德語和希臘文中被本地化。 本地化元素是應用程序(。存儲在 strings.xml 值,值和值el文件夾)。應用程序(。存儲在可以繪製,可以繪製的de文件夾中)的logo 和應用程序頁面的help,它們在資產文件夾( 以後更詳細的細節) stored作為HTML頁面存儲。

幫助

提供應用程序的使用說明是非常重要的。 當然,應用程序應該很容易使用和界面直觀,使用戶可以開始使用應用程序。 這不是 Having 很好的幫助頁面的原因。 有些用戶可能不熟悉你的遊戲或者應用程序的概念,需要一些指導。 此外,你可能會在幫助頁面中提供一些提示和技巧,即使是高級用戶也會發現幫助。

我相信使用 web view來顯示HTML文件是在Android應用程序中實現幫助頁面的理想解決方案。 需要的Java代碼數量很少,然後你只需要在 assets 文件夾中使用一個或者多個HTML文件。 編寫HTML幫助頁面非常容易。 對文本進行樣式化和添加圖像很簡單。 你可能還可以使用描述你的應用程序的現有頁面。 為幫助頁面使用HTML很容易,因為這樣很多應用程序仍然喜歡使用它的他幫助系統。

如果你想要支持多種語言,那麼你將需要不同版本的HTML頁面。 將所有頁面放置在 assets 文件夾中。 然後在 strings.xml 中,為每種語言定義頁面的NAME:

<stringname="help_file_name">index.html</string>

WebView 中載入該頁時,從資源中讀取 NAME:

webview.loadUrl("file:///android_asset/" +
 getResources().getString(R.string.help_file_name));

低調廣告

這對於移動應用非常普遍,尤它的是對於免費給出的這些廣告,顯示某種廣告。 這些廣告可以為開發者帶來收入,同時還允許她自由地分發應用程序。 有許多可以供選擇的廣告框架。 對於 PuzzleSolver,我選擇了 AdMob。 無論你選擇什麼框架,你都需要確保廣告不會分散用戶的注意力。 在遊戲中,廣告應該只顯示在輔助屏幕( 遊戲選擇,得分,幫助) 中,而不能顯示在遊戲屏幕中。 廣告不應該分散用戶玩遊戲的注意力。

為了能夠在多個 Activity 中顯示廣告,而不重複相同的代碼,我創建了一個名為 AdsManager的實用工具類。 AdsManager 實現 AdListener 並提供 addAdsView 方法,它設置發布者 id。測試設備',並將請求添加到視圖中。

publicclass AdsManager implements AdListener {
 privateString publisherId = "your publisher id here";
 publicvoid addAdsView(Activity activity, LinearLayout layout) {
 AdView adView;
 int screenLayout = activity.getResources().getConfiguration().screenLayout;
 if ((screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)> = 3) {
 adView = new AdView(activity, AdSize.IAB_LEADERBOARD, publisherId);
 }
 else {
 adView = new AdView(activity, AdSize.BANNER, publisherId);
 }
 layout.addView(adView);
 AdRequest request = new AdRequest();
 request.addTestDevice(AdRequest.TEST_EMULATOR);
 request.addTestDevice("Your test device id here - Find the id in Log Cat");
 adView.loadAd(request);
 } 

注意,對於不同的屏幕大小,有一個 differentation。 對於小型和普通屏幕,( 值 1和 2 ) 顯示橫幅。 對於大型和超大屏幕( 值 3和 4 ),將顯示一個排行榜。

這裡方法在顯示廣告的每個 Activity的onCreate 方法中調用:

LinearLayout layout = (LinearLayout)findViewById(R.id.banner_layout);
(new AdsManager()).addAdsView(this, layout);

歷史記錄

  • ::
    • 增加了對三角形板的支持和單獨的對角線移動。
    • 板上按鈕( 撤消,重新啟動,解決)。
    • 當沒有剩餘的移動時,會出現一條消息。
  • July 2012: 固定 Bug 新 icon。 使用最新版本的Android工具。 jar 包含在libs文件夾中。
  • April 2012: 新得分屏幕。 更好地支持大屏幕,包括AdMob的更改。 一個新的獨奏主題。在騎士遊覽中顯示連線的選項。 消除了大部分Lint警告。
  • January 2012: 為拼圖選擇添加了新的彈出窗口
  • January: 在Solo中增加了創建定製板的支持。 Bug 固定
  • 文章的第一個版本


文章标签:Solver  Puzzle  

Copyright © 2011 HelpLib All rights reserved.    知识分享协议 京ICP备05059198号-3  |  如果智培  |  酷兔英语