• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.egg.quares
18 
19 import android.app.Activity
20 import android.content.Context
21 import android.content.res.Configuration
22 import android.graphics.Canvas
23 import android.graphics.Paint
24 import android.graphics.Typeface
25 import android.graphics.drawable.Icon
26 import android.os.Bundle
27 import android.text.StaticLayout
28 import android.text.TextPaint
29 import android.util.Log
30 import android.view.View
31 import android.view.View.GONE
32 import android.view.View.VISIBLE
33 import android.widget.Button
34 import android.widget.CompoundButton
35 import android.widget.GridLayout
36 
37 import java.util.Random
38 
39 import com.android.egg.R
40 
41 const val TAG = "Quares"
42 
43 class QuaresActivity : Activity() {
44     private var q: Quare = Quare(16, 16, 1)
45     private var resId = 0
46     private var resName = ""
47     private var icon: Icon? = null
48 
49     private lateinit var label: Button
50     private lateinit var grid: GridLayout
51 
52     @Suppress("DEPRECATION")
onCreatenull53     override fun onCreate(savedInstanceState: Bundle?) {
54         super.onCreate(savedInstanceState)
55 
56         window.decorView.systemUiVisibility =
57                 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
58 
59         actionBar?.hide()
60 
61         setContentView(R.layout.activity_quares)
62 
63         grid = findViewById(R.id.grid)
64         label = findViewById(R.id.label)
65 
66         if (savedInstanceState != null) {
67             Log.v(TAG, "restoring puzzle from state")
68             q = savedInstanceState.getParcelable("q") ?: q
69             resId = savedInstanceState.getInt("resId")
70             resName = savedInstanceState.getString("resName", "")
71             loadPuzzle()
72         }
73 
74         label.setOnClickListener { newPuzzle() }
75     }
76 
onResumenull77     override fun onResume() {
78         super.onResume()
79         if (resId == 0) {
80             // lazy init from onCreate
81             newPuzzle()
82         }
83         checkVictory()
84     }
85 
onSaveInstanceStatenull86     override fun onSaveInstanceState(outState: Bundle) {
87         super.onSaveInstanceState(outState)
88 
89         outState.putParcelable("q", q)
90         outState.putInt("resId", resId)
91         outState.putString("resName", resName)
92     }
93 
newPuzzlenull94     fun newPuzzle() {
95         Log.v(TAG, "new puzzle...")
96 
97         q.resetUserMarks()
98         val oldResId = resId
99         resId = android.R.drawable.stat_sys_warning
100         try {
101             for (tries in 0..3) {
102                 val ar = resources.obtainTypedArray(R.array.puzzles)
103                 val newName = ar.getString(Random().nextInt(ar.length()))
104                 if (newName == null) continue
105 
106                 Log.v(TAG, "Looking for icon " + newName)
107 
108                 val pkg = getPackageNameForResourceName(newName)
109                 val newId = packageManager.getResourcesForApplication(pkg)
110                         .getIdentifier(newName, "drawable", pkg)
111                 if (newId == 0) {
112                     Log.v(TAG, "oops, " + newName + " doesn't resolve from pkg " + pkg)
113                 } else if (newId != oldResId) {
114                     // got a good one
115                     resId = newId
116                     resName = newName
117                     break
118                 }
119             }
120         } catch (e: RuntimeException) {
121             Log.v(TAG, "problem loading puzzle, using fallback", e)
122         }
123         loadPuzzle()
124     }
125 
getPackageNameForResourceNamenull126     fun getPackageNameForResourceName(name: String): String {
127         return if (name.contains(":") && !name.startsWith("android:")) {
128             name.substring(0, name.indexOf(":"))
129         } else {
130             packageName
131         }
132     }
133 
checkVictorynull134     fun checkVictory() {
135         if (q.check()) {
136             val dp = resources.displayMetrics.density
137 
138             val label: Button = findViewById(R.id.label)
139             label.text = resName.replace(Regex("^.*/"), "")
140             val drawable = icon?.loadDrawable(this)?.also {
141                 it.setBounds(0, 0, (32 * dp).toInt(), (32 * dp).toInt())
142                 it.setTint(label.currentTextColor)
143             }
144             label.setCompoundDrawables(drawable, null, null, null)
145 
146             label.visibility = VISIBLE
147         } else {
148             label.visibility = GONE
149         }
150     }
151 
loadPuzzlenull152     fun loadPuzzle() {
153         Log.v(TAG, "loading " + resName + " at " + q.width + "x" + q.height)
154 
155         val dp = resources.displayMetrics.density
156 
157         icon = Icon.createWithResource(getPackageNameForResourceName(resName), resId)
158         q.load(this, icon!!)
159 
160         if (q.isBlank()) {
161             // this is a really boring puzzle, let's try again
162             resId = 0
163             resName = ""
164             recreate()
165             return
166         }
167 
168         grid.removeAllViews()
169         grid.columnCount = q.width + 1
170         grid.rowCount = q.height + 1
171 
172         label.visibility = GONE
173 
174         val orientation = resources.configuration.orientation
175 
176         // clean this up a bit
177         val minSide = resources.configuration.smallestScreenWidthDp - 25 // ish
178         val size = (minSide / (q.height + 0.5) * dp).toInt()
179 
180         val sb = StringBuffer()
181 
182         for (j in 0 until grid.rowCount) {
183             for (i in 0 until grid.columnCount) {
184                 val tv: View
185                 val params = GridLayout.LayoutParams().also {
186                     it.width = size
187                     it.height = size
188                     it.setMargins(1, 1, 1, 1)
189                     it.rowSpec = GridLayout.spec(GridLayout.UNDEFINED, GridLayout.TOP) // UGH
190                 }
191                 val x = i - 1
192                 val y = j - 1
193                 if (i > 0 && j > 0) {
194                     if (i == 1 && j > 1) sb.append("\n")
195                     sb.append(if (q.getDataAt(x, y) == 0) " " else "X")
196                     tv = PixelButton(this)
197                     tv.isChecked = q.getUserMark(x, y) != 0
198                     tv.setOnClickListener {
199                         q.setUserMark(x, y, if (tv.isChecked) 0xFF else 0)
200                         val columnCorrect = (grid.getChildAt(i) as? ClueView)?.check(q) ?: false
201                         val rowCorrect = (grid.getChildAt(j*(grid.columnCount)) as? ClueView)
202                                 ?.check(q) ?: false
203                         if (columnCorrect && rowCorrect) {
204                             checkVictory()
205                         } else {
206                             label.visibility = GONE
207                         }
208                     }
209                 } else if (i == j) { // 0,0
210                     tv = View(this)
211                     tv.visibility = GONE
212                 } else {
213                     tv = ClueView(this)
214                     if (j == 0) {
215                         tv.textRotation = 90f
216                         if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
217                             params.height /= 2
218                             tv.showText = false
219                         } else {
220                             params.height = (96 * dp).toInt()
221                         }
222                         if (x >= 0) {
223                             tv.setColumn(q, x)
224                         }
225                     }
226                     if (i == 0) {
227                         if (orientation == Configuration.ORIENTATION_PORTRAIT) {
228                             params.width /= 2
229                             tv.showText = false
230                         } else {
231                             params.width = (96 * dp).toInt()
232                         }
233                         if (y >= 0) {
234                             tv.setRow(q, y)
235                         }
236                     }
237                 }
238                 grid.addView(tv, params)
239             }
240         }
241 
242         Log.v(TAG, "icon: \n" + sb)
243     }
244 }
245 
246 class PixelButton(context: Context) : CompoundButton(context) {
247     init {
248         setBackgroundResource(R.drawable.pixel_bg)
249         isClickable = true
250         isEnabled = true
251     }
252 }
253 
254 class ClueView(context: Context) : View(context) {
255     var row: Int = -1
256     var column: Int = -1
257     var textRotation: Float = 0f
258     var text: CharSequence = ""
259     var showText = true
260     val paint: TextPaint
261     val incorrectColor: Int
262     val correctColor: Int
263 
264     init {
265         setBackgroundColor(0)
<lambda>null266         paint = TextPaint().also {
267             it.textSize = 14f * context.resources.displayMetrics.density
268             it.color = context.getColor(R.color.q_clue_text)
269             it.typeface = Typeface.DEFAULT_BOLD
270             it.textAlign = Paint.Align.CENTER
271         }
272         incorrectColor = context.getColor(R.color.q_clue_bg)
273         correctColor = context.getColor(R.color.q_clue_bg_correct)
274     }
275 
setRownull276     fun setRow(q: Quare, row: Int): Boolean {
277         this.row = row
278         this.column = -1
279         this.textRotation = 0f
280         text = q.getRowClue(row).joinToString("-")
281         return check(q)
282     }
setColumnnull283     fun setColumn(q: Quare, column: Int): Boolean {
284         this.column = column
285         this.row = -1
286         this.textRotation = 90f
287         text = q.getColumnClue(column).joinToString("-")
288         return check(q)
289     }
checknull290     fun check(q: Quare): Boolean {
291         val correct = q.check(column, row)
292         setBackgroundColor(if (correct) correctColor else incorrectColor)
293         return correct
294     }
295 
onDrawnull296     override fun onDraw(canvas: Canvas?) {
297         super.onDraw(canvas)
298         if (!showText) return
299         canvas?.let {
300             val x = canvas.width / 2f
301             val y = canvas.height / 2f
302             var textWidth = canvas.width
303             if (textRotation != 0f) {
304                 canvas.rotate(textRotation, x, y)
305                 textWidth = canvas.height
306             }
307             val textLayout = StaticLayout.Builder.obtain(
308                     text, 0, text.length, paint, textWidth).build()
309             canvas.translate(x, y - textLayout.height / 2)
310             textLayout.draw(canvas)
311         }
312     }
313 }
314