Friday, July 13, 2007

Lists and Lists

Lists and Lists
อาฮ้า เกมส์นี้เขียนขึ้นเมื่อปี 1996 เป็นเกมส์ประเภท IF (interactive fiction)
แต่จุดเด่นของเกมส์นี้ก็คือ มันเป็น programming tuturial ด้วย
ใครอยากรู้จัก scheme ลองเล่นเกมส์นี้ดูได้

Note: ที่ลง link ไว้เป็น java applet ที่เข้าเล่นเกมส์เลย
แต่ถ้าใครอยาก download ไว้เล่นที่เครื่องตัวเอง
ต้องไปที่นี่ Link

Related link from Roti

Thursday, July 12, 2007

FlexUnit

เมื่อวานนั่งทดลองเขียนเกมส์ Hangman บน Flex ดู
สิ่งที่ขัดอกขัดใจก็คือ มันไม่มี Unit Testing มาให้

ซึ่งก็จริงของมันในแง่หนึ่ง ก็คือ
component ที่เป็น UI component
มันทำ testing แบบ Unit ได้ยาก (ก็เลยไม่มีเสียเลย แต่ก็ไปแอบมี third-party ทำ GUI Testing plugin แทน)
แต่เผอิญว่า component ของเรา มันเป็น Non-visual component
และต้องการทดสอบความถูกต้อง ก่อนที่จะ integrate กับ UI component

จากการ search พบว่า มันมี library ที่ชื่อ FlexUnit อยู่เหมือนกัน
(อยู่ใน adobe labs project ด้วย)
ก็เลย load มาทดสอบดู

การใช้งาน ก็คือ สร้าง MXML Application ขึ้นมาตัวหนึ่ง
มีหน้าตาแบบนี้

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:flexunit="flexunit.flexui.*"
creationComplete="onCreationComplete()"
>
<mx:Script>
<![CDATA[
import flexunit.framework.TestSuite;
import hangman.test.GameTest;

// After everything is built, configure the test
// runner to use the appropriate test suite and
// kick off the unit tests
private function onCreationComplete():void
{
testRunner.test = createSuite();
testRunner.startTest();
}

// Creates the test suite to run
private function createSuite():TestSuite {
var ts:TestSuite = new TestSuite(GameTest);
return ts;
}

]]>
</mx:Script>

<!-- flexunit provides a very handy default test runner GUI -->
<flexunit:TestRunnerBase id="testRunner" width="100%" height="100%" />
</mx:Application>


ตัว TestCase ก็เขียนเหมือน XUnit ทั่วๆไป

package hangman.test
{
import flexunit.framework.TestCase;
import hangman.core.Game;

public class GameTest extends TestCase
{
public function testNewGame():void
{
var game:Game = new Game();
game.word = "hello";
assertEquals("_____", game.unreveal);
}

public function testGuess():void
{
var game:Game = new Game();
game.word = "hello";
game.guess("h");
assertEquals("h____", game.unreveal);
game.guess("l");
assertEquals("h_ll_", game.unreveal);
}

public function testGuessMistake():void
{
var game:Game = new Game();
game.word = "hello";
game.guess("a");
assertEquals(1, game.mistake);
game.guess("b");
assertEquals(2, game.mistake);
assertEquals("_____", game.unreveal);
}
}
}


เวลา run ก็ใช้ได้ผลลัพท์ออกมาใน browser แบบนี้



ข้อสังเกต
1. unit test ไม่ได้ integrate ใน flex-builder ทำให้เราไม่สามารถกระโดดจากผลลัพท์
ไปยัง source code ได้โดยตรง
2. กรณีที่มี testcase, flex ยังไม่มี best-practice ของการจัด Project Layout
ว่าควรจะเขียน testcase แยกเป็นอีก project หรือ รวมไว้ใน project เดียวกัน
ถ้ารวมจะจัด package หรือ folder structure อย่างไรดี

Related link from Roti

Tuesday, July 10, 2007

ทดลอง implement widget บน GWT

ช่วงนี้ผมกำลังทดลองเขียนโปรแกรม hangman
เพื่อจะได้ทดสอบ framework ต่างๆเปรียบเทียบกัน
โดยต้นแบบของเกมส์ ได้มาจาก code ตัวอย่างใน cincom smalltalk
ที่ใช้ seaside เป็น framework

บน game Hangman, user สามารถกดเลือกตัวอักษรที่จะทายได้
โดยตัวอักษรพวกนี้ จะ render เป็น hyperlink ไว้
และเมื่อ user กดเลือกไปแล้ว, hyperlink นั้นก็จะกลายเป็น text ธรรมดา
เพื่อเป็น feedback ให้ user รู้ว่าได้ทำการเลือกไปแล้ว



ผมลอง implement feature ที่ว่าเป็น widget ใน GWT ดู
เกริ่นก่อนว่า, ใน GWT ก็มี widget ประเภท hyperlink ให้ใช้
แต่มีข้อจำกัดว่า ไม่สามารถทำให้มันกลายเป็น text ธรรมดาได้
เราก็เลยต้อง customize ทำ hyperlink แบบพิเศษของเราขึ้นมาเอง

ทางเลือกในการ implement hyperlink แบบพิเศษของเรา มีอยู่ 2 ทางคือ
1. extends จาก hyperlink ของเดิม แล้ว overide method บางตัว เพื่อปรับพฤติกรรม
2. เขียนขึ้นมาเอง โดยไม่ยุ่งกับ HyperLink widgetเลย
ผมเลือกใช้วิธีที่ 2 เพื่อจะได้ทำความเข้าใจกับกลไกการทำงานของ GWT มากขึ้น

เริ่มแรกสุด ก็คือเลือก extend widget ของเราจาก FocusWidget,
โดย FocusWidget คือ base class สำหรับ widget ที่สามารถ click
หรือเรียกใช้ผ่าน access key ได้
public class Letter extends FocusWidget {

}

ขั้นถัดมา ก็คือ implement ส่วนที่เกี่ยวกับ การ render
วิธีการที่ GWT ใช้ render widget ก็คือ render เป็น DOM element
public class Letter extends Widget {
private Element anchorElem;
private char ch;

public Letter(char ch) {
this.ch = ch;
setElement(DOM.createSpan());
DOM.appendChild(getElement(), anchorElem = DOM.createAnchor());
DOM.setInnerText(anchorElem, Character.toString(ch));
DOM.setElementProperty(anchorElem, "href", "#");
}
}

ผลลัพท์ที่ได้จากข้างบน ก็คือเราจะได้ html element ดังนี้
<span><a href="#"></a></span>


ข้อสงสัยก็คือ ถ้า render แค่นี้แล้ว GWT จะรับ javascript event onclick ได้อย่างไร
คำตอบก็คือ ถ้าเราไปไล่ code ของ FocusWidget ดู
เราจะเห็นว่าหลังจากที่เราสั่ง setElement
code ใน FocusWidget จะทำการเรียกใช้
sinkEvents(Event.ONCLICK | Event.FOCUSEVENTS | Event.KEYEVENTS);

ซึ่งเป็นการระบุว่า widget เรา สามารถรับ event กลุ่ม click, focus, keyevent ได้
โดย callback ที่ต้อง implement เพื่อรับ event ก็คือ method onBrowserEvent
ในกรณีของเรา FocusWidget มีการ implement onBrowserEvent ไว้ดังนี้แล้ว
public void onBrowserEvent(Event event) {

switch (DOM.eventGetType(event)) {
case Event.ONCLICK:
if (clickListeners != null) {
clickListeners.fireClick(this);
}
break;
...
}
}

สิ่งที่เราต้องการ overide ก็คือ เมื่อเกิด clickEvent แล้ว
เราต้องการให้ hyperlink ของเรา click อีกไม่ได้
ซึ่งทำง่ายๆโดย เอา attribute href ออกจาก anchor tag
public void onBrowserEvent(Event event) {
super.onBrowserEvent(event);
if (DOM.eventGetType(event) == Event.ONCLICK) {
DOM.removeElementAttribute(anchorElem, "href");
DOM.eventPreventDefault(event);
}
}


เมื่อได้ตัวอักษรเดี่ยวๆแล้ว ก็มาลองทำ widget ที่ช่วย render ชุดตัวอักษรบ้าง
โดย widget นี้เราจะนำเอา Letter widget ที่เราพึ่งทำไปมาเรียงต่อๆกัน
ในกรณีนี้ widget ที่เราจะเขียน จะ extend จาก Composite
public class Letters extends Composite implements ClickListener {

private Letter[] letters;

public Letters(String chars) {
FlowPanel panel = new FlowPanel();
char[] chs = chars.toCharArray();
letters = new Letter[chs.length];
for (int i = 0; i < chs.length; i++) {
letters[i] = new Letter(chs[i]);
letters[i].addClickListener(this);
panel.add(letters[i]);
panel.add(new Space(1));
}
initWidget(panel);
}

...

}


ความรู้สึกที่ได้จากการทดลอง implement widget ใน GWT ก็คือ
1. ความรู้สึกก้ำกึ่ง ระหว่างความชอบ กับ ไม่ชอบ
เนื่องจาก บางอย่าง ถ้าเราเขียนด้วย javascript โดยตรง มันจะง่ายกว่า
แต่บางอย่าง เขียนด้วย GWT ก็จะง่ายกว่า (เช่น sinkEvent)
2. GWT มันไม่ support feature ของ Java 1.5
ทำให้รู้สึกเย่นเย้อเวลาเขียนบางอย่าง เช่นพวก for loop
หรือต้องใส่ casting เวลา access collection

Related link from Roti

Monday, July 09, 2007

LEL

โปรเจค Profligacy เป็นโปรเจค ที่ช่วยให้เรา paint swing component
โดยใช้ JRuby เข้ามาช่วย
สิ่งที่น่าสนใจในโปรเจคนี้ก็คือแนวคิดเรื่อง LEL (Layout Expression Language)
โดยนำแนวคิดของเรื่อง table format ในพวก wiki มาผสมกับ Constraint programming
ก็เลยได้ออกมาเป็นวิธีใหม่ในการวาง layout

ลองดูตัวอย่าง code
require 'profligacy/swing'
require 'profligacy/lel'

module Test
include_package 'javax.swing'
include Profligacy

layout = "
[ label_1 | label3 ]
[ (300,300)*text1 | (150)people ]
[ <label2 | _ ]
[ message | buttons ]
"


ui = Swing::LEL.new(JFrame,layout) do |c,i|
c.label_1 = JLabel.new "The chat:"
c.label2 = JLabel.new "What you're saying:"
c.label3 = JLabel.new "The people:"
c.text1 = JTextArea.new
c.people = JComboBox.new
c.message = JTextArea.new

c.buttons = Swing::LEL.new(JPanel, "[send|hate|quit]") do |c,i|
c.send = JButton.new "Send"
c.hate = JButton.new "Hate"
c.quit = JButton.new "Quit"
end.build :auto_create_container_gaps => false
end

ui.build(:args => "Simple LEL Example")
end


จะเห็นว่า main format เขาวางไว้แบบนี้
[ label_1         | label3      ]
[ (300,300)*text1 | (150)people ]
[ <label2 | _ ]
[ message | buttons ]


คำอธิบายก็คือ layout จะเป็น grid ที่มี 4 แถว 2 column,
< หมายถึง ชิดซ้าย
> หมายถึง ชิดขวา
* คือ expand ให้เต็มพื้นที่
_ หมายถึง empty cell
(150) หมายถึง กว้าง 150 pixel
(300,300) กว้าง 300 ยาว 300 pixel

ผลลัพท์ที่ได้ ลองตามไปดูที่นี่
http://ihate.rubyforge.org/profligacy/images/sample_lel_gui_nested.png

Related link from Roti