我的誰是臥底遊戲程式碼總結
誰是臥底遊戲程式碼總結
來源
和朋友在臺北跨年,記得在她的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>
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>
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))
從1到10的int型隨數
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的一些原始碼來實現第一版的功能,最後還是放棄拍照功能,但在工程中還保留了這一部分原始碼。
新書上路,能學多少,能總結多少是多少吧。