2010年4月29日星期四

OpenOffice的小工具:字數顯示器


(本來想發在另一個部落格,但想想這邊應該較多有需要的用戶,橫豎跟寫作相關,貼這邊了)
譚劍兄的部落格看到他感嘆OpenOffice沒有像MS Word長期顯示字數的功能,在下作為OOo的支持者,便去找找看有沒有plugin可用(OOo是開放源碼軟件,吸引了不少開發者為他們編寫擴充插件)。說來有趣,我從來沒想過讓文字處理器不斷更新字數,但細心一想,這也的確很方便。找了好些插件也沒有完全合用的,於是往巨集的方向出發,結果找到一個好東西:Real-Time Word Count MACRO

然而,這個巨集是外國人用的,所以是Word Count(字數)不是Character Count(字元數),這對咱們中文系統的沒啥用。既然如此,我便發揮典型的developer精神--它沒有這個功能,便自己寫囉。(爆)

其實也不是真的自己寫,上面那個WordCount MACRO已十分完整,只要把計算字數的地方換成計算字元便行。可是,我從沒寫過Python,到OOo看API Reference,他們又寫得含糊。我實在懶得花時間去慢慢學Python和OOo API入門,於是用囫圇吞棗的方式,利用吃完晚飯的休息時間把WordCount改成以下的CharCount:


# Continuously updating word count
import unohelper, uno, os, time
from com.sun.star.i18n.WordType import WORD_COUNT
from com.sun.star.i18n import Boundary
from com.sun.star.lang import Locale
from com.sun.star.awt import XTopWindowListener

#socket = True
socket = False
localContext = uno.getComponentContext()

if socket:
resolver = localContext.ServiceManager.createInstanceWithContext('com.sun.star.bridge.UnoUrlResolver', localContext)
ctx = resolver.resolve('uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext')
else: ctx = localContext

smgr = ctx.ServiceManager
desktop = smgr.createInstanceWithContext('com.sun.star.frame.Desktop', ctx)

waittime = 5 # seconds
goal = 0

def printOut(txt):
if socket: print txt
else:
model = desktop.getCurrentComponent()
text = model.Text
cursor = text.createTextCursorByRange(text.getEnd())
text.insertString(cursor, txt + '\r', 0)

def updateCount(wordCountModel, percentModel):
'''Updates the GUI.

Updates the word count and the percentage completed in the
Tkinter GUI. If some text of more than one word is selected,
it updates the GUI based on the selection; if not, on the
whole document.'''

model = desktop.getCurrentComponent()
try:
if not model.supportsService('com.sun.star.text.TextDocument'):
return
except AttributeError: return

try: wc = model.CharacterCount
except AttributeError: return

wordCountModel.Label = str(wc)

if goal != 0:
pc_text = '(%.2f %%)' % (100 * (wc / float(goal)))
percentModel.Label = pc_text

# This is the user interface bit. It looks more or less like this:

###############################
# Word Count _ o x #
###############################
# _____ #
# 451 of |500| (90.20 percent)#
# ----- #
###############################

# The boxed `500' is the text entry box.

class WindowClosingListener(unohelper.Base, XTopWindowListener):
def __init__(self):
global keepGoing

keepGoing = True
def windowClosing(self, e):
global keepGoing

keepGoing = False
e.Source.setVisible(False)

def addControl(controlType, dlgModel, x, y, width, height, label, name = None):
control = dlgModel.createInstance(controlType)
control.PositionX = x
control.PositionY = y
control.Width = width
control.Height = height
if controlType == 'com.sun.star.awt.UnoControlFixedTextModel':
control.Label = label
elif controlType == 'com.sun.star.awt.UnoControlEditModel':
control.Text = label

if name:
control.Name = name
dlgModel.insertByName(name, control)
else:
control.Name = 'unnamed'
dlgModel.insertByName('unnamed', control)

return control

def loopTheLoop(goalModel, wordCountModel, percentModel):
global goal

while keepGoing:
try: goal = int(goalModel.Text)
except: goal = 0
updateCount(wordCountModel, percentModel)
time.sleep(waittime)

if not socket:
import threading
class UpdaterThread(threading.Thread):
def __init__(self, goalModel, wordCountModel, percentModel):
threading.Thread.__init__(self)

self.goalModel = goalModel
self.wordCountModel = wordCountModel
self.percentModel = percentModel

def run(self):
loopTheLoop(self.goalModel, self.wordCountModel, self.percentModel)

def charCount(*args):
'''Displays a continuously updating character count.'''
dialogModel = smgr.createInstanceWithContext('com.sun.star.awt.UnoControlDialogModel', ctx)

dialogModel.PositionX = 300
dialogModel.PositionY = 20
dialogModel.Width = 100
dialogModel.Height = 16
dialogModel.Title = 'Char Count'

lblWc = addControl('com.sun.star.awt.UnoControlFixedTextModel', dialogModel, 2, 2, 20, 14, '', 'lblWc')
lblWc.Align = 2 # Align right
addControl('com.sun.star.awt.UnoControlFixedTextModel', dialogModel, 23, 2, 10, 14, '/')
addControl('com.sun.star.awt.UnoControlEditModel', dialogModel, 30, 2, 25, 14, '', 'txtGoal')

addControl('com.sun.star.awt.UnoControlFixedTextModel', dialogModel, 62, 2, 50, 14, '(%)', 'lblPercent')
addControl('com.sun.star.awt.UnoControlFixedTextModel', dialogModel, 114, 2, 12, 14, '', 'lblMinus')

controlContainer = smgr.createInstanceWithContext('com.sun.star.awt.UnoControlDialog', ctx)
controlContainer.setModel(dialogModel)

controlContainer.addTopWindowListener(WindowClosingListener())
controlContainer.setVisible(True)
goalModel = controlContainer.getControl('txtGoal').getModel()
wordCountModel = controlContainer.getControl('lblWc').getModel()
percentModel = controlContainer.getControl('lblPercent').getModel()

if socket:
loopTheLoop(goalModel, wordCountModel, percentModel)
else:
uthread = UpdaterThread(goalModel, wordCountModel, percentModel)
uthread.start()

keepGoing = True
if socket:
charCount()
else:
g_exportedScripts = charCount,

# Disclaimer and license from acb's macros
#
# Standard disclaimer and MIT licence .
# These macros are copyright (c) 2003-4 Andrew Brown.
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the 'Software'), to deal in the Software without
# restriction, including without limitation the rights to use, copy,
# modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
用法是先把以上的程式碼貼到記事本,把檔案命名為CharCount.py,放到OpenOffice下的子目錄「\Basis\share\Scripts\python」。打開OpenOffice Write,「工具→巨集→執行巨集」,在左邊選「OpenOffice.org巨集」,會找到一個叫「charcount」的項目,執行。


看,字元數目的視窗出來了。你可以在那個空格輸入目標字數,後方便顯示進度百分比(不過那個目標字數要每次輸入,暫時沒法儲存下來)。如果覺得每次打開OOo也要按一次「工具→巨集→……」很麻煩,你可以在「工具→自訂」裡設Hotkey,在「鍵盤」頁裡選一個你喜歡的熱鍵(我選Shift+F3),在下方函數種類選「OpenOffice巨集→share→charcount」,然後按上方的「修改」,以後只要按指定的鍵(例如Shift+F3)便會執行了。


放在上方,還有空位放iTune Mini-player!

原來的word count的功能更完善,可以讓用戶選擇文字,顯示選擇中的段落的字數,但我Trace Code後發覺改這部分很麻煩,OOo的API裡要計算某段文字的字元數沒有直接用的函式,自己計算的話又不準(會錯誤計算換行的字元),要準的話又花時間(運算時間),結果不改了,讓這個小程式更簡潔吧(老實說,修改期間花了80%以上的時間在研究這方面,如果單純要改成現在的樣子,真是五分鐘也不用……)。現在更新速度是五秒一次,想快些的話可以把「waittime = 5 # seconds」那個5改成3或1,不過別忘了愈小愈吃資源。

最後提醒一下,Python是一種以空白位元來計算流程的高階語言,所以不要多手把程式碼前方的空白增減耶。

台灣推理作家協會第八屆徵文獎作品集販售資訊



徵文獎作品集即將販售,這次作品集由協會自行編輯出版,將於年會會場上及網上發售。數量有限,為免向隅請從速選購。

收錄作品:
文善 〈畢業生大逃殺〉
東默農〈刑〉
何敬堯〈盡頭之濱〉
風神 〈少女的祈禱〉
高普 〈索菲亞.血色謎團〉

官方網頁:
台灣推理作家協會第八屆徵文獎作品集販售資訊
網上販售:
台灣推理作家協會第八屆徵文獎作品集(露天拍賣網站)

2010年4月20日星期二

【活動】第九屆台灣推理作家協會年會暨第八屆徵文獎頒獎典禮



年會暨頒獎禮的時間地點終於公開了!
日期:二○一○年五月二十九日(星期六)
時間:下午兩點三十分至四點三十分
地點:中國文化大學推廣教育部APA藝文中心建國本部九樓904頂級教室
(台北市建國南路二段231號,地處建國南北高架橋下,鄰近大安森林公園及台北市立圖書館,交通資訊請看這兒

檢視較大的地圖

至於為什麼海報的調子如此詭異,是因為據說年會上將公開處刑(?),五名協會成員會被拖出來鞭打(詳情請參閱受害者冷言的網誌)(爆),對酷刑推理創作有興趣的朋友萬勿錯過啊。

官方網站:
第九屆台灣推理作家協會年會暨第八屆徵文獎頒獎典禮訊息

2010年4月10日星期六

第八屆徵文獎心得結語

六十五篇作品的心得全貼完了。除了一篇之外,所有感想也是甫讀完便寫的,其實在決選結果公布前(甚至是提交評分前)已全寫好了,只是我不想把總共四萬多字的意見一口氣貼出來,才一天貼一至兩篇,發文時改改錯別字,把一些有可能爆雷的詞語修一修等等。有一篇是在決選名單公布後才追加段落,蠻明顯的(笑),這兒不贅。我在這兒再申明一次,這些感想文章只是個人心得,看法不一定和其他評審或讀者相同,如果有作者不認同在下的意見、或者覺得我的看法有違您自己的創作理念,請不要特別在意,因為您們真正要面對的不是在下一個人,而是廣大的讀者(和編輯--如果您們投稿的話)。我不敢說自己的看法客觀,但我相信至少具有透明度,把覺得優劣的理由寫出來。

我無法預測哪篇作品能得到首獎。決選名單中的五篇作品,我最欣賞的是《索菲亞‧血色謎團》(這可以從我的感想文中輕易看出)。那個背景和題材我也一直很想寫,但自問難以寫得好,所以看到有人把兩者合二為一,令我感到相當震撼。然而這篇作品的優點或許也是缺點--當中某些趣味非凡、天馬行空的點子,可能會讓追求邏輯結構嚴謹的推理迷卻步。其次的是《少女的祈禱》,結構和謎團完整之餘,我相當喜愛當中的感情流露,可是當中犯人的心理和行為會不會被指為不夠寫實,我實在不敢說。在《盡頭之濱》和《畢業生大逃殺》的感想文中,我也挑過一些瑕疵,但相對地來說,這兩篇作品的優點也是相當出眾和獨有的,前者的拼湊式故事和描寫很是吸引,即使我現在正在寫這篇文章時,腦海中仍浮現了那個我沒見過的海濱和站在沙灘上的角色的影子;而《畢》的結局令我深感現實的無奈和殘酷,最神奇的是這篇作品壓根兒沒有犯罪事件也能帶出這樣的訊息!無可否認我不太看好《刑》,但正如我之前在心得所說,配合最近沸沸揚揚的廢死話題,這故事的切入點相當有力,可說是在推理之上帶出了更深入更貼身的意義,這一點也是十分值得重視的。

能讀到這些作品(包括落選的作品),令我體會到不少創作者具有熱誠和想法,以不同的風格去演繹他們心目中理想的推理故事。雖然在下是上屆的首獎得主,但我不覺得我比各位作者強,我只是比您們早一步踏進這個圈圈而已,期望各位和我們一起擴大這個圈圈,讓更多讀者領略閱讀推理的樂趣。

【徵文獎心得】65.失落的密碼

一九六二年,英國皇室收到恐嚇信函,女王通知軍情五處接手處理。不久,倫敦發生殘忍的凶殺案,碰巧被年輕的子爵洛克菲勒兄妹遇上,曾偵破凶案的他們便插手這一連串的事件。

作者的文筆通順,節奏掌握得很好,善用新聞報導和交替場景來作多方描述,令人讀得很舒服。故事中的線索、引用某些資料的推論亦處理得不俗,一方面顯出作者下過工夫做資料搜集,另一方面加強了故事的背景的說服力。中段的轉折有一定的意外性,可是相比之下,結局凶手的動機和出場未免弱了點。事實上,單以推理橋段來說,有好些訊息沒有在前段透露(例如死者的身分、偵探們調查到的一般資料),只在得出結論時才告訴讀者,這樣讀者的投入程度便大減。作者刻意安排的推理線是很清晰的(例如凶手寄來的金幣、死者身旁的物件),可是正正因為這些項目十分詳細,更顯得一般人會詢問的資訊的缺乏。

雖然推理方面有所缺失,但當中亦顯出作者有能力處理好伏線和解謎,我覺得可以了。然而,這作品卻有一個地方讓我頗為失望,便是故事背景的描繪。以一九六二年的英國倫敦作為舞台,如何描述異國情調、時代背景等等便十分重要,描寫技巧大大影響讀者的觀感。當我在第一段看到「倫敦警視廳」一詞便皺眉頭了。警視廳是日本用語,日本人會說「倫敦警視廳」,但中文只會用「倫敦警察廳」、「倫敦警察局」、「倫敦警察總署」或更正式的稱呼「蘇格蘭場」。我想,這或許是作者一時手誤,小問題罷,因為往下看也看到很多合乎背景的設定,雖有瑕疵(例如警長的職銜太小了,他亦沒理由直接跑去找女王)但不礙眼。不過,當中段茉莉看到屍體,驚魂甫定之際,以下一段描述令我大吃一驚:

「嗶……嗶……嗶……」手機的提示音從泰的口袋裡傳出……

手機?!這不是一九六二年嗎?為了說服自己,我唯有把這「手機」當成通話器(我肯定英國警方在七十年代初已有手提的對講機,推前十年也不太過分),可是我再讀下去--

……他急忙看了來電顯示,有些著急地看了看四周後,目光回到茉莉身上。

「來電顯示」便怎說也說不通了。這種「時代謬誤」還有後段出現的「電視遙控器」,英國電視遙控器在一九七○年代才販售,勉強說六○年代已發明出來不是不行,但在文中提到這個只令讀者感到突兀。另一方面,作者努力營造英國的古老氣氛,特意安排馬車行走--無可否認,今天也有馬車,但一九六二年汽車已經普及,就算為了突顯主角們的貴族氣派而讓他們坐馬車,凶手「乘馬車棄屍」也實在太失實了(除非凶手的目的是要引人注意)。此外像「王室經營的醫院」、「王室插手警界」也似乎跟現實有一定距離。描寫一個自己不熟悉的環境是很考工夫的,而且這次的難度還不低(地域不同外還要考慮時代差異)。我對故事中的幾位主角蠻有好感,可是假如作者想繼續發展這個故事,也許要花更多時間和精神去做資料搜集,豐富背景,襯托出劇情的趣味。

2010年4月9日星期五

【徵文獎心得】64.氣球小鎮

收到奶奶重病的消息,主角回到離開多年的故鄉小鎮,卻發覺小鎮滿佈氣球。主角得知老街正被財團收購,而有一個叫作「世界懸浮會」的組織幫忙鎮民對抗。加入這個會便得通過考驗,於是主角在弟弟的協助下,接觸這個神秘的組織……

這作品令我難以作出評語。一方面,我很欣賞它前段的描寫,那種魔幻的手法令人摸不著頭腦,叫我不住幻想結局的可能--不過我卻怕故事會流於奇幻,脫離了推理的範圍。另一方面,結尾的寫實性叫我鬆一口氣,確認這是篇實在的推理作品--可是前後的落差之大,就像一心以為氣球該向上飄,卻突然發覺原來氣球灌了鉛一樣。

我想,這是美學的問題。以魔幻手法包裝的推理是很難寫的,一是像《奧杜邦的祈禱》那般建架出獨立而異常的世界觀,一是像上屆入圍作品《西巴斯貝之戀》合理地把魔幻的情節配合經過轉換、我們所認知的邏輯。這篇作品開首便導入了詭異的氛圍,像漫天的紅色氣球、注視著主角後方的視線、每天也抓著黑色氣球的弟弟……每一個元素也帶著強烈的吸引力,所以如何解釋(或不解釋)是讀者期待的重點。現在的結局在某程度上可說是反高潮,舉例說,在一個有幽靈出現的推理故事裡,如果最後解釋幽靈是角色假扮來嚇人的,總會令讀者覺得不外如是(就像科幻故事最後把反常現象的原委推到外星人身上)。

雖然謎團讓我五味雜陳,作者的文筆和人物的感情描寫也實在優秀,講故事的技巧也不俗,這作品真是叫我難以評論啊。

【徵文獎心得】63.殺貓季節

身為房東女兒的主角,在父母外遊時得處理房客和鄰居之間的麻煩--房客之一的鐘小姐的貓兒逐一被殺,凶手是誰?是看貓主人不順眼的年輕太太?是一樓的頑皮小鬼兄弟?是神秘兮兮的女房客?還是宣稱看到「空中飛貓」的新住客?

相當工整的作品。故事沒有重大的罪行(嗯,「連續殺貓事件」也是罪行,但總比不上「連續殺人事件」吧),有著日常生活之謎的趣味,作者以各位鄰居和房客為中心,構成一個既輕鬆又離奇的怪異事件。伏線和細節處理得很有條理,在謎團上每一個環節也互相關連,揭曉謎底時會讓讀者發覺每一件瑣事也有其意義,作者亦掌握了角色的心態,人物的描寫相當立體,這些都是本作的最大優點。

我覺得很難找這作品的缺點,事實上,我甚至覺得沒有什麼好建議的--這作品每一個元素也恰如其分地運作,從文筆至佈局、角色至敘事手法、謎團至解謎過程也互相配合。單以一篇作品而論,我覺得無可挑剔了。只是,縱使故事有趣有餘,我就總是覺得欠缺一份魅力。或者簡單來說,在這樣的格局下,這篇作品已充分表現出這格局裡最好的樣子,然而這個「格局」本身的吸引力和趣味性不及其他作品。有些作品雖有瑕疵,但當中有令人意外、發噱、驚嘆的點子,相比起來這一篇作品就好像欠一點什麼似的。或許這只是我個人的想法,無論如何,這篇作品也是上乘的推理作品。

2010年4月8日星期四

【徵文獎心得】62.風太大了,我聽不見

三位女生結伴到南部旅行,卻遇上颱風,與當護士的朋友會合時,得悉對方的義診團隊有人失蹤,其後在泥石流中找回屍體。死亡時間說明死者在前一晚已遇害,是意外還是被殺?團隊裡錯綜複雜的男女關係、沒有曝光的醜聞在事件中又擔當什麼角色?

不得不先說一句,這作品看得我很累。主要原因是作者很少分段,對話和描寫的句子都擠成一塊,大部分段落都上五百字甚至一千字,這令人很難消化。更甚者是,作者常用借喻、幽默、嘲諷的描寫手法,本來這是值得加分、讓讀者感到趣味的元素,可是段落太長、句子又不夠直接,讀者的腦袋便未必容易看懂作者的話語了。我不是說斷句成一句一段,或用上毫不修飾的文字便是好作品,只是想說,角色對話分成獨立的段落會讓讀者較易掌握內容,使用比喻的俏皮話不要過火,作者單是留意這兩點已能讓這作品更進一步。

推理方面,案件的起因、原委、過程都合理可信,結局也有它的趣味,可是有部分線索似有未說清之嫌(或許有說清,但基於上述原因讀者根本沒法看到),解謎的劇情出現時有點唐突。我覺得作者對推理邏輯是有不錯的能力的,嘲諷的修辭手法亦有種獨特的個性,如果他或她能整理一下描寫方法,更有條理地敘事,我相信下一篇作品會更有看頭。

【徵文獎心得】61.隨機預謀殺人提案

連環殺手殺害第四位受害人後,特意留下挑釁警方的信件,小劉擔任隊長的偵緝小隊奉命追查。然而,案情拖了數個月仍一無進展,凶手卻再一次犯案了……

不得不先說一句,這篇作品令我覺得十分惋惜。原因是這作品在文筆、格局、氣氛等等也處理得很出色,警方追查犯人的過程、推論邏輯也紮實可信,不少細節也顯出作者對辦案小說的認識和了解,可是核心的謎團卻沒追上其他優秀的元素,令作品的成績打了折扣。

故事先以凶手的角度來描寫犯案過程,情景和角色內心描述得鉅細無遺,很容易讓讀者投入;後來轉而描述警方的搜查過程,警方的每一步也合情合理,叫讀者期待他們到底如何找到破綻。組長小劉提出的想法、警員宋濂耍的手段也有趣精闢,滷蛋和艾利的吵吵鬧鬧為故事加添活潑的氣氛,這些都是十分不俗的設計。然而作為骨幹的謎團雖合理,卻會讓讀者感到失望--因為這不是因為警方精明而破案,而是因為凶手笨得犯下錯誤讓對方有機可乘。或許這個破綻正是作者想說明的主題「怎樣的凶嫌是最難抓」,可是在這個命題下,我想不少人也是想看看作者如何設計「完美的犯罪下警方怎樣找到缺口」,或是「完美的犯罪下犯人因為什麼事情而落網/逃之夭夭」,而不是「因為犯人在本來完美的犯罪加入了不完美的元素導致被警方抓辮子」。撇開這個要求,這篇作品相當紮實,足見作者的創作潛力。

2010年4月7日星期三

【徵文獎心得】60.索菲亞‧血色謎團

十五世紀的鄂圖曼帝國,蘇丹的選妃在邊境行省索菲亞的清真寺內離奇死亡,地方長官大感頭痛。這時,獄中一名非穆斯林的青年卻向侍衛表示可以找出真相--誰人在真主安拉的眼下作出如此殘忍的罪行?

※決選入圍作品

幾乎找不到話語來表達我對這作品的讚嘆。這作品不單能生動地描述十五世紀的伊斯蘭世界,更厲害的,是能配合豐富的知識、極其嚴謹考究的細節、加上傳說和歷史元素去創造出一篇推理故事。謎團引人入勝,看似誇張的情節最後不但能合理地解釋,更完美地跟現實掛勾。更特別的是,作者採用了大膽的敘事手法和角度,為本來已經不平凡的故事添上一個特別的意義,在結局時令人透不過氣。

作者以魔幻的手法去包裝故事,挑戰推理小說的可能性。這篇作品有三個層次--核心是一個典型的推理小說謎團,在特定地點死亡的死者,在一段時間內進出這地點的嫌犯,誰人說謊?為何說謊?往外推一層,是以古代伊斯蘭世界的設定包裹,在這個歐亞交界的迷城裡讓異邦人偵探解開重重疑團。最精采的是,在這層結構外,還有令人意外的真相、不可思議的故事架構、再披上一層歷史和傳說的外衣。在故事發想上我已覺得是一流,而作者在歷史考據、情景描寫上下了很多工夫,在對白裡連細微的地方也運用上相當可信的伊斯蘭世界用語,光這一點已叫人佩服。

身為一位喜歡歷史的推理創作者,我對這作品的評語不是「很好」或「出色」,而是「我也想寫出這樣的故事啊!」。喜歡非一般的推理小說的讀者,這是必看的作品。

2010年4月6日星期二

【徵文獎心得】59.沒有遺體的葬禮

企業家張寶城病逝,遺體竟被偷去,之後遺孀及看護被擄,偵探介入只救回護士,張氏妻子王櫻子連屍體也無法尋回。舉辦了兩個沒有遺體的葬禮,張家的司機陳大威決定探訪偵探,要找出事件的真相……

完成度高、思慮周詳的推理作品。作者的文筆流暢,以插敘的方式交替兩個時間兩個人物的經歷,做出「事前」和「事後」的對比,讓讀者更能投入故事,是十分不俗的設計。謎團的構造亦很完備,基本線索、角色心態也條理分明地呈現讀者眼前,具備優秀推理作品的條件。我覺得這個謎底的缺點是太明顯,我看到第四頁已猜想到答案,搞不好有人光從上述的簡介已推敲到原因;當然,公平性和意外性一向是繩子的兩端,愈是公平,愈難產生意外感,而作者在這篇作品裡有另一個補救辦法,在謎底之上多套上一個謎底之外的動機,用來顯示犯人的高明。這個點子我覺得基本上成功,但卻出現了另一個小瑕疵,便是當中偵探提到的細節有部分沒有在前文提及,公平及意外的天秤這一回便偏向了後者。

話雖如此,以上對「公平性和意外性」的要求是以一篇高水準的作品的角度來評論,因為我覺得這作品已是一篇優秀的作品,上述的意見只是以「如何精益求精」的方向提出。我沒弄錯的話,這篇作者有投稿另一篇作品,而那篇的優點在於角色的感情描寫,弱於邏輯上的闡述,如果作者能揉合兩篇的優點,我便認為他或她的下一篇作品更能超越現有的成績了。

2010年4月5日星期一

【徵文獎心得】58.熊胃

布染藝術家萊絲‧雪佛被殺,死狀有如傳說中泰王擊殺蛇妖之貌,在屍體旁邊有躲過凶手殺害、雪佛的寵物浣熊。獸醫譚愛夢從浣熊的嘔吐物中發現人骨--偶然到訪的主角柳翼,便以此為起點,偵查真正的凶手。

單以資料搜集的程度來說,這篇作品實在厲害。故事舞台設在泰國,劇情牽涉到泰國曼谷王朝的歷史、泰國布染工藝、泰國地理環境、古泰國文化……每個環節也十分仔細,描寫的事物具有強大的真實性,令人懷疑作者是否居於泰國,或是從事跟泰國相關的職業。謎團的設計也算完整,最後加上逆轉消除了好幾處的違和感,帶來一點驚喜。不過,這作品有兩點令我覺得不足,稍為可惜。

第一個問題來自文筆,作者的用詞華美,宛若瓊堆玉砌,初段寫泰王殺蛇妖一段猶可,往後卻過了火位。作者在文字上的雕琢令劇情不太易讀,更糟糕的是作者交代謎面欠缺條理,有部分應該透露的資訊沒有明寫。比方說,為什麼凶手的範圍縮小至四人?為什麼一開始沒否定搶劫殺人?譚愛夢為什麼查到人骨後沒通知警方?更重要的是她為什麼這麼閒去驗浣熊嘔吐物的DNA?(她很簡單說出人骨是男性,我實在不知道除了檢驗染色體外,有什麼方法可以從碎骨知道主人的性別)初段佈局時有點令我摸不著頭腦,當角色開始解謎我才完全掌握角色和劇情。令讀者了解謎面是很重要的,如果讀者不能跟偵探踏著相同的步伐,只會覺得偵探(或作者)在自說自話。

第二個問題是角色很神秘,甚至神秘得過分做作--也許這是作者特意安排的角色設定,可是我搞不懂故事最後主角所做的「消除」是什麼。這表示主角擁有奇異的力量?他是像某漫畫那般從魔界來的魔人偵探?抑或是某種文學上的暗示?就算真的要加入奇幻的元素也無不可,不過得小心讀者的觀感。

撇除這兩點,這作品令人印象深刻,氣氛的處理相當出眾,如果作者繼續發展這題材,我想會吸引一批讀者。

2010年4月4日星期日

【徵文獎心得】57.少女的祈禱

擔任替代役的主角阿觀在警局裡遇上一起奇異的案件--一名男子涉嫌車禍肇事逃逸,警方從監視器錄影發現他逃到女兒公寓,進行圍捕時卻發現男人人間蒸發。阿觀在警局的員警慫恿下加入調查,只是案情發展並不如他預計之內……

※決選入圍作品

這一篇作品,是這屆參賽稿件中唯一一篇令我鼻頭一酸,差點掉淚的作品。作者的敘事手法十分乾淨俐落,沒有用什麼華麗的修飾,但當中的情感卻真摰動人,故事愈往後發展,戲劇性便愈是濃郁。謎團沒有花巧的設計,表面看來十分平實,但在平凡中又顯出當中的睿智,配合角色的描寫,充分表現出故事的主題和深刻的人性。

這篇作品除了以上的優點外,作者的敘事節奏也幾乎天衣無縫。一般的推理作品通常單純地分成「謎面→推理→解謎」,這篇作品卻一開始便作出解謎,故事集中在主角找尋證據支持他的推理,之後再引出新謎面。也許有人說,「錯誤的推理→逆轉」的手法也沒有什麼大不了,但這作品的節奏的精準,在於連重覆的推理也是謎面之一。引導讀者思考、在最後結局前把急促的敘事節奏放緩,顯示作者說故事的功力。沒有破天荒的想法、沒有特別的主題、沒有異色的背景,單單以最簡單、最地道的材料,泡製出這樣一篇出色的作品,實在優秀得叫人無話可說。

2010年4月3日星期六

【徵文獎心得】56.蟑螂

流氓高文瑞被發現陳屍山上廢屋,處事胡混的探員劉銘向死者的妹妹高文雪打探,不滿劉銘工作態度的林正宇亦插手調查,察覺高家的鄰居吳俊偉似乎跟事件有關。不久後,街上傳出巨蟑出現廢屋的謠言,令事件蒙上詭異的陰影……

這是一篇以懸疑驚悚為主要賣點的推理作品。作者的文筆成熟,從描寫角色以至蟑螂的舉動亦栩栩如生,好些段落我一邊讀一邊錯覺眼前出現那些六隻腳的討厭鬼,而提到臭豆腐的氣味時,我也不由自主地擦了一下鼻子。角色的行動和心理亦十分自然,故事發展流暢,作者說故事的技巧可說是這篇作品最大的優點。

在推理方面卻有點瑕疵,我閱讀首三份之一時,滿以為是一篇本格派作品,畢竟一開始便交出屍體,連死因和環境也說明清楚,調查的警察也很快登場;可是,愈往下讀卻發覺劇情不斷擴展,故事並不是往解謎的方向走,反倒把謎題弄大了--角色之間都有不同而獨立的利害關係,而讀到最後時,我卻無法得到一個確切的謎底。縱使故事裡不少疑團已釐清,但其中幾個最主要的問題卻是採用開放式的結局,沒有交代原因,令我有點失望。當然,這或許是因為我一開始搞錯了類型,私自認為這是本格派的解謎式作品,讀完後再重新審視,以懸疑驚慄的角度來欣賞這作品大概更為合適。即便如此,我仍有一種言猶未止、意猶未盡的感覺,是好是壞則見仁見智了。

2010年4月2日星期五

【徵文獎心得】55.西遊記殺人事件

唐三藏一行四人往西天取經,途中再次遇上金角銀角大王,一同前往沙漠中的一條小村。小村舉行的祭典中,負責祈禱的姑娘在密室被殺,村民認定是孫悟空所為,把他關在禁閉室裡。一路上替師父解決不少殺人事件的齊天大聖,這次又如何為自己脫罪,擒拿真凶呢?

發想超級奇妙的作品。作者借用西遊記的故事,把唐三藏取西經路上遇上九九八十一件苦難的設定,換成「唐三藏會遇上九九八十一件殺人事件」,所以觀世音菩薩要曾偵破「王母娘娘壽宴仙桃殺人事件」的孫悟空拜唐僧為師,協助他解決一路上的難題。單以想法而言,這是十分好玩有趣的構想,作者的文筆亦十分流暢易讀,敘事有條理,照理應該是一篇不錯的作品。可是,讀完這篇二萬四千字的小說,我只能說句可惜了。

這作品最大的缺憾,是直接使用西遊記的設定。孫悟空依舊懂七十二變,妖怪依舊想吃唐僧肉,在這個奇幻的背景下,無法好好把推理元素溶入,令故事只能停留在奇幻作品的範疇。不依現實的推理作品是最難寫的--例如武俠推理,作者得先讓讀者充分了解武俠世界和我們一向所知的現實世界的不同,像「輕功」、「暗器」、「易容」等等,在武俠世界都是「合邏輯」的東西,如果有人死在密室裡,作者便得先解釋清楚這個世界有沒有「隔山打牛」的武功。另一個例子是科幻推理,作者沒說明故事裡的科技到底有多進步,讀者便會質疑推理的合理性。

這篇作品以孫悟空擔當名偵探,本身便是一個錯配。因為西遊記裡,任何在地上發生的案件也沒有調查的需要,大聖爺只要一踹腳,把土地公抓出來問個一清二楚,任何凶手也無所遁形。神仙本來就是超犯規的設定,如果要在這種天馬行空的背景下寫推理,一是把世界觀改變,一是把疑團縮小至心理懸疑(正所謂人心難測,懂法術也猜不到),否則會超難寫。此外,這篇作品對西遊記只是形似神不似,像老孫居然會被村民抓住已夠奇怪,被困到囚室時,即使師父有令合作,以他的性格大概會放個分身出來代替,自己變成蚊子出外查探吧。最令我疑惑的是,故事中說金角銀角二人本來是山賊,被孫悟空收伏後替村民辦事,洗心革面--這只是《七龍珠》的故事吧?原著中,金角銀角是菩薩給唐三藏他們的試練,二人本來是太上老君的看爐童子,沒有什麼作惡啊?或許作者覺得更動這個設定會更有趣,可是這更令故事的發展方向不明朗了。

如果作者銳意發展這個系列,挑戰「神仙推理」的類型,希望他或她能考慮一下在下的意見,找尋一個明確的方向,我相信出來的作品會更吸引。

2010年4月1日星期四

【徵文獎心得】54.cat X dead X pet

勝旻的寵物貓突然從家中消失了,好友光冀動用私人關係,到警局看附近的監視器錄影。觀看期間,勝旻得知當天出現一宗企業千金懷疑失蹤事件,究竟兩宗事件有沒有關係?

這篇作品構想有趣,但交代情節的部分卻不夠清楚,相當可惜。故事裡一些天馬行空的角色、不按常規的想法也挺有趣,問題是關鍵部分的企業千金失蹤事件說得不明不白,令人看得糊塗。報警通知警方千金小姐失蹤的那人到底是什麼身分?(除了故事中提出的那一個身分外)受害人的家人呢?抑或是相關的事情都是假象?故事前半段和結局也透露了作者的創作意圖,單就這一點來說我覺得滿精采的,可是作為連接的事件缺乏說服力,那是致命傷。

作者也得留心文字,雖然文筆甚為輕鬆,有時有些句子實在長得令人頭暈。像「畢竟現在大眾的想法不管是愛情觀還是社會觀如果兩個孤男寡女待在同一室內發生了關係一點也不會覺得不正常甚至還覺得如果沒發生什麼事的話男方一定膽小如鼠」,不好好加逗號分開一下,一般人就算明白也會覺得難讀喔。