Android 2¶
Aknakereső Android alkalmazás fejlesztése¶
Bevezetés¶
A labor során Android platformra kell elkészíteni egy Aknakereső játékot Kotlin nyelven:
http://en.wikipedia.org/wiki/Minesweeper_(video_game)

Szabályok¶
A labor során az idő rövidsége miatt elegendő, ha az alkalmazás az alábbi megkötésekkel üzemel:
- 5x5 játéktér elég
- 3 akna elég
- nem kell teljes feltöltő logikát írni, elég ha be van égetve egy fix elrendezés
- a felületen elhelyezhető egy ToggleButton vagy checkbox az akció kiválasztásához:
- zászló lehelyezése
- mező kipróbálása
- Ha a kipróbált mező:
- akna, a játék véget ér
- nem akna, akkor megjelenik egy szám, ami a szomszédos aknák számát jelzi
Érdemes először végigolvasni az összes feladatot, hogy aki minden funkciót szeretne megvalósítani, már az elején úgy tervezhessen, és ne közben kelljen módosítani az adatmodellen pl. Az alkalmazás elkészítése során törekedj a strukturált felépítésre, kódszervezésére. Készíts letisztult, ergonomikus felhasználói felületeket, figyelj a vissza gomb megfelelő működésére, továbbá a hibák kezelésére és a felhasználó számára történő releváns visszajelzésekre.
Értékelés¶
Az Alapok rész hiánytalan megvalósítása esetén sikeres (elégséges) a labor. Az Alapok részen kívül megoldott minden további részfeladat hiánytalan megvalósítása plusz egy jegyet jelent. A feladatok részben egymásra épülnek, ezért az ebből következő hiányok – feladat kihagyás esetén – szükség szerint áthidalhatók pl. dummy adatok használatával.
Előkészületek¶
A feladatok megoldása során ne felejtsd el követni a feladat beadás folyamatát.
Git repository létrehozása és letöltése¶
-
Moodle-ben keresd meg a laborhoz tartozó meghívó URL-jét és annak segítségével hozd létre a saját repository-dat.
-
Várd meg, míg elkészül a repository, majd checkout-old ki.
Checkout
Egyetemi laborokban, ha a checkout során nem kér a rendszer felhasználónevet és jelszót, és nem sikerül a checkout, akkor valószínűleg a gépen korábban megjegyzett felhasználónévvel próbálkozott a rendszer. Először töröld ki a mentett belépési adatokat (lásd itt), és próbáld újra.
-
Hozz létre egy új ágat
megoldasnéven, és ezen az ágon dolgozz. -
A
neptun.txtfájlba írd bele a Neptun kódodat. A fájlban semmi más ne szerepeljen, csak egyetlen sorban a Neptun kód 6 karaktere.
Projekt létrehozása¶
Első lépésként indítsuk el az Android Studio-t, majd:
- Hozzunk létre egy új projektet, válasszuk az Empty Views Activity lehetőséget.
- A projekt neve legyen
MineSweeper, a kezdő package pedighu.bme.aut.klaf.minesweeper. - Nyelvnek válasszuk a Kotlin-t.
- A minimum API szint legyen API24: Android 7.0.
- A Build configuration language Kotlin DSL legyen.
FILE PATH
A projekt mindenképpen a repository-ban lévő MineSweeper könyvtárba kerüljön, és beadásnál legyen is felpusholva! A kód nélkül nem tudunk maximális pontot adni a laborra!
1. Alapok (2 pont)¶
Tippek
- Törekedj a rövid osztályokra és függvényekre, valamint az átlátható forráskódra!
- Ügyelj a megfelelően hierarchikus package szervezésre, a kódolás során tartsd szem előtt a Clean Code elveket!
- Kezeld megfelelően a készülék elforgatása során bekövetkező életciklus változásokat!
- Perzisztenciánál figyelj a megfelelő szálkezelésre!
- A feladat megvalósítása és beadása során önálló, egyedi munkákat várunk!
Egyedi ikon¶
Állíts be az alkalmazásnak egyedi vagy saját készítésű ikont.
Főmenü¶
Az alkalmazás indulása után egy főmenü jelenjen meg két gombbal: "Új játék" és "Eredmények"
Az "Új játék" gomb megnyomására egy új felületen jelenjen meg a játéktér, amin érintés esemény hatására változnak az egyes mezők. Mivel itt több elem együttes működésére van szükség, a lépéseket és mintakódokat biztosítunk.
Játék motor¶
A játék állapotának nyilvántartásához szükségünk lesz egy mező osztályra és egy singleton model osztályra:
data class Tile(
var type: TileType = TileType.FIELD_TYPE_NORMAL,
var minesAround: Byte = 0, //-1 means mine
) {
enum class TileType {
FIELD_TYPE_NORMAL,
FIELD_TYPE_REVEALED,
FIELD_TYPE_FLAGGED
}
}
object MineSweeperModel {
var fieldMatrix: Array<Array<Tile>> = arrayOf(
arrayOf(
Tile(Tile.TileType.FIELD_TYPE_NORMAL, 1),
Tile(Tile.TileType.FIELD_TYPE_NORMAL, 1),
...
),
arrayOf(
Tile(Tile.TileType.FIELD_TYPE_NORMAL, 2),
Tile(Tile.TileType.FIELD_TYPE_FLAGGED, -1),
...
),
...
)
)
fun initGameArea() {
...
}
fun getFieldContent(x: Int, y: Int): Tile {
return fieldMatrix[x][y]
}
Felület¶
A képernyőn való megjelenítéshez szükségünk lesz egy saját View-ra, minek felül kell írni az alábbi függvényeit:
- onDraw: itt történik a view kirajzolása. Ezt jelenleg két részre bontottuk: a játéktér és a játékállapot kirajzolása.
- onMeasure: ahhoz szükséges, hogy megállapítsuk a megfelelő dimenziókat egy maximális négyzet rajzolásához.
- onTouchEvent: az érintés események lekezelése. Itt kell az felhasználói akció és a meglévő modell alapján változtatni a modellt. A végén az invalidate() hatására rajzolódik újra a view.
class MineSweeperView : View {
private val paintBg = Paint()
private val paintLine = Paint()
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
init {
paintBg.color = Color.BLACK
paintBg.style = Paint.Style.FILL
paintLine.color = Color.WHITE
paintLine.style = Paint.Style.STROKE
paintLine.strokeWidth = 5F
}
override fun onDraw(canvas: Canvas) {
canvas.drawRect(0F, 0F, width.toFloat(), height.toFloat(), paintBg)
drawGameArea(canvas)
drawGameState(canvas)
}
private fun drawGameArea(canvas: Canvas) {
val widthFloat: Float = width.toFloat()
val heightFloat: Float = height.toFloat()
// border
canvas.drawRect(0F, 0F, widthFloat, heightFloat, paintLine)
// four horizontal lines
canvas.drawLine(0F, heightFloat / 5, widthFloat, heightFloat / 5, paintLine)
canvas.drawLine(0F, 2 * heightFloat / 5, widthFloat, 2 * heightFloat / 5, paintLine)
...
// four vertical lines
canvas.drawLine(widthFloat / 5, 0F, widthFloat / 5, heightFloat, paintLine)
canvas.drawLine(2 * widthFloat / 5, 0F, 2 * widthFloat / 5, heightFloat, paintLine)
...
}
private fun drawGameState(canvas: Canvas) {
for (i in 0 until 5) {
for (j in 0 until 5) {
if (MineSweeperModel.getFieldContent(i, j).type == Tile.TileType.FIELD_TYPE_FLAGGED) {
canvas.drawLine(
(i * width / 5).toFloat(),
(j * height / 5).toFloat(),
((i + 1) * width / 5).toFloat(),
((j + 1) * height / 5).toFloat(),
paintLine
)
canvas.drawLine(
((i + 1) * width / 5).toFloat(),
(j * height / 5).toFloat(),
(i * width / 5).toFloat(),
((j + 1) * height / 5).toFloat(),
paintLine
)
}
...
}
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val w = View.MeasureSpec.getSize(widthMeasureSpec)
val h = View.MeasureSpec.getSize(heightMeasureSpec)
val d: Int
when {
w == 0 -> {
d = h
}
h == 0 -> {
d = w
}
else -> {
d = min(w, h)
}
}
setMeasuredDimension(d, d)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
val tX: Int = (event.x / (width / 5)).toInt()
val tY: Int = (event.y / (height / 5)).toInt()
if (tX < 5 && tY < 5 && MineSweeperModel.getFieldContent(tX, tY).type == Tile.TileType.FIELD_TYPE_NORMAL) {
...
invalidate()
}
return true
}
else -> return super.onTouchEvent(event)
}
}
- A saját view ezek után teljes package hivatkozással már használható is a felületen:
<hu.bme.aut.klaf.minesweeper.view.MineSweeperView
android:id="@+id/mineSweeperView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
.../>
BEADANDÓ (2 pont)
Ebben a fázisban az alkalmazásnak képesnek kell lennie az érintés eseményre reagálni. Vagyis: E mezőhöz való hozzáérésnél valamilyen változást kell látnunk a játéktéren.
Készíts egy képernyőképet, amelyen látszik a futó alkalmazás és egy hozzá tartozó kódrészlet.
A képet a megoldásban a repository-ba f1.png néven töltsd föl.
2. Bombák, zászlók, számok megjelenítése (1 pont)¶
Ha már a játéktér megjelenik a felületen és össze van kötve a modellel, készítsd el és tedd használhatóvá a játék különbözö elemeit (grafikusan mindegyik máshogy jelenjen meg):
- A megérintett mező színe változzon meg, hogy egyértelmű legyen, hogy az már fel van fedve.
- Ha a megérintett mező alatt bomba van, az legyen egyértelműen jelezve.
- Ha a megérintett mező alatt szám van, az jelenjen meg.
- Legyen lehetőség zászlók letételére és felvételére még nem megnézett mezőn. (Ez bármilyen módszerrel történhet, egy lehetséges mód ToggleButton elhelyezése a felületen, amivel a következő akció állítható)
BEADANDÓ (1 pont)
Készíts egy képernyőképet, amelyen látszik a futó alkalmazás a feladatnak megfelelő állapotban és egy hozzá tartozó kódrészlet.
A képet a megoldásban a repository-ba f2.png néven töltsd föl.
3. Játék vége és időmérés (1 pont)¶
A felületre helyezz el egy futó órát, ami a megoldás idejét számolja. Készítsd el a játék befejezésének logikáját, és a befejezést jelezze dialógusablakban:
- Ha egy bomba felfedésre kerül, a játék vereséggel véget ér.
- Ha a bombák kivételével az összes mező felfedésre került, a játék győzelemmel ér véget. Ekkor a dialógusablakban jelenjen meg a felhasznált idő is.
BEADANDÓ (1 pont)
Készíts egy képernyőképet, amelyen látszik a futó alkalmazás az órával és egy hozzá tartozó kódrészlet.
A képet a megoldásban a repository-ba f3.png néven töltsd föl.
4. Perzisztencia (1 pont)¶
Ha a játék győzelemmel ér véget, kérjen be egy felhasználónevet, és ezt az idővel együtt mentse el perzisztensen.
Valósítsd meg az "Eredmények" gomb funkcionalitását! Nyíljon meg egy új felület, amin egy listában láthatóak a nevek és az elért időeredmények.
BEADANDÓ (1 pont)
Készíts egy képernyőképet, amelyen látszik a futó alkalmazás a játék vége dialógussal és egy hozzá tartozó kódrészlet.
A képet a megoldásban a repository-ba f4.png néven töltsd föl.