Android O版本 应用锁功能

公司在开发android O版本,分配了一个应用锁功能给我,现在在这里跟大家一起分享

需求:应用锁功能(主要功能是在打开某个app的锁的时候,弹出密码锁)

心路历程:看到这个功能*时间想的是在哪个位置拦截可以挡住所有的app,左思右想决定在Activity.java里面来写,因为所有的Activity肯定会继承这个类。想通了这一点其他的也就一步步迎刃而解。

主要逻辑:Activity onResume 的时候去get当前的包名,然后跟存在数据库里面加锁的包名遍历对比,如果有包含在数据库里,即弹出密码界面,如果没有,正常打开Activity。

主要代码:frameworks\base\core\java\android\app\Activity.java

//add by illa for applock
KeyguardManager mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
Intent intentKeyguard = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null);

String lastPackageName = Settings.System.getString(getContentResolver(), “lastName”);
android.util.Log.d(“illa”,”onResume!”+”\ngetPackageName() = “+getPackageName()+”\tlastPackageName = “+lastPackageName);
android.util.Log.d(“illa”,”onResume!”+”\ngetClassName = “+getComponentName().getClassName());

List<String> lockPackageName = new ArrayList<String>();
lockPackageName = getLockedPkgs();

/*android.util.Log.d(“illa”,”List<String> size = “+lockPackageName.size());
android.util.Log.i(“illa”, “lockPackageName.size() > 0 = ” + (lockPackageName.size() > 0));
android.util.Log.i(“illa”, “getComponentName().getClassName() = ” + (getComponentName().getClassName().equals(“com.android.settings.applock.SetLockApp”)));
android.util.Log.i(“illa”, “(intentKeyguard != null) = ” + (intentKeyguard != null));
android.util.Log.i(“illa”, “(lastPackageName == null) = ” + (lastPackageName == null));
android.util.Log.i(“illa”, “mKeyguardManager.inKeyguardRestrictedInputMode = ” + !mKeyguardManager.inKeyguardRestrictedInputMode());*/
if((lockPackageName.size() > 0) && (getComponentName().getClassName().equals(“com.android.settings.applock.SetLockApp”))
&& (intentKeyguard != null) &&(lastPackageName == null)&& !mKeyguardManager.inKeyguardRestrictedInputMode()){ //

intentKeyguard.putExtra(“packagename”, getPackageName());
intentKeyguard.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intentKeyguard.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(intentKeyguard, 11111);

android.util.Log.i(“illa”, “sendBroadcast(intent)”);
}
/*android.util.Log.i(“illa”, “lockPackageName.contains(getPackageName()) = ” + lockPackageName.contains(getPackageName()));
android.util.Log.i(“illa”, “intentKeyguard != null = ” + (intentKeyguard != null));
android.util.Log.i(“illa”, “lastPackageName == null = ” + (lastPackageName == null));
android.util.Log.i(“illa”, “equals InCallActivity = ” + !getComponentName().getClassName().equals(“com.android.incallui.InCallActivity”));
android.util.Log.i(“illa”, “!mKeyguardManager.inKeyguardRestrictedInputMode()) = ” + !mKeyguardManager.inKeyguardRestrictedInputMode());*/
if (lockPackageName.contains(getPackageName()) && intentKeyguard != null &&
lastPackageName == null && !getComponentName().getClassName().equals(“com.android.incallui.InCallActivity”)
&& !mKeyguardManager.inKeyguardRestrictedInputMode()) {

intentKeyguard.putExtra(“packagename”, getPackageName());
intentKeyguard.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intentKeyguard.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(intentKeyguard, 11111);

android.util.Log.i(“illa”, “sendBroadcast(intent)”);
}

String launcherPackagename = getLauncherPackageName(this);
if(launcherPackagename != null) {
if(getPackageName().equals(launcherPackagename)) {
//Settings.System.putString(getContentResolver(), “lastName”, null);
Intent intent = new Intent(“fprint.receiver.update.last_package_name”);
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
intent.setPackage(“com.android.settings”);
sendBroadcastAsUser(intent, UserHandle.SYSTEM);
android.util.Log.i(“illa”, “last name = null”);
}

数据库那块的代码 就不贴了, 后续补充如何写数据库,

这里用到了一个内容提供者读取数据库代码如下

private Uri uri = Uri.parse(“content://com.android.settings.lockapp_provider/lockapp”);
private String COLUMN_PKGNAME = “pkgname”;
private String COLUMN_ISLOCKED = “islocked”;

private ArrayList<String> getLockedPkgs(){
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(uri, null, null, null, null);
ArrayList<String>lockPkgs = new ArrayList<String>();

if(cursor != null){
// cursor.moveToFirst();
int pkg_index = cursor.getColumnIndex(COLUMN_PKGNAME);
int lock_index = cursor.getColumnIndex(COLUMN_ISLOCKED);
while (cursor.moveToNext()) {
String pkgName = cursor.getString(pkg_index);
String isLocked = cursor.getString(lock_index);
Log.e(“illa”, “pkgName->”+pkgName + ” || isLocked:” + isLocked);
if(“1”.equals(isLocked)){
lockPkgs.add(pkgName);
}
}
cursor.close();
}
return lockPkgs;
}

今天就到这里,希望以上代码可以对阅读者有所启发。我们来日方长!

Android开发之应用锁解锁方式:指纹和密码

先上效果图:
主要实现的是通过自定义密码或者指纹,给应用锁进行解锁,然后进入APP。

%title插图%num %title插图%num
主要代码:LoginActivity.java
package com.fyang21117.rdiot1.second;

import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.fyang21117.rdiot1.FingerprintUtil;
import com.fyang21117.rdiot1.KeyguardLockScreenManager;
import com.fyang21117.rdiot1.MainActivity;
import com.fyang21117.rdiot1.R;
import com.fyang21117.rdiot1.core.FingerprintCore;

public class LoginActivity extends AppCompatActivity implements View.OnClickListener {

private FingerprintCore mFingerprintCore;
private KeyguardLockScreenManager mKeyguardLockScreenManager;//指纹管理
private Toast mToast;
private Handler mHandler = new Handler(Looper.getMainLooper());//主线程
private EditText editText;
private String string;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
setTitle(“应用锁”);

initFingerprintCore();

Button psw_login = findViewById(R.id.psw_login);
Button use_fingerprint = findViewById(R.id.use_fingerprint);
editText = findViewById(R.id.unlock_num);
psw_login.setOnClickListener(this);
use_fingerprint.setOnClickListener(this);
}

@Override
public void onClick(View v) {
final int viewId = v.getId();
switch (viewId) {
case R.id.use_fingerprint:
//调用非活动布局的控件
View view= getLayoutInflater().inflate(R.layout.fingerprint_dialog, null);
final MyDialog mMyDialog= new MyDialog(this, 0, 0, view, R.style.DialogTheme);
Button cancel_fingerprint = view.findViewById(R.id.cancel_unlock);
mMyDialog.setCancelable(true);
mMyDialog.show();
startFingerprintRecognition();
cancel_fingerprint.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mMyDialog.dismiss();
}
});
break;
case R.id.psw_login:
string =editText.getText().toString();
if(string.equals(“1028″))
MainActivity.actionStart(this);
else
Toast.makeText(this,”Unlocking failed!Please input again!”,Toast.LENGTH_SHORT).show();
break;
}
}
/*** 指纹识别初始化*/
private void initFingerprintCore() {
mFingerprintCore = new FingerprintCore(this);
mFingerprintCore.setFingerprintManager(mResultListener);
mKeyguardLockScreenManager = new KeyguardLockScreenManager(this);
}

/*** 进入系统设置指纹界面*/
private void enterSysFingerprintSettingPage() {
FingerprintUtil.openFingerPrintSettingPage(this);
}
private void startFingerprintRecognitionUnlockScreen() {
if (mKeyguardLockScreenManager == null) {
return;
}
if (!mKeyguardLockScreenManager.isOpenLockScreenPwd()) {
FingerprintUtil.openFingerPrintSettingPage(this);
return;
}
mKeyguardLockScreenManager.showAuthenticationScreen(this);
}

/*** 开始指纹识别*/
private void startFingerprintRecognition() {
if (mFingerprintCore.isSupport()) {
if (!mFingerprintCore.isHasEnrolledFingerprints()) {
toastTipMsg(R.string.fingerprint_recognition_not_enrolled);
FingerprintUtil.openFingerPrintSettingPage(this);
return;
}

toastTipMsg(R.string.fingerprint_recognition_tip);
if (mFingerprintCore.isAuthenticating()) {
toastTipMsg(R.string.fingerprint_recognition_authenticating);
} else {
mFingerprintCore.startAuthenticate();
}
} else {
toastTipMsg(R.string.fingerprint_recognition_not_support);
}
}

private void IntoActivity() {
MainActivity.actionStart(this);//识别成功
}

private FingerprintCore.IFingerprintResultListener mResultListener
= new FingerprintCore.IFingerprintResultListener() {
@Override
public void onAuthenticateSuccess() {
toastTipMsg(R.string.fingerprint_recognition_success);
IntoActivity();
}
@Override
public void onAuthenticateFailed(int helpId) {
toastTipMsg(R.string.fingerprint_recognition_failed);
}
@Override
public void onAuthenticateError(int errMsgId) {
toastTipMsg(R.string.fingerprint_recognition_error);
}
@Override
public void onStartAuthenticateResult(boolean isSuccess) {
}
};

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == KeyguardLockScreenManager.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) {
// Challenge completed, proceed with using cipher
if (resultCode == RESULT_OK) {
toastTipMsg(R.string.sys_pwd_recognition_success);
} else {
toastTipMsg(R.string.sys_pwd_recognition_failed);
}
}
}

private void toastTipMsg(int messageId) {
if (mToast == null) {
mToast = Toast.makeText(this, messageId, Toast.LENGTH_SHORT);
}
mToast.setText(messageId);
mToast.cancel();
mHandler.removeCallbacks(mShowToastRunnable);
mHandler.postDelayed(mShowToastRunnable, 0);
}

private void toastTipMsg(String message) {
if (mToast == null) {
mToast = Toast.makeText(this, message, Toast.LENGTH_LONG);
}
mToast.setText(message);
mToast.cancel();
mHandler.removeCallbacks(mShowToastRunnable);
mHandler.postDelayed(mShowToastRunnable, 200);
}

private Runnable mShowToastRunnable = new Runnable() {
@Override
public void run() {
mToast.show();
}
};

@Override
protected void onDestroy() {
if (mFingerprintCore != null) {
mFingerprintCore.onDestroy();
mFingerprintCore = null;
}
if (mKeyguardLockScreenManager != null) {
mKeyguardLockScreenManager.onDestroy();
mKeyguardLockScreenManager = null;
}
mResultListener = null;
mShowToastRunnable = null;
mToast = null;
super.onDestroy();
}
}
activity_login.xml
<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout
xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”>

<LinearLayout
android:id=”@+id/digital_layout”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
android:layout_gravity=”center_vertical”
android:gravity=”center_vertical”>

<EditText
android:id=”@+id/unlock_num”
android:layout_width=”270dp”
android:layout_height=”wrap_content”
android:layout_gravity=”center_horizontal”
android:gravity=”center_horizontal”
android:inputType=”number”
android:hint=”input your unlocking nums:”
android:textAllCaps=”false”/>
<Button
android:id=”@+id/psw_login”
android:layout_width=”270dp”
android:layout_height=”wrap_content”
android:layout_gravity=”center_horizontal”
android:gravity=”center_horizontal”
android:text=”@string/login2″
android:textAllCaps=”false”
android:textSize=”20sp”/>
<Button
android:id=”@+id/use_fingerprint”
android:layout_width=”270dp”
android:layout_height=”wrap_content”
android:layout_gravity=”center_horizontal”
android:gravity=”center_horizontal”
android:text=”@string/use_fingerprint”
android:textAllCaps=”false”
android:textSize=”20sp”/>
</LinearLayout>

</LinearLayout>
解锁只是 个人工程的一部分,具体可参考下面链接:
完整代码见:https://github.com/fyang21117/RDIOT1

安卓开发之应用锁

安卓应用锁在各大应用商店都有很多类似的产品,所以关于其功能我就不再过多的介绍了。

现在,我们来简单说说应用锁核心功能的实现原理:

首先,需要一个Service来实时监听当前运行的程序,判断是否为选中需要锁定的程序。这里需要设置一个时间间隔,不然会加重CPU的负担,增加耗电量。
如果当前正在显示的程序是被锁定的程序,那么我们需要弹出锁定窗口。让用户输入密码,这里应当注意:需要重写Back方法,实现返回桌面的功能。
如果输入密码正确,那么直接finish这个Activity就可以实现进入被锁定的应用了。如果密码错误,可作相应的提示,要求重新输入。
我们来分别讲解每个步骤如何实现的。

1、监听程序,需要用到ActivityManager当中的方法。代码如下:
<span style=”white-space:pre”> </span>ActivityManager mActivityManager;
mActivityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
ComponentName topActivity = mActivityManager.getRunningTasks(1).get(0).topActivity;
String packageName = topActivity.getPackageName();
通过上面的代码就可以获取到当前界面所属的应用的packageName。然后判断该应用是否属于被锁定的应用。
if(lockName.get(i).equals(packageName)){
return true;
}
2、通过一个boolean变量isUnlockActivity来判断当前应用是否已经解锁了。如果已经解锁了,那么不用再输入密码。可以直接进入。
if(isLockName() && !isUnLockActivity){

<span style=”white-space:pre”> </span>//调用了解锁界面之后,需要设置一下isUnLockActivity的值 <pre name=”code” class=”java”><span style=”white-space:pre”> </span>isUnLockActivity = true;
Intent intent = new Intent(LockServ.this,UnLockActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); }
至于,Back方法的重写,可以在重写里面直接调用Home键的函数即可。这样,Back键也可以直接回到桌面。(这样虽然方便,但是在某些场合会造成用户的困惑,如果用户正在程序内调用某个被保护的应用,那么此时,返回键应当回到当前应用中,而不应该是桌面)

3、*开始我在处理怎么进入解锁的应用中的时候,不知道怎么处理。想过重新调用该应用,但是处理起来很复杂。后来,干脆直接finish当前的Activity。根据安卓的机制,就会自动回到上一个Activity当中来。这样是能成功的。但是在某些情况下,会产生一些Bug,不能正确的进入应用。可能和这个有关系,后期应当在这上面做一些改进。

完美运行的应用锁Demo源码

Android应用锁实现

为了不让别人查看隐私,我们经常会对各种应用进行加锁。目前应用市场上应用锁有很多,各种安全类软件都有这个功能。

对于第三方软件,其实现应用锁的基本原理就是不断的轮询栈顶的应用包名,如果是在加锁名单里的应用,则会弹出解锁界面。这种方式的缺点比较明显,应用实际上已经启动起来了,只是强行在上面又盖了一层界面,同时也避免不了会先看到应用的界面然后才弹出解锁界面的问题。具体实现可查看http://blog.csdn.net/turkeycock/article/details/50521103

如果有root权限,就可以避免该这些问题。下面看下具有系统权限的app的应用锁实现方案。

应用锁从用户的角度看是对应用进行加锁,但是从系统的角度看,其实是对activity的拦截。

其实现原理比较简单,在startActivity的过程中,我们注意到

frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java
final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container,
TaskRecord inTask) {

boolean abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho,
requestCode, callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp,
resultRecord, resultStack, options);
abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
callingPid, resolvedType, aInfo.applicationInfo);

if (mService.mController != null) {
try {
// The Intent we give to the watcher has the extra data
// stripped off, since it can contain private information.
Intent watchIntent = intent.cloneFilter();
abort |= !mService.mController.activityStarting(watchIntent,
aInfo.applicationInfo.packageName);
} catch (RemoteException e) {
mService.mController = null;
}
}


if (abort) {
if (resultRecord != null) {
resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode,
RESULT_CANCELED, null);
}
// We pretend to the caller that it was really started, but
// they will just get a cancel result.
ActivityOptions.abort(options);
return START_SUCCESS;
}

}
这里有个abort变量,如果其为true,那么就直接返回了,不会再去继续startActivity的流程。
看下赋值的地方

abort |= !mService.mController.activityStarting(watchIntent,
aInfo.applicationInfo.packageName);

调用了ams的mController的activityStarting方法,该方法返回是否拦截activity的启动。
mController是IActivityController类型的对象,通过IActivityController实现对activity的拦截。
因此只要mController的activityStarting返回false,abor就会t设置为true,activity将不会启动,这时我们可以启动加锁页面,如果用户输入了正确的密码,再去把要启动的activity启动起来,达到应用锁的目的。

IActivityController.aidl
就是通过这个aidl文件来实现系统层的应用锁功能,看下该文件的具体内容
frameworks/base/core/java/android/app/IActivityController.java
/**
* Testing interface to monitor what is happening in the activity manager
* while tests are running.  Not for normal application development.
* {@hide}
*/
interface IActivityController
{
/**
* The system is trying to start an activity.  Return true to allow
* it to be started as normal, or false to cancel/reject this activity.
*/
boolean activityStarting(in Intent intent, String pkg);

/**
* The system is trying to return to an activity.  Return true to allow
* it to be resumed as normal, or false to cancel/reject this activity.
*/
boolean activityResuming(String pkg);

/**
* An application process has crashed (in Java).  Return true for the
* normal error recovery (app crash dialog) to occur, false to kill
* it immediately.
*/
boolean appCrashed(String processName, int pid,
String shortMsg, String longMsg,
long timeMillis, String stackTrace);

/**
* Early call as soon as an ANR is detected.
*/
int appEarlyNotResponding(String processName, int pid, String annotation);

/**
* An application process is not responding.  Return 0 to show the “app
* not responding” dialog, 1 to continue waiting, or -1 to kill it
* immediately.
*/
int appNotResponding(String processName, int pid, String processStats);

/**
* The system process watchdog has detected that the system seems to be
* hung.  Return 1 to continue waiting, or -1 to let it continue with its
* normal kill.
*/
int systemNotResponding(String msg);
}

这里提供了一系列的方法去监听应用状态,包括activity的启动,resume,无响应等。该文件时hide的,不对第三方应用开放。应用锁主要关注
boolean activityStarting(in Intent intent, String pkg);
pkg是要启动的activity的包名,在这个方法里我们可以去判断这个pkg是否需要加锁,如果需要则返回false拦截该activity的启动,如果不需要加锁则返回true继续startActivity的过程。

ams中也提供了设置该监听器的方法

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public void setActivityController(IActivityController controller, boolean imAMonkey) {
enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
“setActivityController()”);
synchronized (this) {
mController = controller;
mControllerIsAMonkey = imAMonkey;
Watchdog.getInstance().setActivityController(controller);
}
}
通过IActivityController可以实现真正的应用锁。

 

2021年5月上旬流通领域重要生产资料市场价格变动情况

中国统计信息服务中心 卓创资讯   据对全国流通领域9大类50种重要生产资料市场价格的监测显示,2021年5月上旬与4月下旬相比,42种产品价格上涨,7种下降,1种持平。 2021年5月上旬流通领域重要生产资料市场价格变动情况  产品名称 单位 本期价格(元) 比上期 价格涨跌(元) 涨跌幅 (%) 一、黑色金属         螺纹钢(Φ16-25mm,HRB400E) 吨 5684.5 565.3 11.0 线材(Φ6.5mm,HPB300) 吨 5929.1 577.0 10.8 普通中板(20mm,Q235) 吨 6121.2 471.5 8.3 热轧普通薄板(3mm,Q235) 吨 6293.1 498.6 8.6 无缝钢管(219*6,20#) 吨 6162.2 245.9 4.2 角钢(5#) 吨 5766.9 301.7 5.5 二、有色金属         电解铜(1#) 吨 74607.7 4201.7 6.0 铝锭(A00) 吨 19656.1 1242.7 6.7 铅锭(1#) 吨 15410.0 254.0 1.7 锌锭(0#) 吨 22303.3 540.8 2.5 三、化工产品         硫酸(98%) 吨 575.0 11.9 2.1 烧碱(液碱,32%) 吨 490.3 -3.0 -0.6 甲醇(优等品) 吨 2493.5 59.6 2.4 纯苯(石油苯,工业级) 吨 7866.7 575.7 7.9 苯乙烯(一级品) 吨 10364.3 212.8 2.1 聚乙烯(LLDPE,7042) 吨 8623.8 121.3 1.4 聚丙烯(T30S) 吨 9162.8 112.1 1.2 聚氯乙烯(SG5) 吨 9381.7 357.5 4.0 顺丁胶(BR9000) 吨 12088.3 -9.8 -0.1 涤纶长丝(FDY150D/96F) 吨 7466.7 -133.3 -1.8 四、石油天然气         液化天然气(LNG) 吨 3329.1 58.5 1.8 液化石油气(LPG) 吨 4265.0 48.6 1.2 汽油(95#国VI) 吨 7979.9 51.7 0.7 汽油(92#国VI) 吨 7746.3 47.4 0.6 柴油(0#国VI) 吨 6133.7 23.4 0.4 石蜡(58#半) 吨 7233.3 8.3 0.1 五、煤炭         无烟煤(洗中块) 吨 1000.0 100.0 11.1 普通混煤(4500大卡) 吨 681.7 53.6 8.5 山西大混(5000大卡) 吨 801.7 87.9 12.3 山西优混(5500大卡) 吨 861.7 56.7 7.0 大同混煤(5800大卡) 吨 886.7 56.7 6.8 焦煤(主焦煤) 吨 1668.8 112.1 7.2 焦炭(二级冶金焦) 吨 2376.8 257.0 12.1 六、非金属建材         普通硅酸盐水泥(P.O 42.5袋装) 吨 487.9 8.4 1.8 普通硅酸盐水泥(P.O 42.5散装) 吨 448.9 5.0 1.1 浮法平板玻璃(4.8/5mm) 吨 2430.0 56.0 2.4 七、农产品(主要用于加工)         稻米(粳稻米) 吨 4021.2 -5.6 -0.1 小麦(国标三等) 吨 2530.5 -4.9 -0.2 玉米(黄玉米二等) 吨 2818.4 15.1 0.5 棉花(皮棉,白棉三级) 吨 16298.7 309.9 1.9 生猪(外三元) 千克 20.1 -2.7 -11.8 大豆(黄豆) 吨 5253.3 37.0 0.7 豆粕(粗蛋白含量≥43%) 吨 3631.7 114.9 3.3 花生(油料花生米) 吨 8805.6 -2.7 0.0 八、农业生产资料         尿素(小颗料) 吨 2226.7 15.8 0.7 复合肥(硫酸钾复合肥,氮磷钾含量45%) 吨 2512.5 25.0 1.0 农药(草甘膦,95%原药) 吨 37833.3 2583.3 7.3 九、林产品         天然橡胶(标准胶SCRWF) 吨 13917.3 497.6 3.7 纸浆(漂白化学浆) 吨 5955.0 -12.5 -0.2 瓦楞纸(高强) 吨 3829.8 16.0 0.4 注:上期为2021年4月下旬。    附注   1.指标解释   流通领域重要生产资料市场价格,是指重要生产资料经营企业的批发和销售价格。与出厂价格不同,生产资料市场价格既包含出厂价格,也包含有经营企业的流通费用、利润和税费等。出厂价格与市场价格互相影响,存在时滞,两者的变动趋势在某一时间段内有可能会出现不完全一致的情况。   2.监测内容   流通领域重要生产资料市场价格监测内容包括9大类50种产品的价格。类别与产品规格说明详见附表。   3.监测范围   监测范围涵盖全国31个省(区、市)300多个交易市场的近2000家批发商、代理商、经销商等经营企业。   4.监测方法   价格监测方法包括信息员现场采价,电话、即时通讯工具和电子邮件询价等。   5.涨跌个数的统计   产品价格上涨、下降、持平个数按照涨跌幅(%)进行统计。   6.发布日期   每月4日、14日、24日发布上一旬数据,节假日顺延。 附表:流通领域重要生产资料市场价格监测产品规格说明表  序号 监测产品 规格型号 说明   一、黑色金属      1   螺纹钢 Φ16-25mm,HRB400E 屈服强度≥400MPa  2 线材 Φ6.5mm,HPB300 屈服强度≥300MPa  3 普通中板 20mm,Q235 屈服强度≥235MPa  4 热轧普通薄板 3mm,Q235 屈服强度≥235MPa  5 无缝钢管 219*6,20# 20#钢材,屈服强度≥245MPa  6 角钢 5# 屈服强度≥235MPa   二、有色金属      7 电解铜 1# 铜与银质量分数≥99.95%  8 铝锭 A00 铝质量分数≥99.7%  9 铅锭 1# 铅质量分数≥99.994% 10 锌锭 0# 锌质量分数≥99.995%   三、化工产品     11  硫酸 98% H2SO4质量分数≥98% 12 烧碱(液碱) 32% NaOH质量分数≥32%的离子膜碱 13 甲醇 优等品 水质量含量≤0.10% 14 纯苯(石油苯) 工业级 苯纯度≥99.8% 15 苯乙烯 一级品 纯度≥99.5% 16 聚乙烯(LLDPE) 7042 熔指:2.0±0.5g/10min 17 聚丙烯 T30S 熔指:3.0±0.9g/10min 18 聚氯乙烯 SG5 K值:66-68 19 顺丁胶 BR9000 块状、乳白色,灰分≤0.20% 20 涤纶长丝 FDY150D/96F 150旦,AA级   四、石油天然气     21 液化天然气 LNG 甲烷含量≥75%,密度≥430kg/m3 22 液化石油气 LPG 饱和蒸汽压1380-1430kPa 23 汽油 95#国VI 国VI标准 24 汽油 92#国VI 国VI标准 25 柴油 0#国VI 国VI标准 26 石蜡 58#半 熔点不低于58℃   五、煤炭     27 无烟煤 洗中块 挥发分≤8% 28 普通混煤 4500大卡 山西粉煤与块煤的混合煤,热值4500大卡 29 山西大混 5000大卡 质量较好的混煤,热值5000大卡 30 山西优混 5500大卡 优质的混煤,热值5500大卡 31 大同混煤 5800大卡 大同产混煤,热值5800大卡 32 焦煤  主焦煤 含硫量<1% 33 焦炭 二级冶金焦 12.01%≤灰分≤13.50%   六、非金属建材     34 普通硅酸盐水泥 P.O 42.5袋装 抗压强度42.5MPa 35 普通硅酸盐水泥 P.O 42.5散装 抗压强度42.5MPa 36 浮法平板玻璃 4.8/5mm 厚度为4.8/5mm的无色透明玻璃   七、农产品(主要用于加工)     37 稻米 粳稻米 杂质≤0.25%,水分≤15.5% 38 小麦 国标三等 杂质≤1.0%,水分≤12.5% 39 玉米 黄玉米二等 杂质≤1.0%,水分≤14.0% 40 棉花(皮棉) 白棉三级 纤维长度≥28mm,白或乳白色 41 生猪 外三元 三种外国猪杂交的肉食猪 42 大豆 黄豆 杂质≤1.0%,水分≤13.0% 43 豆粕 粗蛋白含量≥43% 粗蛋白≥43%,水分≤13.0% 44 花生 油料花生米 杂质≤1.0%,水分≤9.0%   八、农业生产资料     45 尿素 小颗料 总氮≥46%,水分≤1.0% 46 复合肥 硫酸钾复合肥 氮磷钾含量45% 47 农药(草甘膦) 95%原药 草甘膦质量分数≥95%   九、林产品     48 天然橡胶 标准胶SCRWF 杂质含量≤0.05%,灰分≤0.5% 49 纸浆 漂白化学浆 亮度≥80%,黏度≥600cm³/g 50 瓦楞纸 高强 80-160g/m2  

钉钉自定义机器人发送消息SDK

钉钉自定义机器人发送消息SDK

钉钉自定义机器人发送消息SDK
当需要将系统的一些预警等信息推送到钉钉群时,我们可以通过开启自定义机器人来通过API接口推送钉钉消息。

使用说明
当前自定义机器人支持文本 (text)、链接 (link)、markdown(markdown)、ActionCard、FeedCard消息类型; 每个机器人每分钟*多发送20条。消息发送太频繁会严重影响群成员的使用体验,大量发消息的场景 (譬如系统监控报警) 可以将这些信息进行整合,通过markdown消息以摘要的形式发送到群里。

发送普通消息
@Test
public void textMessage() throws ApiException {
//这个是通过钉钉获取的机器人的连接,需要PC版才可以
DingTalkClient client = new DefaultDingTalkClient(“https://oapi.dingtalk.com/robot/send?access_token=2324980345ufnklasjnfaiopuw3rnq9o32874yq29035ryhvq2”);
OapiRobotSendRequest request = new OapiRobotSendRequest();
request.setMsgtype(“text”);
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
text.setContent(“测试文本消息”);
request.setText(text);

OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
at.setIsAtAll(“true”);//设置@所有的人
request.setAt(at);
OapiRobotSendResponse response = client.execute(request);
System.out.println(response.getErrcode());
}

发送link消息
@Test
public void linkMessage() throws ApiException {
DingTalkClient client = new DefaultDingTalkClient(“https://oapi.dingtalk.com/robot/send?access_token=2324980345ufnklasjnfaiopuw3rnq9o32874yq29035ryhvq2”);
OapiRobotSendRequest request = new OapiRobotSendRequest();
request.setMsgtype(“link”);
OapiRobotSendRequest.Link link = new OapiRobotSendRequest.Link();
link.setMessageUrl(“https://www.dingtalk.com/”);
link.setPicUrl(“”);
link.setTitle(“时代的火车向前开”);
link.setText(“这个即将发布的新版本,创始人陈航(花名“无招”)称它为“红树林”。\n” +
“而在此之前,每当面临重大升级,产品经理们都会取一个应景的代号,这一次,为什么是“红树林”);
request.setLink(link);

OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
at.setAtMobiles(Arrays.asList(“13672384534”));//设置@那些人,使用的是手机号
request.setAt(at);
OapiRobotSendResponse response = client.execute(request);
System.out.println(response.getErrcode());
}

发送markdown消息

%title插图%num
@Test
public void markdownMessage() throws ApiException {
DingTalkClient client = new DefaultDingTalkClient(“https://oapi.dingtalk.com/robot/send?access_token=2324980345ufnklasjnfaiopuw3rnq9o32874yq29035ryhvq2”);
OapiRobotSendRequest request = new OapiRobotSendRequest();
request.setMsgtype(“markdown”);
OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown();
markdown.setTitle(“杭州天气”);
markdown.setText(“#### 杭州天气 @156xxxx8827\n” +
“> 9度,西北风1级,空气良89,相对温度73%\n\n” +
“> ![screenshot](https://img-blog.csdnimg.cn/img_convert/d7945a05864dde53f98bb3d309082d7e.png)\n” +
“> ###### 10点20分发布 [天气](http://www.thinkpage.cn/) \n”);
request.setMarkdown(markdown);
OapiRobotSendResponse response = client.execute(request);
System.out.println(response.getErrcode());
System.out.println(response.getErrmsg());
}

数据库|sql视图介绍

数据库|sql视图介绍

视图(View)是一种虚拟存在的表,对于使用视图的用户来说基本上是透明的。视图并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。

定义视图
create view 视图名称 as select 语句

SELECT * FROM provinces;

CREATE VIEW v_pro AS SELECT * FROM provinces;

SELECT * FROM v_pro;

查看视图
查看表的时候会把视图表也列出来

show tables;

使用视图
select * from v_pro;

删除视图
drop view 视图名称;

视图的作用
简单:提高了重用性,就像一个函数。
安全:提高了安全性能,可以针对不同的用户,设定不同的视图。
数据独立:一旦视图的结构确定了,可以屏蔽表结构变化对用户的影响,源表增加列对视图没有影响;源表修改列名,则可以通过修改视图来解决,不会造成对访问者的影响
视图的修改
有下列内容之一,视图不能做修改

select子句中包含distinct
select字句中包含组函数
select语句中包含group by子句
selecy语句红包含order by子句
where子句中包含相关子查询
from字句中包含多个表
如果视图中有计算列,则不能更新
如果基表中有某个具有非空约束的列未出现在视图定义中,则不能做insert操作。

Python课程包括哪些内容

python课程介绍的主要内容有哪些_Python课程包括哪些内容?

从职友集之前Python招聘岗位需求来看,Python工程师的岗位需求量巨大,并且岗位需求量还在上涨。尤其是在北京、上海等一线城市,而深圳、杭州、广州等一线城市更是占比16.53%,可见现在Python人才缺口很大。那么,Python课程包括哪些内容?

Python课程共分为八大阶段:阶段一是Python语言(用时5周,包括基础语法、面向对象、高级课程、经典课程);阶段二是Linux初级(用时1周,包括Linux系统基本指令、常用服务安装);

阶段三是Web开发之Diango(5周+2周前端+3周diango);阶段四是Web开发之Flask(用时2周);阶段五是Web框架之Tornado(用时1周);阶段六是docker容器及服务发现(用时2周);阶段七是爬虫(用时2周);阶段八是数据挖掘和人工智能(用时3周)。

%title插图%num

以上是某Python培训中心的课程的主要内容。Python 今年 28 岁了。尽管它比我的许多读者年纪还要大,但是仍然受到高度的关注,因为它可以被应用于如今你所能想得到的相当多的软件开发和操作场景。

要管理本地或者云基础设施吗?Python可以。开发网站?OK,它也能行的。需要处理一个 SQL 数据库?可以。需要为 Hive 或者 Pig 定制一个功能?能做到。只是想为自己构建一个小工具?Python 就是*好的选择。需要一门支持面向对象设计的语言?Python 的特性就能满足啦。

简而言之,将Python了解得更加深入一点点,就能让你具备可以适应范围更宽泛的工作角色的技能。如果你也想加入到这个行业,一定要抓紧时间去学习。

Python 5 行代码的神奇操作!

Python 5 行代码的神奇操作!

 

Python 语言实现功能直接了当,简明扼要,今天咱们就来一起看看 Python 5 行代码的神奇操作!

我能行
1、古典兔子问题
有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?

def count(n):
if (1 == n or 2 == n):
return 1
elif (n >= 2):
return count(n – 2) + count(n – 1)
print(count(36) * 2)

2、加法计算器
num1 = input(“*个数:”)
num2 = input(“第二个数:”)
new_num1 = int(num1)
new_num2 = int(num2)
print(new_num1 + new_num2)
3、循环问答
while(True):
question = input()
answer = question.replace(‘吗’, ‘呢’)
answer = answer.replace(‘?’, ‘!’)
print(answer)
输出:

在吗
在呢
吃饭了吗
吃饭了呢
要下班了吗
要下班了呢
*近好吗
*近好呢
4、实现一个简单的服务器
from http import server
from http.server import SimpleHTTPRequestHandler
server_address = (‘127.0.0.1’, 8888)
httpd = server.HTTPServer(server_address, SimpleHTTPRequestHandler)
httpd.serve_forever()
5、九九乘法表1
for i in range(1, 10):
for j in range(1, i+1):
print(‘{}x{}={}\t’.format(j, i, i*j), end=”)
print()
输出:

1×1=1
1×2=2 2×2=4
1×3=3 2×3=6 3×3=9
1×4=4 2×4=8 3×4=12 4×4=16
1×5=5 2×5=10 3×5=15 4×5=20 5×5=25
1×6=6 2×6=12 3×6=18 4×6=24 5×6=30 6×6=36
1×7=7 2×7=14 3×7=21 4×7=28 5×7=35 6×7=42 7×7=49
1×8=8 2×8=16 3×8=24 4×8=32 5×8=40 6×8=48 7×8=56 8×8=64
1×9=9 2×9=18 3×9=27 4×9=36 5×9=45 6×9=54 7×9=63 8×9=72 9×9=81
6、九九乘法表2
for i in range(1, 10):
for j in range(i, 10):
print(f'{i}x{j}={i*j}’,end=’\t’)
print(” “)
print(“\n”)
输出:

1×1=1 1×2=2 1×3=3 1×4=4 1×5=5 1×6=6 1×7=7 1×8=8 1×9=9
2×2=4 2×3=6 2×4=8 2×5=10 2×6=12 2×7=14 2×8=16 2×9=18
3×3=9 3×4=12 3×5=15 3×6=18 3×7=21 3×8=24 3×9=27
4×4=16 4×5=20 4×6=24 4×7=28 4×8=32 4×9=36
5×5=25 5×6=30 5×7=35 5×8=40 5×9=45
6×6=36 6×7=42 6×8=48 6×9=54
7×7=49 7×8=56 7×9=63
8×8=64 8×9=72
9×9=81
7、逆序打印数字
给一个不多于5位的正整数,逆序打印出各位数字,实现思路如下:

def nixu(n):
l = str(n)
l_str = l[::-1]
print(“逆序:%s” % ( l_str))
nixu(2020)
输出:

逆序:0202
8、生成词云
from wordcloud import WordCloud
import PIL.Image as image
with open(‘wordcloud.txt’) as fp:
text = fp.read()
wordcloud = WordCloud().generate(text)
img = wordcloud.to_image()
img.show()

词云
9、快速生成二维码
以百度为例,生成二维码

from MyQR import myqr
myqr.run(
words=’https://www.baidu.com/’,
colorized=True,
save_name=’baidu_code.png’)

百度二维码
10、实现批量抠图
抠图具体教程详见 Python装逼指南–五行代码实现批量抠图

import os, paddlehub as hub
huseg = hub.Module(name=’deeplabv3p_xception65_humanseg’) # 加载模型
path = ‘./imgs/’ # 文件目录
files = [path + i for i in os.listdir(path)] # 获取文件列表
results = huseg.segmentation(data={‘image’: files}) # 抠图

我用 Python 制作了一个迷宫游戏

我用 Python 制作了一个迷宫游戏

 

相信大家都玩过迷宫的游戏,对于简单的迷宫,我们可以一眼就看出通路,但是对于复杂的迷宫,可能要仔细寻找好久,甚至耗费数天,然后可能还要分别从入口和出口两头寻找才能找的到通路,甚至也可能找不到通路。

虽然走迷宫问题对于我们人类来讲比较复杂,但对于计算机来说却是很简单的问题。为什么这样说呢,因为看似复杂实则是有规可循的。

我们可以这么做,携带一根很长的绳子,从入口出发一直走,如果有岔路口就走*左边的岔口,直到走到死胡同或者找到出路。如果是死胡同则退回上一个岔路口,我们称之为岔口 A,

这时进入左边第二个岔口,进入第二个岔口后重复*个岔口的步骤,直到找到出路或者死胡同退回来。当把该岔路口所有的岔口都走了一遍,还未找到出路就沿着绳子往回走,走到岔口 A 的前一个路口 B,重复上面的步骤。

不知道你有没有发现,这其实就是一个不断递归的过程,而这正是计算机所擅长的。

上面这种走迷宫的算法就是我们常说的深度优先遍历算法,与之相对的是广度优先遍历算法。有了理论基础,下面我们就来试着用 程序来实现一个走迷宫的小程序。

先来看看*终的效果视频。

生成迷宫
生成迷宫有很多种算法,常用的有递归回溯法、递归分割法和随机 Prim 算法,我们今天是用的*后一种算法。

该算法的主要步骤如下:

1、迷宫行和列必须为奇数
2、奇数行和奇数列的交叉点为路,其余点为墙,迷宫四周全是墙
3、选定一个为路的单元格(本例选 [1,1]),然后把它的邻墙放入列表 wall
4、当列表 wall 里还有墙时:
4.1、从列表里随机选一面墙,如果这面墙分隔的两个单元格只有一个单元格被访问过
4.1.1、那就从列表里移除这面墙,同时把墙打通
4.1.2、将单元格标记为已访问
4.1.3、将未访问的单元格的的邻墙加入列表 wall
4.2、如果这面墙两面的单元格都已经被访问过,那就从列表里移除这面墙
我们定义一个 Maze 类,用二维数组表示迷宫地图,其中 1 表示墙壁,0 表示路,然后初始化左上角为入口,右下角为出口,*后定义下方向向量。

class Maze:
def __init__(self, width, height):
self.width = width
self.height = height
self.map = [[0 if x % 2 == 1 and y % 2 == 1 else 1 for x in range(width)] for y in range(height)]
self.map[1][0] = 0  # 入口
self.map[height – 2][width – 1] = 0  # 出口
self.visited = []
# right up left down
self.dx = [1, 0, -1, 0]
self.dy = [0, -1, 0, 1]
接下来就是生成迷宫的主函数了。

def generate(self):
start = [1, 1]
self.visited.append(start)
wall_list = self.get_neighbor_wall(start)
while wall_list:
wall_position = random.choice(wall_list)
neighbor_road = self.get_neighbor_road(wall_position)
wall_list.remove(wall_position)
self.deal_with_not_visited(neighbor_road[0], wall_position, wall_list)
self.deal_with_not_visited(neighbor_road[1], wall_position, wall_list)
该函数里面有两个主要函数 get_neighbor_road(point) 和 deal_with_not_visited(),前者会获得传入坐标点 point 的邻路节点,返回值是一个二维数组,后者 deal_with_not_visited() 函数处理步骤 4.1 的逻辑。

由于 Prim 随机算法是随机的从列表中的所有的单元格进行随机选择,新加入的单元格和旧加入的单元格被选中的概率是一样的,因此其分支较多,生成的迷宫较复杂,难度较大,当然看起来也更自然些。来看看我们生成的迷宫。

[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
[1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1]
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
[1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1]
[1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1]
[1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1]
[1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1]
[1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1]
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
走出迷宫
得到了迷宫的地图,接下来就按照我们文首的思路来走迷宫即可。主要函数逻辑如下:

def dfs(self, x, y, path, visited=[]):
# outOfIndex
if self.is_out_of_index(x, y):
return False

# visited or is wall
if [x, y] in visited or self.get_value([x, y]) == 1:
return False

visited.append([x, y])
path.append([x, y])

# end…
if x == self.width – 2 and y == self.height – 2:
return True

# recursive
for i in range(4):
if 0 < x + self.dx[i] < self.width – 1 and 0 < y + self.dy[i] < self.height – 1 and \
self.get_value([x + self.dx[i], y + self.dy[i]]) == 0:
if self.dfs(x + self.dx[i], y + self.dy[i], path, visited):
return True
elif not self.is_out_of_index(x, y) and path[-1] != [x, y]:
path.append([x, y])
很明显,这就是一个典型的递归程序。当该节点坐标越界、该节点被访问过或者该节点是墙壁的时候,直接返回,因为该节点肯定不是我们要找的路径的一部分,否则就将该节点加入被访问过的节点和路径的集合中。

然后如果该节点是出口则表示程序执行结束,找到了通路。不然就遍历四个方向向量,将节点的邻路传入函数 dfs 继续以上步骤,直到找到出路或者程序所有节点都遍历完成。

来看看我们 dfs 得出的路径结果:

[[0, 1], [1, 1], [2, 1], [3, 1], [4, 1], [5, 1], [6, 1], [7, 1], [8, 1], [9, 1], [9, 1], [8, 1], [7, 1], [6, 1], [5, 1], [5, 2], [5, 3], [6, 3], [7, 3], [8, 3], [9, 3], [9, 4], [9, 5], [9, 5], [9, 4], [9, 3], [8, 3], [7, 3], [7, 4], [7, 5], [7, 5], [7, 4], [7, 3], [6, 3], [5, 3], [4, 3], [3, 3], [2, 3], [1, 3], [1, 3], [2, 3], [3, 3], [3, 4], [3, 5], [2, 5], [1, 5], [1, 6], [1, 7], [1, 8], [1, 9], [1, 9], [1, 8], [1, 7], [1, 6], [1, 5], [2, 5], [3, 5], [3, 6], [3, 7], [3, 8], [3, 9], [3, 9], [3, 8], [3, 7], [3, 6], [3, 5], [3, 4], [3, 3], [4, 3], [5, 3], [5, 4], [5, 5], [5, 6], [5, 7], [6, 7], [7, 7], [8, 7], [9, 7], [9, 8], [9, 9], [10, 9]]
可视化
有了迷宫地图和通路路径,剩下的工作就是将这些坐标点渲染出来。今天我们用的可视化库是 pyxel,这是一个用来写像素级游戏的  Python 库,

当然是用前需要先安装下这个库。

Win 用户直接用 pip install -U pyxel命令安装即可。

Mac 用户使用以下命令安装:

brew install python3 gcc sdl2 sdl2_image gifsicle
pip3 install -U pyxel
先来看个简单的 Demo。

import pyxel

class App:
def __init__(self):
pyxel.init(160, 120)
self.x = 0
pyxel.run(self.update, self.draw)

def update(self):
self.x = (self.x + 1) % pyxel.width

def draw(self):
pyxel.cls(0)
pyxel.rect(self.x, 0, 8, 8, 9)

App()
类 App 的执行逻辑就是不断的调用 update 函数和 draw 函数,因此可以在 update 函数中更新物体的坐标,然后在 draw 函数中将图像画到屏幕即可。

如此我们就先把迷宫画出来,然后在渲染 dfs 遍历动画。

width, height = 37, 21
my_maze = Maze(width, height)
my_maze.generate()

class App:
def __init__(self):
pyxel.init(width * pixel, height * pixel)
pyxel.run(self.update, self.draw)

def update(self):
if pyxel.btn(pyxel.KEY_Q):
pyxel.quit()

if pyxel.btn(pyxel.KEY_S):
self.death = False

def draw(self):
# draw maze
for x in range(height):
for y in range(width):
color = road_color if my_maze.map[x][y] is 0 else wall_color
pyxel.rect(y * pixel, x * pixel, pixel, pixel, color)
pyxel.rect(0, pixel, pixel, pixel, start_point_color)
pyxel.rect((width – 1) * pixel, (height – 2) * pixel, pixel, pixel, end_point_color)

App()
看起来还可以,这里的宽和高我分别用了 37 和 21 个像素格来生成,所以生成的迷宫不是很复杂,如果像素点很多的话就会错综复杂了。

接下里来我们就需要修改 update 函数和 draw 函数来渲染路径了。为了方便操作,我们在 init 函数中新增几个属性。

self.index = 0
self.route = [] # 用于记录待渲染的路径
self.step = 1 # 步长,数值越小速度越快,1:每次一格;10:每次 1/10 格
self.color = start_point_color
self.bfs_route = my_maze.bfs_route()
其中 index 和 step 是用来控制渲染速度的,在 draw 函数中 index 每次自增 1,然后再对 step 求余得到当前的真实下标 real_index,简言之就是 index 每增加 step,real_index 才会加一,渲染路径向前走一步。

def draw(self):
# draw maze
for x in range(height):
for y in range(width):
color = road_color if my_maze.map[x][y] is 0 else wall_color
pyxel.rect(y * pixel, x * pixel, pixel, pixel, color)
pyxel.rect(0, pixel, pixel, pixel, start_point_color)
pyxel.rect((width – 1) * pixel, (height – 2) * pixel, pixel, pixel, end_point_color)

if self.index > 0:
# draw route
offset = pixel / 2
for i in range(len(self.route) – 1):
curr = self.route[i]
next = self.route[i + 1]
self.color = backtrack_color if curr in self.route[:i] and next in self.route[:i] else route_color
pyxel.line(curr[0] + offset, (curr[1] + offset), next[0] + offset, next[1] + offset, self.color)
pyxel.circ(self.route[-1][0] + 2, self.route[-1][1] + 2, 1, head_color)
def update(self):
if pyxel.btn(pyxel.KEY_Q):
pyxel.quit()

if pyxel.btn(pyxel.KEY_S):
self.death = False

if not self.death:
self.check_death()
self.update_route()

def check_death(self):
if self.dfs_model and len(self.route) == len(self.dfs_route) – 1:
self.death = True
elif not self.dfs_model and len(self.route) == len(self.bfs_route) – 1:
self.death = True

def update_route(self):
index = int(self.index / self.step)
self.index += 1
if index == len(self.route):  # move
if self.dfs_model:
self.route.append([pixel * self.dfs_route[index][0], pixel * self.dfs_route[index][1]])
else:
self.route.append([pixel * self.bfs_route[index][0], pixel * self.bfs_route[index][1]])

App()
至此,我们完整的从迷宫生成,到寻找路径,再到路径可视化已全部实现。直接调用主函数 App() 然后按 S 键盘开启游戏,就可以看到文首的效果了。