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 onCreatenull52 override fun onCreate(savedInstanceState: Bundle?) { 53 super.onCreate(savedInstanceState) 54 55 window.decorView.systemUiVisibility = 56 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE 57 58 actionBar?.hide() 59 60 setContentView(R.layout.activity_quares) 61 62 grid = findViewById(R.id.grid) 63 label = findViewById(R.id.label) 64 65 if (savedInstanceState != null) { 66 Log.v(TAG, "restoring puzzle from state") 67 q = savedInstanceState.getParcelable("q") ?: q 68 resId = savedInstanceState.getInt("resId") 69 resName = savedInstanceState.getString("resName", "") 70 loadPuzzle() 71 } 72 73 label.setOnClickListener { newPuzzle() } 74 } 75 onResumenull76 override fun onResume() { 77 super.onResume() 78 if (resId == 0) { 79 // lazy init from onCreate 80 newPuzzle() 81 } 82 checkVictory() 83 } 84 onSaveInstanceStatenull85 override fun onSaveInstanceState(outState: Bundle) { 86 super.onSaveInstanceState(outState) 87 88 outState.putParcelable("q", q) 89 outState.putInt("resId", resId) 90 outState.putString("resName", resName) 91 } 92 newPuzzlenull93 fun newPuzzle() { 94 Log.v(TAG, "new puzzle...") 95 96 q.resetUserMarks() 97 val oldResId = resId 98 resId = android.R.drawable.stat_sys_warning 99 try { 100 for (tries in 0..3) { 101 val ar = resources.obtainTypedArray(R.array.puzzles) 102 val newName = ar.getString(Random().nextInt(ar.length())) 103 if (newName == null) continue 104 105 Log.v(TAG, "Looking for icon " + newName) 106 107 val pkg = getPackageNameForResourceName(newName) 108 val newId = packageManager.getResourcesForApplication(pkg) 109 .getIdentifier(newName, "drawable", pkg) 110 if (newId == 0) { 111 Log.v(TAG, "oops, " + newName + " doesn't resolve from pkg " + pkg) 112 } else if (newId != oldResId) { 113 // got a good one 114 resId = newId 115 resName = newName 116 break 117 } 118 } 119 } catch (e: RuntimeException) { 120 Log.v(TAG, "problem loading puzzle, using fallback", e) 121 } 122 loadPuzzle() 123 } 124 getPackageNameForResourceNamenull125 fun getPackageNameForResourceName(name: String): String { 126 return if (name.contains(":") && !name.startsWith("android:")) { 127 name.substring(0, name.indexOf(":")) 128 } else { 129 packageName 130 } 131 } 132 checkVictorynull133 fun checkVictory() { 134 if (q.check()) { 135 val dp = resources.displayMetrics.density 136 137 val label: Button = findViewById(R.id.label) 138 label.text = resName.replace(Regex("^.*/"), "") 139 val drawable = icon?.loadDrawable(this)?.also { 140 it.setBounds(0, 0, (32 * dp).toInt(), (32 * dp).toInt()) 141 it.setTint(label.currentTextColor) 142 } 143 label.setCompoundDrawables(drawable, null, null, null) 144 145 label.visibility = VISIBLE 146 } else { 147 label.visibility = GONE 148 } 149 } 150 loadPuzzlenull151 fun loadPuzzle() { 152 Log.v(TAG, "loading " + resName + " at " + q.width + "x" + q.height) 153 154 val dp = resources.displayMetrics.density 155 156 icon = Icon.createWithResource(getPackageNameForResourceName(resName), resId) 157 q.load(this, icon!!) 158 159 if (q.isBlank()) { 160 // this is a really boring puzzle, let's try again 161 resId = 0 162 resName = "" 163 recreate() 164 return 165 } 166 167 grid.removeAllViews() 168 grid.columnCount = q.width + 1 169 grid.rowCount = q.height + 1 170 171 label.visibility = GONE 172 173 val orientation = resources.configuration.orientation 174 175 // clean this up a bit 176 val minSide = resources.configuration.smallestScreenWidthDp - 25 // ish 177 val size = (minSide / (q.height + 0.5) * dp).toInt() 178 179 val sb = StringBuffer() 180 181 for (j in 0 until grid.rowCount) { 182 for (i in 0 until grid.columnCount) { 183 val tv: View 184 val params = GridLayout.LayoutParams().also { 185 it.width = size 186 it.height = size 187 it.setMargins(1, 1, 1, 1) 188 it.rowSpec = GridLayout.spec(GridLayout.UNDEFINED, GridLayout.TOP) // UGH 189 } 190 val x = i - 1 191 val y = j - 1 192 if (i > 0 && j > 0) { 193 if (i == 1 && j > 1) sb.append("\n") 194 sb.append(if (q.getDataAt(x, y) == 0) " " else "X") 195 tv = PixelButton(this) 196 tv.isChecked = q.getUserMark(x, y) != 0 197 tv.setOnClickListener { 198 q.setUserMark(x, y, if (tv.isChecked) 0xFF else 0) 199 val columnCorrect = (grid.getChildAt(i) as? ClueView)?.check(q) ?: false 200 val rowCorrect = (grid.getChildAt(j*(grid.columnCount)) as? ClueView) 201 ?.check(q) ?: false 202 if (columnCorrect && rowCorrect) { 203 checkVictory() 204 } else { 205 label.visibility = GONE 206 } 207 } 208 } else if (i == j) { // 0,0 209 tv = View(this) 210 tv.visibility = GONE 211 } else { 212 tv = ClueView(this) 213 if (j == 0) { 214 tv.textRotation = 90f 215 if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 216 params.height /= 2 217 tv.showText = false 218 } else { 219 params.height = (96 * dp).toInt() 220 } 221 if (x >= 0) { 222 tv.setColumn(q, x) 223 } 224 } 225 if (i == 0) { 226 if (orientation == Configuration.ORIENTATION_PORTRAIT) { 227 params.width /= 2 228 tv.showText = false 229 } else { 230 params.width = (96 * dp).toInt() 231 } 232 if (y >= 0) { 233 tv.setRow(q, y) 234 } 235 } 236 } 237 grid.addView(tv, params) 238 } 239 } 240 241 Log.v(TAG, "icon: \n" + sb) 242 } 243 } 244 245 class PixelButton(context: Context) : CompoundButton(context) { 246 init { 247 setBackgroundResource(R.drawable.pixel_bg) 248 isClickable = true 249 isEnabled = true 250 } 251 } 252 253 class ClueView(context: Context) : View(context) { 254 var row: Int = -1 255 var column: Int = -1 256 var textRotation: Float = 0f 257 var text: CharSequence = "" 258 var showText = true 259 val paint: TextPaint 260 val incorrectColor: Int 261 val correctColor: Int 262 263 init { 264 setBackgroundColor(0) <lambda>null265 paint = TextPaint().also { 266 it.textSize = 14f * context.resources.displayMetrics.density 267 it.color = context.getColor(R.color.q_clue_text) 268 it.typeface = Typeface.DEFAULT_BOLD 269 it.textAlign = Paint.Align.CENTER 270 } 271 incorrectColor = context.getColor(R.color.q_clue_bg) 272 correctColor = context.getColor(R.color.q_clue_bg_correct) 273 } 274 setRownull275 fun setRow(q: Quare, row: Int): Boolean { 276 this.row = row 277 this.column = -1 278 this.textRotation = 0f 279 text = q.getRowClue(row).joinToString("-") 280 return check(q) 281 } setColumnnull282 fun setColumn(q: Quare, column: Int): Boolean { 283 this.column = column 284 this.row = -1 285 this.textRotation = 90f 286 text = q.getColumnClue(column).joinToString("-") 287 return check(q) 288 } checknull289 fun check(q: Quare): Boolean { 290 val correct = q.check(column, row) 291 setBackgroundColor(if (correct) correctColor else incorrectColor) 292 return correct 293 } 294 onDrawnull295 override fun onDraw(canvas: Canvas?) { 296 super.onDraw(canvas) 297 if (!showText) return 298 canvas?.let { 299 val x = canvas.width / 2f 300 val y = canvas.height / 2f 301 var textWidth = canvas.width 302 if (textRotation != 0f) { 303 canvas.rotate(textRotation, x, y) 304 textWidth = canvas.height 305 } 306 val textLayout = StaticLayout.Builder.obtain( 307 text, 0, text.length, paint, textWidth).build() 308 canvas.translate(x, y - textLayout.height / 2) 309 textLayout.draw(canvas) 310 } 311 } 312 } 313