2013年9月20日金曜日

[ラーメン]我流拉麵排名 2013/09版


第一名 緣

推薦店:
鷹流 凪 長濱No.1 神山
輝(南京店) 一風堂 山頭火復興店(鹽味)

一般店:
長安旺味 太龍軒 三田製麵所 誠屋京站店 大和家
竹北博多一幸舍 樂麵屋 熱烈一番亭



底線:
花月嵐 三士 屯京 玫瑰緣忠孝復興店 山頭火京站店
北無双 惡鬼 博多麵屋台た組 どさん娘
黑平 虎嘯



以下:
美濃屋 市民博多 東京銀座


即將打入以下的觀察名單:
赤坂 一騎 味之時計台 梅光軒 玫瑰緣市府店

2013年9月18日水曜日

[餐廳]茂園餐廳

很標準的台菜海鮮餐廳。除了一些基本菜色之外,食材都在冷凍櫃上面現點現做。
調味偏甜。廚師應該是南部人。每道菜都好吃,就沒什麼需要特別提的了。據說那塊很像麵包的鱈魚很貴...







2013年9月17日火曜日

[安藤]Android 一個可在DB中隨時增加Table的欄位,也會自動升級並保留原本資料的Content Provider + DB 架構

在這邊提出一個android用的DB+content provider的架構供參考。
設計理念:

  • 簡單增加欄位
  • 使用較少程式碼
  • 資料穩定度
  • 全自動升級資料庫(重要!)



所有的Table設計都繼承此class:

public abstract class DBTable {

    public static final String SYS_CONTENT_TYPE = "vnd.android.cursor.dir";

    public static final String SYS_CONTENT_ITEM_TYPE = "vnd.android.cursor.item";

    public static final String COLUMN_TYPE_TEXT = "TEXT DEFAULT '' ";  //所有的資料欄位都加上預設值。

    public static final String COLUMN_TYPE__ID = "INTEGER PRIMARY KEY AUTOINCREMENT ";  //此欄位為android的DB table所必需。資料格式也是固定。勿動。

    public static final String COLUMN_TYPE_INT = "INTEGER DEFAULT 0 ";

    public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS ";

    public static final String URL_PREFIX_CONTENT = "content://";

    public static final String COLUMN_TYPE_REAL = "REAL DEFAULT 0 ";

    public abstract String getTableName();

    public abstract int getTotalColumns();

    public abstract String getColumnName(int columnIndex);

    public abstract String getColumnType(int columnIndex);

    public abstract String getTableCreateCmd();



}
接下來是各個table的定義。這邊只用一個來舉例,其他的請如法炮製:
/**
 * When you define columns, first column name must be "_id", data type
 * must be "INTEGER PRIMARY KEY AUTOINCREMENT", other column name use lower case is better.
 *
 */
public class Table1 extends DBTable {
public static String AUTHORITY;  //android 的content provider所需要的authorities。 跟AndroidManifest.xml裡面宣告Provider的時候的字串要相同。我的建議是直接取App的packagename來用。
public static String TABLENAME;  //table名。不可以跟其他table名稱重複。本例直接拿class name來用。
public static Uri CONTENT_URI;  //android 的content provider所需要的uri。
public static String CONTENT_TYPE;  //給content provider Uri matche使用。
public static String CONTENT_ITEM_TYPE;  //給content provider Uri matche使用。
public static enum eColumns {_id, column1, column2, column3};  //欄位名稱加在這裡
private static eColumns[] arEColumns;  //方便以index取得欄位名稱。(跑迴圈用)

public Table1(String authority)
{
    AUTHORITY = authority;
    CONTENT_URI = Uri.parse(URL_PREFIX_CONTENT + AUTHORITY + "/" +   TABLENAME);
    CONTENT_TYPE = SYS_CONTENT_TYPE + "/" + AUTHORITY + "." + TABLENAME;
    CONTENT_ITEM_TYPE = SYS_CONTENT_ITEM_TYPE + "/" + AUTHORITY + "." + TABLENAME;
    arEColumns = eColumns.values();
    TABLENAME = this.getClass().getSimpleName();
}

@Override
public int getTotalColumns() {
    return arEColumns.length;
}

@Override
public String getColumnName(int columnIndex) {
if ((columnIndex >= 0) && (columnIndex < arEColumns.length))
    return arEColumns[columnIndex].name();
else
    return null;
}

@Override
public String getTableName() {
    return TABLENAME;
}

/**
* 定義各欄位的資料格式
**/
@Override
    public String getColumnType(int columnIndex)
    {
    if (
         columnIndex == eColumns. column1.ordinal()
         || columnIndex == eColumns. column2.ordinal()
     )
        return COLUMN_TYPE_TEXT;
    else if (  //別動。
         columnIndex == eColumns._id.ordinal()
    )
        return COLUMN_TYPE__ID;
    
    return COLUMN_TYPE_INT;
    }

/**
* 取得建立此table的完整sql指令。
**/
@Override
    public String getTableCreateCmd()
    {
        StringBuilder strbuilder = new StringBuilder();
        strbuilder.append(CREATE_TABLE).append(TABLENAME);
        for (eColumns eTmp: arEColumns)
        {
            if (eTmp.ordinal() == 0)
                strbuilder.append(" (");
            else
                strbuilder.append(", ");
            strbuilder.append(eTmp.name())
            .append(" ")
            .append(getColumnType(eTmp.ordinal()));
        }
        strbuilder.append(")");
        return strbuilder.toString();
    }

}

==================================
第二步:建立DBHelper。主要負責DB的table建立跟版本管理。

public class MyDBHelper extends SQLiteOpenHelper {

 private final static int DATABASE_VERSION = 1;  //有新增欄位的時候必須改動此編號,DB才會自動upgrade。
 public ArrayList alDbList;
 public static MyDBHelper dbHelper;
  
 public MyDBHelper(Context mContext, String dbName)
 {
  super(mContext, dbName, null, DATABASE_VERSION);
  dbHelper = this;
  String strAuthority = mContext.getPackageName();
  alDbList = new ArrayList();

  //新增db的話請加入alDbList裡面。
  alDbList.add(new Table1(strAuthority));
 }
 
 @Override
 public void onCreate(SQLiteDatabase db) {

  for (StoreAppTable table : alDbList)
  {
            try{
       db.execSQL(table.getTableCreateCmd());
            }
   catch(SQLiteException e) 
   {e.printStackTrace();}
  }
 }

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

  //第一步:把目前DB裡面的所有table更名。
  for (StoreAppTable table : alDbList)
  {
   StringBuilder strb = new StringBuilder();
   strb.append("ALTER TABLE ")
   .append(table.getTableName())
   .append(" RENAME TO ")
   .append(table.getTableName() + "_");
   try{
       db.execSQL(strb.toString());
            }
   catch(SQLiteException e) 
   {e.printStackTrace();}
  }
  
  //第二步:建立新的table。
  onCreate(db);
  
  //第三步:從舊的table搬資料進新的table。
  //先把舊table填上新的欄位。
  for (StoreAppTable table : alDbList)
  {
   Cursor cursor = null;
   
   try{            
    cursor = db.rawQuery("SELECT * FROM " + table.getTableName() + "_" + " LIMIT 1", null);
    if (cursor != null)
    {
     for (int i = 0; i < table.getTotalColumns(); i++)
     {
      if (cursor.getColumnIndex(table.getColumnName(i)) < 0)
      {
       StringBuilder strb = new StringBuilder();
       strb.append("ALTER TABLE ")
       .append(table.getTableName() + "_")
       .append(" ADD ")
       .append(table.getColumnName(i)).append(" ")
       .append(table.getColumnType(i));
       try {
           db.execSQL(strb.toString());
                }
       catch(SQLiteException e) 
       {e.printStackTrace();}
      }
     }
     cursor.close();
    }
   }
   catch(SQLiteException e) 
   {e.printStackTrace();}
  }
  //利用insert [newtable] select (columns) in [oldtable] 一口氣灌入新table。
  for (StoreAppTable table : alDbList)
  {
   StringBuilder strb = new StringBuilder();
   strb.append("INSERT INTO ")
   .append(table.getTableName())
   .append(" (");

   for (int i = 1; i < table.getTotalColumns(); i++)  //_id欄位是自動增加,不需要insert。
   {
    if (i != 1)
     strb.append(",");
    strb.append(table.getColumnName(i));
   }
   strb.append(")").append(" SELECT ");
   for (int i = 1; i < table.getTotalColumns(); i++)
   {
    if (i != 1)
     strb.append(",");
    strb.append(table.getColumnName(i));
   }
   strb.append(" FROM ")
   .append(table.getTableName() + "_");
   try {
       db.execSQL(strb.toString());
   }
   catch(SQLiteException e) 
   {e.printStackTrace();}
  }
  
 
  
  //第三步:刪除舊table。
  for (StoreAppTable table : alDbList)
  {
   StringBuilder strb = new StringBuilder();
   strb.append("DROP TABLE ")
   .append(table.getTableName() + "_");
   try {
       db.execSQL(strb.toString());
            }
   catch(SQLiteException e) {;}
  }
 }
 
}
=======================
接著是content provider class:
public class StoreAppContentProvider extends ContentProvider {
private StoreAppDBHelper databaseHelper;
private UriMatcher sUriMatcher; //URi 
public ArrayList alDbList;  //儲存所有的table. 方便回圈使用。
    private String AUTHORITY = null;
    private static StoreAppContentProvider storeAppContentProvider;  //singleton設計
    public static ContentResolver resolver;
    public static long lSipServerTimeDelta = 0;
    
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
storeAppContentProvider = this;
resolver = getContext().getContentResolver();
AUTHORITY = getContext().getPackageName();  //之前提到建議authority使用package name. 這樣要沿用到其他app的時候只要直接取代packagename的字串即可。

databaseHelper = new StoreAppDBHelper(getContext(), AUTHORITY.substring(AUTHORITY.lastIndexOf(".") + 1));  //DB的名稱,各個app是可以重複的。就是靠Authority去分辨... 在這邊取packagename的最後一串字。

if (databaseHelper == null)
return false;
else
{
alDbList = databaseHelper.alDbList;  //從dbhelper裡面取得所有db的列表。
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);  //開始建立所有table的Uri matcher
for (int i = 0; i < alDbList.size(); i++)  //for entire table
{
sUriMatcher.addURI(AUTHORITY, alDbList.get(i).getTableName(), i);
}
for (int i = 0; i < alDbList.size(); i++)  //for specified position item
{
sUriMatcher.addURI(AUTHORITY, alDbList.get(i).getTableName() + "/#", alDbList.size() + i);
}
return true;
}
}


public static StoreAppContentProvider getInstance()
{
    return storeAppContentProvider;
}

/**
* DB比較用不到getType,意義不大。
**/

public String getType(Uri uri) {
// TODO Auto-generated method stub
int index = sUriMatcher.match(uri);
if ((index >= (alDbList.size() * 2)) || (index < 0))
{
            throw new IllegalArgumentException("**** get type Unknown URI " + uri); 
}
else
{
if (index <= alDbList.size())
return StoreAppTable.SYS_CONTENT_TYPE + "/" + AUTHORITY + "." + alDbList.get(index);
else
return StoreAppTable.SYS_CONTENT_ITEM_TYPE + "/" + AUTHORITY + "." + alDbList.get(index - alDbList.size());
}
}


@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
try {
SQLiteDatabase db = databaseHelper.getWritableDatabase(); 
int index = sUriMatcher.match(uri);
int count = 0;
if ((index >= (alDbList.size() * 2)) || (index < 0))
{
            throw new IllegalArgumentException("**** get type Unknown URI " + uri); 
}
else
{
if (index < alDbList.size())
count = db.delete(alDbList.get(index).getTableName(), selection, selectionArgs);
else
count =  db.delete(alDbList.get(index - alDbList.size()).getTableName(), BaseColumns._ID + " = " + uri.getPathSegments().get(1)    
+ (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""), selectionArgs);
}
if (count > 0)
    resolver.notifyChange(uri, null);
return count;
}
catch(SQLiteException e)
{
if (BuildConfig.DEBUG) Log.d("Jeff", "Failed to delete data in db " + uri);
e.printStackTrace();
}
return 0;
}

@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
try {
SQLiteDatabase db = databaseHelper.getWritableDatabase(); 
int index = sUriMatcher.match(uri);
//int count = 0;
long rowId;
if (values == null) {
return null;
}
if ((index >= alDbList.size()) || (index < 0))
{
            throw new IllegalArgumentException("**** get type Unknown URI " + uri); 
}
else
{
rowId = db.insert(alDbList.get(index).getTableName(), null, values);
if(rowId > 0){
Uri uriNew = ContentUris.withAppendedId(Uri.parse("content://" + AUTHORITY + "/" + alDbList.get(index)), rowId); 
resolver.notifyChange(uri, null);
return uriNew;
}
}
if (rowId >= 0)
    resolver.notifyChange(uri, null);
}
catch(SQLiteException e)
{
if (BuildConfig.DEBUG) Log.d("Jeff", "Failed to insert data in db " + uri);
e.printStackTrace();
}

return null;
}


@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
try {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
int index = sUriMatcher.match(uri);
//int count = 0;
if ((index >= (alDbList.size() * 2)) || (index < 0))
{
            throw new IllegalArgumentException("**** get type Unknown URI " + uri); 
}
else
{
if (index <= alDbList.size())
{
qb.setTables(alDbList.get(index).getTableName());
}
else
{
qb.setTables(alDbList.get(index - alDbList.size()).getTableName());
qb.appendWhere(BaseColumns._ID + "=" + uri.getPathSegments().get(1));
}
}

if (projection != null)
{
Cursor c = qb.query(databaseHelper.getReadableDatabase(), projection, 
selection, selectionArgs, null, null, sortOrder);
if (c != null) {
c.setNotificationUri(resolver, uri);
return c;
}
}
else
{
Cursor c = qb.query(databaseHelper.getReadableDatabase(), new String[]{"*"}, 
selection, selectionArgs, null, null, sortOrder);
if (c != null) {
c.setNotificationUri(resolver, uri);
return c;
}
}

}
catch(SQLiteException e)
{
if (BuildConfig.DEBUG) Log.d("Jeff", "Failed to query data in db " + uri);
e.printStackTrace();
}
return null;
}

@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
try {
SQLiteDatabase db = databaseHelper.getWritableDatabase(); 
int index = sUriMatcher.match(uri);
int count = 0;
if ((index >= (alDbList.size() * 2)) || (index < 0))
{
            throw new IllegalArgumentException("**** get type Unknown URI " + uri); 
}
else
{
if (index < alDbList.size())
count = db.update(alDbList.get(index).getTableName(), values, selection, selectionArgs);
}
if (count > 0)
    resolver.notifyChange(uri, null);
return count;
}
catch(SQLiteException e)
{
if (BuildConfig.DEBUG) Log.d("Jeff", "Failed to update data in db " + uri);
e.printStackTrace();
}
return 0;
}


}


==========================
使用方法:
讀:
Cursor cursor = this.getContentResolver().query(
Table1.CONTENT_URI, null
, Table1.eColumns.column1.name() + " =? " 
   + " AND " + Table1.eColumns.column2.name() + " =? " 
, new String[1] {"text", "text2"}

, Table1.eColumns.column3.name());


寫:
ContentValues values = new ContentValues();

values.put(Table1.eColumns.column1.name(), "data");

this.getContentResolver().insert(Table1.CONTENT_URI, values);

2013年9月15日日曜日

[HG]Baldr Sky Zero體驗感想

遊戲時間:4HR。

其實我是在要發賣的前兩週才知道要出...作為一個全系列都玩到快爛掉的玩者真是失格。

我對於這個遊戲一直是戰鬥系統重於劇情的角度看待,所以沒有眼鏡角色沒有ノイ老師世界觀沿用原畫更換聲優棒讀等等的更動都不是重點。在這裡也只談戰鬥系統。

本作的戰鬥系統,幾個根本上的更動:


  • 全3D即時運算畫面。真是讓我眼睛一亮... 沒料到會有這麼大的改變。
    本來想說會不會只是類似外傳的東西,根本就是全新作品。
    記憶體會吃到1G左右,i5+HD6850跑得很順。


  • dash的時候可以做到U字形的接近敵人再離開的動作。這是一個非常大的改進。前幾作敵人常做的引誘你揮空的動作,現在你也做得到了。
  • 增加"跳"的動作。體驗版只能跳到高處的地型打高處的敵人(雖然遠距武器也設計成會自動瞄準高處敵人,不會像之前得靠會產生高低差的武器去打),之後說不定會有需要爬山的地型。
  • A/B/C三個按鈕的武器只能掛載定點使用/移動使用兩組(之前可以掛4組)。可以玩的combo變少了。這可能就要看之後可以做出來的武裝的變化。(體驗版還不能開發新武器)
  • 熱表改成各項武器獨自計算。各項武器可以重複使用直到該武器的熱表滿。
    冷卻時間也是分開算,所以武器可以輪流用。不會像之前的系統那樣熱表滿了就什麼事都不能做只能等冷卻,現在可以一直持續攻擊下去。
  • 那...感覺上會出現永久combo?
    不會。本作多了一個量表叫作裝甲。沒滿之前被打到也不會硬直。
    (還是有被打到就會停止當前動作的武器...)
  • 遠距武器除了熱表之外還加上彈數的限制,用完之後會有reload時間,reload次數無限。
  • FC可以掛三個。但是該按鈕的FC表滿了之後就非用FC不可,我覺得這是缺點。

綜合以上的改變,就是變得更好玩了啦! ^o^/
體驗版需要1G的容量,劇情的部分給到第三章打完第一隻BOSS也夠誠意了。

2013年9月14日土曜日

[Event]2013/08/31 WhiskyLive 2013



不免俗的先來段宣告:
未成年請勿喝酒未成年請勿喝酒未成年請勿喝酒未成年請勿喝酒未成年請勿喝酒
喝酒不開車不騎車喝酒不開車不騎車喝酒不開車不騎車喝酒不開車不騎車


今年忘記找看看有沒有公關票可以領。一方面是前一週出國,回來之後工作忙碌,轉眼之間就到了活動當天。
沒做什麼準備就來會場了...
因為喝過的我就不會再試,因此這場的照片比較少。
金盃15年威士忌利口酒。 喝起來有點像RUM。 不過這瓶比ZACAPA 23淡一點,多了麥香。



酒體輕,有甘味,花香,不刺鼻,麥味偏淡。還不錯。看到不少人買。



黑美人原味。 淡到讓我覺得不像是威士忌... 問了一下價格原來一瓶只要500。不知道賣場最低價的幸川是不是就這樣的味道...



黑美人焦糖口味。這隻更淡... 真的就只能拿來當調酒。 喝過之後仔細看了一下標籤,酒精度只有35%,難怪淡如水...



suntory會員到攤位報到就可以獲得一瓶山崎10年小樣。在會場的舞台活動回答問題也可以得到。




本次個人VIP。ben13年。46%讓這隻顯得相當嗆人(事實上我一喝就被嗆到了)。有榛果味。但是這隻吸引我的地方是我覺得越喝越覺得香醇。不常有這樣的經驗,所以喝完5ml之後又多要了一次...



最後是這隻。雪莉桶所以帶甜味,平衡度不錯。也有越喝越香的尾韻。


沒料到這天下大雨結果還穿跑鞋去,走到會場前,鞋子就全濕了。待在會場裡面很久卻喝不到幾款,主要原因是為了要晾乾鞋而坐在休息區相當長的一段時間...
這次可以喝到的款式又比上屆更少...
砸越多錢裝潢的攤位,可以喝到的款式越少的感覺... (我不是說那個ABFGJ...這樣好像幾乎全打到了...)XD
百齡罈弄了一個調酒體驗營還可以拿好幾瓶小樣,聽說光是排隊就要排2hr...懶了。
新山崎還不錯。沒有年輕酒的辛辣感...
這次用的螢光豬肉章還蠻有趣的。只是找章就有點累,也蠻怕不小心洗掉之後不能再進場(途中有跑去外面買飲料兼休息,那天又下大雨...)


這次改成喝玩酒之後才去吃拉麵。喝完酒之後容易肚子餓,加上看到會場裡面的香腸一份200,
乾脆出來吃拉麵。

一樣是北無雙。這次點的是燒烤叉燒白味增拉麵。 叉燒是有調味了,但是有點柴。湯的話普通。
下次還是改點北無雙拉麵+叉燒好了。