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