1. 程式人生 > >我的誰是臥底遊戲程式碼總結

我的誰是臥底遊戲程式碼總結

誰是臥底遊戲程式碼總結

來源

和朋友在臺北跨年,記得在她的iPhone上玩了一個帶拍照功能版本的誰是臥底。回家在正月十五前寫出來一個復刻版,在聚會上玩。

遊戲簡介

一群人被髮放兩個詞語,其中有一個臥底,拿到兩個詞語中的一個,另外的人皆是平民,拿到另外一個詞語;
按照順序,每個人開始用一些話描述自己的詞語,一輪描述結束,眾人投票,指出可能是臥底的那個人,如果指出臥底,平民勝利,如果指出平民,相應的平民出局,遊戲繼續下一輪描述,直到臥底被指出,最後平民勝利;或者僅剩一個平民和臥底,那麼臥底獲勝。

遊戲程式碼解析

程式碼已在github開源,地址為:

現在改動中的版本在這個useful分支中:

MainActivity.制定遊戲方案

最初的版本是在MainActivity中只有一個點選開始的按鈕,之後跳轉到Game.Activity.
後來添加了自定義詞語和玩家數量的功能。

總的來說是制定遊戲方案,是幾個人玩,有無自定義詞語。

具體的實現方案是:在startActivity()中新增一個包含Bundle的Intent物件。

以下函式是“開始遊戲”對應的onClick事件。它主要的目的是攜帶Intent開始GameActivity.

    public void onClick(View view){
        Intent i=new Intent(this
,Game.class); Bundle digitalBundle=new Bundle(); if (editText.getText()!=null){ //解析數字輸入框字元,將其轉化成遊戲人數放進bundle。 //預設的EditText的text是4,所以預設遊戲人數是4.所以這個if語句的條件總為真,即總會執行以下這句。 user_count=Integer.parseInt(editText.getText().toString()); } if (linearLayout.isLaidOut()){ //這個if內的條件我也不太懂,本來是想如果這個LinearLayout可見時才讀取資料,
//但是顯然沒有isVisible這個boolean變數直接拿來用,所以找了一個相近的, //但是實際上,它總為真,或許我應該吧對應的LinearLayout的預設值設定為GONE,而非INVISIBLE。 st_man=man_word.getText().toString(); Log.d("MainA",">>>>>>>>>>>the st_man is :"+st_man); st_spy=spy_word.getText().toString(); digitalBundle.putString("man_word",st_man); digitalBundle.putString("spy_word",st_spy); } digitalBundle.putInt("user_count",user_count); i.putExtras(digitalBundle); startActivity(i); }

View.VISIBLE、INVISIBLE、GONE的區別:

View.VISIBLE:可見
View.INVISIBLE:不可見,但這個View仍然會佔用在xml檔案中所分配的佈局空間,不重新layout
View.GONE:不可見,但這個View在ViewGroup中不保留位置,會重新layout,不再佔用空間,那後面的view就會取代他的位置

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:layout_gravity="center_horizontal"
    tools:context="com.example.t26wodiv1.MainActivity">

    <TextView
        android:id="@+id/text_intro"
        android:gravity="center_horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/introduce" />
    <EditText
        android:layout_below="@+id/text_intro"
        android:id="@+id/edit_text_main"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="4"/>
    <LinearLayout
        android:orientation="vertical"
        android:id="@+id/main_empty_layout"
        android:layout_below="@+id/edit_text_main"
        android:visibility="invisible"
        android:layout_width="match_parent"
        android:layout_height="200dp">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/custom_word_intro"/>
        <EditText
            android:id="@+id/spy_word"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="臥底的詞語"
            android:text="笨蛋"/>
        <EditText
            android:id="@+id/man_word"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="平民的詞語"
            android:text="蠢貨"/>
    </LinearLayout>
    <Button
        android:id="@+id/start_game_button"
        android:layout_below="@+id/main_empty_layout"
        android:layout_centerHorizontal="true"
        android:onClick="onClick"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="開始遊戲"/>
    <Button
        android:id="@+id/custom_game"
        android:layout_below="@+id/main_empty_layout"
        android:layout_toRightOf="@+id/start_game_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="自定義遊戲"/>
</RelativeLayout>

MainActivity的介面

Game.java 遊戲的主體

有必要解釋的一些部分:

有許多import語句在下方的程式碼中是多餘的,因為沒有優化包匯入。

之前的版本使用Dialog來告訴使用者他的詞語,後來直接採用Toast,所以有關於Dialog的一些無效導包。

關於RecyclerView的匯入,請參考:郭霖的部落格,RecyclerView的使用詳解。Game.java裡的很大部分依賴RecyclerView的一些特性來達成,例如點選,長按,瀑布流的瓷片效果。這裡就不解釋StaggeredHomeAdapter的程式碼,大神的解釋更加準確。

有一個比較笨的邏輯是:在取得來自MainActivity的Intent後,判斷spy_word是否為“笨蛋”;這實在是下下策,這樣寫的原因是,沒辦法確切知道使用者是否自定義詞語了,所以預設臥底詞語輸入框裡的“笨蛋”二字當作一個flag傳遞給Game的私有靜態變數SPY_WORD,如果SPY_WORD仍舊是“笨蛋”(這裡注意使用了equals()語句,而非”==“,這是java基礎的字串的知識)說明使用者沒有自定義詞語,然後queryDatabase,查詢資料庫的資料,選一個隨機的詞語;如果沒有equals,則說明變數有被使用者改動過,從而直接將其賦值給SPY_WORD。

queryDatabaseForAWord()是一個從資料庫隨機抽取一個詞語的方法。

package com.example.t26wodiv1;

import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

import static android.R.attr.data;
import static android.R.attr.defaultHeight;
import static android.R.attr.switchMinWidth;
import static com.example.t26wodiv1.DBHelper.TABLE_COLUMN_1;
import static com.example.t26wodiv1.DBHelper.TABLE_COLUMN_2;
import static com.example.t26wodiv1.DBHelper.TABLE_NAME;

/**
 * Created by paul on 2/5/17.
 */

public class Game extends Activity {
    private RecyclerView mRecyclerView;
    private List<String> mDatas;
    private StaggeredHomeAdapter mAdapter;
    private int RANDOM_PAIR=(int)(0+Math.random()*12);//Make sure it's 12 instead of 11;
    private static int RANDOM_USER=0;
    private static  String MAN_WORD=null;
    private static  String SPY_WORD=null;
    DBHelper mydb=new DBHelper(this);//Do I need to initial it explicitly?
    private SQLiteDatabase dbReader;
    private int user_count=4;
    private Button query_button;
    private Button finish_button;




    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_game);
        query_button= (Button) findViewById(R.id.game_btn_query);
        query_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(Game.this,"玩家   "+(RANDOM_USER+1)+"   是臥底!!",Toast.LENGTH_SHORT).show();
            }
        });
        finish_button= (Button) findViewById(R.id.game_btn_finish);
        finish_button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });

        Bundle  extras=getIntent().getExtras();
        user_count=extras.getInt("user_count");
        MAN_WORD=extras.getString("man_word");
        SPY_WORD=extras.getString("spy_word");
        //如果是“笨蛋”則說明預設未改變,應從資料庫查詢資料
        //同時,如果因為MainActivity的那個Edittext不可見,而導致“笨蛋”為“”(即空),也要從資料庫查詢
        if ((SPY_WORD.equals("笨蛋"))||(SPY_WORD.equals(""))){
            queryDatabaseForAWord();
        }
        shapeUsersInterface();
        RANDOM_USER=(int)(Math.random()*user_count);
        mRecyclerView= (RecyclerView) findViewById(R.id.id_recyclerview);
//        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setLayoutManager(new GridLayoutManager(this,3));
//        mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.VERTICAL));
        mAdapter=new StaggeredHomeAdapter(this,mDatas);
        mRecyclerView.setAdapter(mAdapter);




//        queryDatabaseForAWord();
        initEvent();
    }
    private void initEvent(){
        mAdapter.setOnItemClickListener(new StaggeredHomeAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                if (position==RANDOM_USER){
                    Toast.makeText(Game.this,"玩家 "+(position+1)+"的詞語是:"+SPY_WORD,Toast.LENGTH_SHORT).show();
                }else {
                    Toast.makeText(Game.this,"玩家 "+(position+1)+"的詞語是:"+MAN_WORD,Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onItemLongClick(View view, int position) {
                if (position==RANDOM_USER){
                    Toast.makeText(Game.this,"玩家"+(position+1)+"    是臥底,遊戲結束",Toast.LENGTH_SHORT).show();
                }else {
                    Toast.makeText(Game.this,"玩家"+(position+1)+"    不是臥底,遊戲繼續",Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    private void queryDatabaseForAWord() {
        dbReader=mydb.getReadableDatabase();
        Cursor cursor=dbReader.query(TABLE_NAME,null,null,null,null,null,null);
        cursor.moveToPosition(RANDOM_PAIR);
        Log.d("GameA",">>>>>>> RANDOM_PAIR is:  "+RANDOM_PAIR);
        //// TODO: 2/9/17 定位完成?大概吧,;總之開始取資料吧!
        MAN_WORD=cursor.getString(cursor.getColumnIndex(TABLE_COLUMN_1));
        SPY_WORD=cursor.getString(cursor.getColumnIndex(TABLE_COLUMN_2));
        cursor.close();
        dbReader.close();
    }
    protected void shapeUsersInterface(){
        mDatas=new ArrayList<>();
        for (int i=1;i<=user_count;i++){
            mDatas.add(""+i);
        }
    }
}

附帶的game的xml佈局檔案如下:

截圖:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    tools:context="com.example.t26wodiv1.Game">

    <FrameLayout
        android:id="@+id/recycler_view_frame"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="0.9">
        <android.support.v7.widget.RecyclerView
            android:id="@+id/id_recyclerview"
            android:divider="#ffff0000"
            android:dividerHeight="10dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            />
    </FrameLayout>

    <LinearLayout
        android:layout_below="@+id/recycler_view_frame"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_weight="0.1">
        <Button
            android:id="@+id/game_btn_query"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="看看臥底"/>
        <Button

            android:id="@+id/game_btn_finish"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="結束這盤"/>
    </LinearLayout>

</RelativeLayout>

GameActivity的介面

DBHelper.java 遊戲的後臺,臥底和平民的詞語來源

這一片的程式碼也是從某個地方抄來並改動的,來源是tutorialspoint的SQLite例程。

一些解釋:SQLite入門的話,請看《第一行程式碼》寫的吧,這裡說一下簡單地初始化SQLiteOpenHelper之後的思路:

一個表,名字是”wordlist“

兩個柱,分別是word1,word2;對應平民或者臥底。

兩組資料,分別是COLUMNS_ODD,COLUMN_EVEN,對應平名或者臥底的詞語,實際上準確的對應誰,都無所謂。

然後在onCreate中迴圈填入11次。使用insertDefaultWords(i,db);

public class DBHelper extends SQLiteOpenHelper {
    public static final String DATABASE_NAME="myDatabaseName.db";
    public static final String TABLE_NAME="wordList";
    public static final String TABLE_COLUMN_1="word1";
    public static final String TABLE_COLUMN_2="word2";
    public static final String[] COLUMN_ODD={"牛奶","紙巾","水盆","芥末","洗髮露","錦寒","國徵","梁山伯和祝應臺","火鍋","電動車","粉絲"};//11 default words
    public static final String[] COLUMN_EVEN={"豆漿","手帕","水桶","辣椒","護髮素","瑞瑞","華新","羅密歐和朱麗葉","冒菜","摩托車","米線"};
    public DBHelper(Context context) {
        super(context,DATABASE_NAME, null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table "+TABLE_NAME+"(id integer primary key, "+TABLE_COLUMN_1+" text,"+TABLE_COLUMN_2+" text)");
        for (int i=0;i<=10;i++){
            insertDefaultWords(i,db);
        }

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
    private boolean insertDefaultWords(int i,SQLiteDatabase db){
        //我覺得這樣先getWritable最後再close的一個迴圈需要後期優化;
//        已優化

        ContentValues contentValues=new ContentValues();
        contentValues.put(TABLE_COLUMN_1,COLUMN_ODD[i]);
        contentValues.put(TABLE_COLUMN_2,COLUMN_EVEN[i]);
        db.insertOrThrow(TABLE_NAME,null,contentValues);

        return true;
    }
}

StageredHomeAdapter.java

Game介面的介面卡,非常好用的一個比listView更好的控制元件

程式碼如下,清希的解釋請看hongyang的部落格,不敢自己抄襲大神的解釋,也不抄襲複製寫劣質的文章了,雖然這篇就挺劣質的。

package com.example.t26wodiv1;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;
import android.support.v7.widget.RecyclerView;


/**
 * Created by paul on 2/11/17.
 */

public class StaggeredHomeAdapter extends RecyclerView.Adapter<StaggeredHomeAdapter.MyViewHolder> {
    private List<String> mDatas;
    private LayoutInflater mInflater;
    private List<Integer> mHeights;


    //這個接口裡的方法全部在MainActivity裡再實現;他們是我們自己捏造的,功能還沒有呢,要在main裡實現
    public interface OnItemClickListener
    {
        void onItemClick(View view, int position);

        void onItemLongClick(View view, int position);
    }

    private OnItemClickListener mOnItemClickListener;


    //The method that will be used in MainActivity to set OnClickListener;
    public void setOnItemClickListener(OnItemClickListener mOnItemClickListener)
    {
        this.mOnItemClickListener = mOnItemClickListener;
    }
    //The constructor
    //Which receive the context and data (in this case ,
    //'data' is A-z and its ASCII number)
    public StaggeredHomeAdapter(Context context, List<String> datas)
    {
        //Obtains the LayoutInflater from the given context.
        mInflater = LayoutInflater.from(context);
        mDatas = datas;

        //initialize the mHeight variable for each mDatas.
        mHeights = new ArrayList<Integer>();
        for (int i = 0; i < mDatas.size(); i++)
        {
            mHeights.add( (int) (100 + Math.random() * 300));
        }
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
    {
        MyViewHolder holder = new MyViewHolder(mInflater.inflate(
                R.layout.item_staggered_home, parent, false));
        return holder;
    }


    //Called by RecyclerView to display the data at the specified position
    @Override
    public void onBindViewHolder(final MyViewHolder holder, final int position)
    {
        //get the TextView's LayoutParams,then set its height attribute.
        ViewGroup.LayoutParams lp = holder.tv.getLayoutParams();
        lp.height = mHeights.get(position);

        //using out layout params to specify the corresponding height attribute.
        holder.tv.setLayoutParams(lp);
        holder.tv.setText(mDatas.get(position));

        // 如果設定了回撥,則設定點選事件
        if (mOnItemClickListener != null)
        {
            holder.itemView.setOnClickListener(new View.OnClickListener()
            {
                @Override
                public void onClick(View v)
                {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickListener.onItemClick(holder.itemView, pos);
                }
            });

            holder.itemView.setOnLongClickListener(new View.OnLongClickListener()
            {
                @Override
                public boolean onLongClick(View v)
                {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickListener.onItemLongClick(holder.itemView, pos);
                    removeData(pos);
                    return false;
                }
            });
        }
    }

    @Override
    public int getItemCount()
    {
        return mDatas.size();
    }

    public void addData(int position)
    {
        mDatas.add(position, "Insert One");
        mHeights.add( (int) (100 + Math.random() * 300));
        notifyItemInserted(position);
    }

    public void removeData(int position)
    {
        mDatas.remove(position);
        notifyItemRemoved(position);
    }

    class MyViewHolder extends RecyclerView.ViewHolder
    {

        TextView tv;

        public MyViewHolder(View view)
        {
            super(view);
            tv = (TextView) view.findViewById(R.id.id_num);

        }
    }
}

一些看似簡單的過程中的薄弱環節

隨機數的生成


(資料型別)(最小值+Math.random()*(最大值-最小值+1))
例:
(int)(1+Math.random()*(10-1+1))
從110int型隨數

java強制型別轉換:

 x = (int)34.56 + (int)11.2;  // 丟失精度
 //output:x=45;
 //可以看出(int)只取整數部分;

onClickListener 的不同用法(來自另外一位的部落格,忘記來源,如有侵權請告知):

1.使用介面,繼承Button的監聽方法:

。。。。。 implements Button.OnClickListener{

.....
my_button.setOnClickListener(this);

.....}



2.使用介面,繼承view類的監聽方法:

... implements View.OnClickListener{
...
imageView1.setOnClickListener(this);
myButton.setOnClickListener(this);
...
}

3.不用介面,在類內部直接實現監聽:

myButton.setOnClickListener(new OnClickListener(){
    public void onClick(View v){
        Toast.....show();
    }
}

或者,不使用匿名例項,也可以定義一個具體的例項:

 class btn_listener implements Button.OnClickListener  
{  
public void onClick(View v) {  
// TODO Auto-generated method stub
//do something here;  

}  
} 

 btn_listener bl = new btn_listener();  
btn_say_hello.setOnClickListener(bl); //bl是類btn_listener的例項,而btn_listener為監聽方法的介面  
} //因此設定監聽的引數只需傳本類的物件即可  
}

結束語

大概第一篇完整的android某個專案的開發記錄就這樣了,其實過程中遇到的問題遠不止描述的這麼清晰,因為第一版本還有先拍照後看看自己單詞的實現,但是寫入檔案流和為ImageView設定src的一套下來bug很多,而我對java IO ,File,stream部分都不太熟悉。 從網上也有抄ImageThumbnail.java的一些原始碼來實現第一版的功能,最後還是放棄拍照功能,但在工程中還保留了這一部分原始碼。

新書上路,能學多少,能總結多少是多少吧。