Hangman on Silverlight

หลังจากติดค้างพี่ป๊อกมานาน ก็ได้เวลาใช้หนี้ซักที

เรามาดูกันดีกว่าว่าไอ้เจ้า Hangman นี่มันเขียนยากมั้ย


วันที่ 25 ม.ค. 2550 เวลา 3 โมงครึ่ง

เปิดโปรแกรม Microsoft Expression Blend มาวาดนู่นวาดนี่ แล้วก็แก้ไปแก้มา ก็เลยได้เป็นโค้ดนี้

<Canvas Width="32" Height="32" Canvas.Left="0" Canvas.Top="16" x:Name="ACanvas" Cursor="Hand">
    <Rectangle Width="32" Height="32" Fill="#FF697BCA" RadiusX="3" RadiusY="3" RenderTransformOrigin="0.5,0.5" x:Name="RectangleA">
        <Rectangle.RenderTransform>
            <TransformGroup>
                <ScaleTransform ScaleX="1" ScaleY="1"/>
            </TransformGroup>
        </Rectangle.RenderTransform>
    </Rectangle>
    <TextBlock Text="A" Canvas.Left="10" Canvas.Top="6"/>
</Canvas>

อันนี้คือสร้าง Canvas ขึ้นมา ไปวางไว้ที่พิกัด Canvas.Top กับ Canvas.Left ที่กำหนด โดยกำหนดให้ชื่อว่า ACanvas โดยเจ้า Canvas นี้ก็จะประกอบด้วย ข้อความ 1 ข้อความแสดงข้อความว่า A อยู่ที่พิกัด (Left, Top) คือ (10,6) และ สี่เหลี่ยม 1 อัน ขนาด 32 x 32 สีฟ้า ชื่อ RectangleA

จากนั้นก็จัดการเขียนสคริปต์สร้างเป็นโค้ด XAML สำหรับตัวอักษร 26 ตัว แหะๆ ตรงนี้ขี้โกงด้วยการใช้โค้ด Python สร้างเอา

ทำตรงนี้ได้ซักพัก เพื่อนก็เอาโค้ดมาให้ช่วยดู แล้วก็เลยนั่งแก้โค้ด แล้วเย็นนั้นก็ไปกินข้าวกับเพื่อนต่อ


วันที่ 25 ม.ค. 2550 เวลา 5 ทุ่ม

กลับถึงบ้าน ทำธุระส่วนตัวเรียบร้อยก็ได้เวลาเปิดคอมแล้วก็ …. คร่อก หลับไปแว้วววว


วันที่ 26 ม.ค. 2550 เวลาตีห้า

สะดุ้งตื่นพร้อมกับนึกขึ้นได้ว่ายังเขียนไม่เสร็จเลยเอามาเขียนต่อ คราวนี้จัดการเพิ่ม animation และ storyboard ลงไป ดังนี้

<Canvas.Resources>
            <Storyboard x:Name="ABigger">
                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="RectangleA" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
                            <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="2"/>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="RectangleA" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
                            <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="2"/>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="RectangleA" Storyboard.TargetProperty="(UIElement.Opacity)">
                            <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="0"/>
                    </DoubleAnimationUsingKeyFrames>
            </Storyboard>

อันนี้คือเราสร้าง Storyboard ขึ้นมา ชื่อว่า ABigger โดยข้างในจะประกอบด้วย animation ดังนี้
1. กำหนด ScaleX ของ RectangleA ไปเป็น 2 ในเวลา 0.5 วินาที
2. กำหนด ScaleY ของ RectangleA ไปเป็น 2 ในเวลา 0.5 วินาที
3. กำหนด Opacity ของ RectangleA ไปเป็น 0 ในเวลา 0.5 วินาที

ปรากฏว่าโอเค ก็เลยได้เวลาสคริปต์เจ้าเก่า จัดการโมซะ


วันที่ 26 ม.ค. 2550 เวลาตีห้าสี่สิบ

เริ่มเขียนตัวจัดการ event เลยเขียนตัวเล่น animation ก่อน

    private void buttonPressed(char p)
    {
            string canvasName = p + "Canvas";
            Canvas pressedCanvas = this.FindName(canvasName) as Canvas;
            pressedCanvas.Cursor = Cursors.Default;

            string animationName = p + "Bigger";
            Storyboard biggerAnimation = this.Resources.FindName(animationName) as Storyboard;
            biggerAnimation.Begin();


    }

ตรงนี้จะใช้สำหรับเวลาปุ่มโดนกด คือจะไปวิ่งทำให้ Canvas ของปุ่มนั้นไม่เปลี่ยนเคอเซอร์เวลาเอาเมาส์ไปชี้ แล้วก็จัดการเล่นแอนิเมชันของปุ่ม

แล้วค่อยเขียนตัว Event Handler แบบนี้

    void ACanvas_MouseLeftButtonDown(object sender, MouseEventArgs e)
    {
        buttonPressed('A');
    }

เพื่อบอกว่า ถ้าเกิดกดปุ่มนี้แล้วก็ให้ไปเรียกเมธอด buttonPressed ด้านบนพร้อมบอกด้วยว่ากดปุ่มไหน

แล้วก็ไปแก้หน้า XAML ให้เรียก event ที่ตรงกับที่สร้างไว้

<Canvas Width="32" Height="32" Canvas.Left="0" Canvas.Top="16" x:Name="ACanvas" Cursor="Hand" MouseLeftButtonDown="ACanvas_MouseLeftButtonDown">
    <Rectangle Width="32" Height="32" Fill="#FF697BCA" RadiusX="3" RadiusY="3" RenderTransformOrigin="0.5,0.5" x:Name="RectangleA">
        <Rectangle.RenderTransform>
            <TransformGroup>
                <ScaleTransform ScaleX="1" ScaleY="1"/>
            </TransformGroup>
        </Rectangle.RenderTransform>
    </Rectangle>
    <TextBlock Text="A" Canvas.Left="10" Canvas.Top="6"/>
</Canvas>

แล้วก็จัดการแก้โค้ด python ให้สร้างโค้ดออกมาให้ถูก


วันที่ 26 ม.ค. 2550 เวลาหกโมงห้านาที

จัดการเขียนโค้ดส่วนที่เป็นตัวตัดสินใจ ก็เลยแก้โค้ดตอนรันนิดหน่อยด้วยการสร้าง Hash ขึ้นมาเก็บว่าตัวไหนใช้ไปแล้วบ้าง พร้อมกับคำตอบที่ถูกต้อง

    private Dictionary<char, bool> usedCharMap = new Dictionary<char, bool>(); 
    private string answerKey = "BARCAMP BANGKOK";

แล้วก็สร้างเมธอด reset เพื่อใช้สำหรับเริ่มเกมใหม่

    private void initCharMap()
    {
        usedCharMap.Clear();
        for (int i = 0; i < 26; i++)
        {
            usedCharMap.Add((char)('A' + i), false);
        }
    }
    private int badCount = 0;
    private void reset()
    {
        badCount = 0;
        for (int i = 0; i < 26; i++)
        {
            string animationName = (char)('A' + i) + "Bigger";
            Storyboard biggerAnimation = this.Resources.FindName(animationName) as Storyboard;
            biggerAnimation.Stop();
        }
        initCharMap();           
    }

ตอน reset ก็จะจัดการหยุดแอนิเมชันทั้งหมดไปเลยด้วย พร้อมทั้งสร้าง hash ใหม่ด้วย
แล้วตอนอีเวนท์ Page Load ก็สั่ง reset ซะหนึ่งที
private void Page_Loaded(object sender, EventArgs args)
{
// Required to initialize variables. Needs to be done from loaded event so FindName works properly.
InitializeComponent();

        // Insert code required on object creation below this point.
        reset();
    }

แล้วก็เพิ่มข้อความที่ใช้บอกสถานะ และขีดที่บอกคำศัพท์เข้าไปใน XAML

<Canvas Width="48" Height="26" Canvas.Left="32" Canvas.Top="233" MouseLeftButtonDown="ResetButton_MouseLeftButtonDown">

    <Rectangle Fill="#FFFFFFFF" Stroke="#FF000000" Width="48" Height="20"/>

    <TextBlock Text="Reset" TextWrapping="Wrap"/>

</Canvas>

<TextBlock Canvas.Left="32" Canvas.Top="185" Text="TextBlock" TextWrapping="Wrap" x:Name="AnswerTextBlock" FontSize="20"/>

<TextBlock Width="63" Height="19" Canvas.Left="545" Canvas.Top="233" Text="" TextWrapping="Wrap" RenderTransformOrigin="0.5,0.5" Foreground="#FFFF0000" x:Name="statusTextBlock">
    <TextBlock.RenderTransform>
        <TransformGroup>
            <ScaleTransform ScaleX="1" ScaleY="1"/>
            <SkewTransform AngleX="0" AngleY="0"/>
            <RotateTransform Angle="0"/>
            <TranslateTransform X="0" Y="0"/>
        </TransformGroup>
    </TextBlock.RenderTransform>
</TextBlock>

วันที่ 26 ม.ค. 2550 เวลาหกโมงครึ่ง

โค้ดที่ตัดสินใจจริงๆยังไม่เสร็จก็เลยเขียนเมธอดที่ใช้สร้างตัวว่างๆสำหรับคำตอบขึ้นมา

    private void createBlindStringAndCheckMatch()
    {
        string newText = "";
        bool match = true;
        foreach (char i in answerKey.ToCharArray())
        {
            if (i == ' ')
            {
                newText += "  ";
            }
            else if (usedCharMap[i])
            {
                newText += i;
            }
            else
            {
                newText += "_";
                match = false;
            }
            newText += "  ";
        }
        allMatch = match;
        AnswerTextBlock.Text = newText;
        statusTextBlock.Text = "Wrong guess : " + badCount;
    }

จัดการสร้าง blind string ด้วยการวนไปครบทุกตัว ตัวไหนที่ตอบถูกก็แสดงขึ้นมา ส่วนตัวที่ยังตอบไม่ถูกก็ใช้เป็นขีดล่างแทน


วันที่ 26 ม.ค. 2550 เวลาหกโมงสี่สิบ

ใส่เงื่อนไขการชนะ แล้วก็แก้ไขตอนที่กดปุ่มไปนิดหน่อยว่าถ้าตอบผิดเกิน 10 ครั้งก็ให้หยุดเล่น

    private void buttonPressed(char p)
    {
        if (!allMatch && badCount < 10 && !usedCharMap[p])
        {
            string canvasName = p + "Canvas";
            Canvas pressedCanvas = this.FindName(canvasName) as Canvas;
            pressedCanvas.Cursor = Cursors.Default;

            string animationName = p + "Bigger";
            Storyboard biggerAnimation = this.Resources.FindName(animationName) as Storyboard;
            biggerAnimation.Begin();

            usedCharMap[p] = true;
            if ((answerKey.IndexOf(p) < 0))
            {
                badCount++;
            }
            createBlindStringAndCheckMatch();
            checkWinLoseCondition();
        }
    }

    private void checkWinLoseCondition()
    {
        if (allMatch)
        {
            statusTextBlock.Text = "You win";
        }
        else if (badCount >= 10)
        {
            statusTextBlock.Text = "You lose";
        }
    }

วันที่ 26 ม.ค. 2550 เวลาเจ็ดโมง

ตอนนี้ก็ถึงเวลาว่าจะทำยังไงให้คำมันเปลี่ยนเองทุกครั้งที่ reset

ตอนแรกใช้วิธีดึงจาก Text file แต่ไม่ผ่าน

จากนั้นก็เปลี่ยนไปเรียก Web Service ก็เลยสร้าง Web Service ขึ้นมา ชื่อว่า WordService

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
[System.Web.Script.Services.ScriptService]
public class Service1 : System.Web.Services.WebService
{
    private static List<string> _wordList;
    private static List<string> WordList
    {
        get
        {
            if (_wordList == null)
            {
                _wordList = new List<string>();
                FileStream fs = File.OpenRead(HttpContext.Current.Server.MapPath("~/App_data/full.dic"));
                StreamReader reader = new StreamReader(fs);
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    _wordList.Add( line.Substring(0, line.IndexOf("\t")).ToUpper());
                }
            }

            return _wordList;
        }
    }
    [WebMethod]
    [ScriptMethod]
    public string GetRandomWord()
    {
        Random r = new Random();
        return WordList[r.Next(WordList.Count)];
    }
}

จัดการเพิ่ม Web Reference ไปยัง Word Service แล้วก็สร้างเป็นคลาสสำหรับดึงข้อมูลมาดังนี้

public class DictHelper
{
    public string GetRandomWord()
    {
        return new dic.Service1().GetRandomWord();
    }
}

แล้วก็เลยแก้ให้มาเรียกใช้ส่วนนี้ซะ

    private void reset()
    {
        badCount = 0;
        for (int i = 0; i < 26; i++)
        {
            string animationName = (char)('A' + i) + "Bigger";
            Storyboard biggerAnimation = this.Resources.FindName(animationName) as Storyboard;
            biggerAnimation.Stop();
        }
        initCharMap();

        answerKey = dic.GetRandomWord();

        createBlindStringAndCheckMatch();
        statusTextBlock.Text = "";
    }

วันที่ 25 ม.ค. 2550 เวลาเจ็ดโมงสี่สิบ

เย้ เย เสร็จซะที ง่ายจัง

Flickr ASP.NET MVC app pt.2 – ajaxified

After we have finished the Flickr MVC app last time. This time will be sequel, ajaxified it.

Although the ASP.NET MVC do provide the abstract ajax helper class, only implementation exists publicly is Nikhil’s. Certainly, ASP.NET Team will include the ASP.NET AJAX/Microsoft AJAX Library helper in the MVC framework for next CTP bits. As for this tutorial, I will continue with jquery instead of Microsoft AJAX Library because I’m more familiar with it.

The ajaxified instructions is mainly related to the view. In order to use jquery, put the library in ~/Content and add the reference to it in the layout.

<head runat="server">
    <title>My Sample MVC Application</title>
    <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="../../Content/jquery.js"></script>
</head>

Next, Open the ~/Views/Home/Index.aspx. Then, add a placeholder div called “result” in the view. We’ll use this div to display the response from the ajax call. Additionally, add a eye-candy loading indicator to let user knows that the page is being loaded.

<%Html.Form("Search", "Home", FormExtensions.FormMethod.post, new { ID = "theform" });%>
    <label for="tags">
        Tags:
    </label>
    <%=Html.TextBox("tags", 30) %>
    <%=Html.SubmitButton("find", "Find", "") %>
    <img src="../../Content/loading.gif" id="spinner" style="display:none;" />
    </form>
<div id="result"></div>

Last but not least, add some lines of script to make an ajax call once the form is goin to be submitted.

 $(document).ready(function() {        
        $('#theform').submit(function (){            
            $.post('/Home/Search', {'tags':$('#tags')[0].value}, completeRequest);
            $('#spinner').show();                        
            $('#result').slideUp(1500);
            return false;            
        });
    });
    
    function completeRequest(r){            
        $('#result').html(r).slideDown(1500);                            
        $('#spinner').hide();        
    }

The script will be invoked when the DOM is ready. It’ll hook the form submit event, post the textbox value to ~/Home/Search, make the loading indicator visible, hide the result div and then, cancel the event to prevent the full post process. And then, after the request is completed, completeRequest function will be called. It will replace the div’s content with the result from the response and then reveal the div by using slide down effect. And lastly, hide the indicator.

Try the page and Voila! The ajaxified is completed.

Compare to the traditional web form, MVC has a better degree to integrate with the javascript library. Though I use the web form view engine, extension method on the HTML Helper that makes the markup more flexible, no generated id. However, integration with the jquery may not be the easiest one for ASP.NET MVC as ASP.NET Team will deliver its own Ajax helper.

In conclusion, AJAX in ASP.NET MVC has a lot of room for improvement, either officially or unofficially. ASP.NET AJAX becomes useless when using it with ASP.NET MVC. However, using the alternative libraries in MVC require less effort than the traditional way. jQquery and ExtJs are the most notable. Besides, mootools and YUI are also gaining their share. Anyway, Microsoft will push ASP.NET AJAX little ahead those 3rd party library through their Ajax Helper once again.

ASP.NET MVC Ajax

After the long weekend, Scott announced the ASP.NET 3.5 Extension which includes the preview version of long-waited MVC Framework, ASP.NET MVC. Today, there are many websites and blogs regarding ASP.NET MVC. However, just a few of them are talking about AJAX.

In Web form model of ASP.NET, ASP.NET AJAX, along with AJAX Control Toolkit, is the major player for AJAX library. Since ASP.NET AJAX relies on Web form, migrating it to use with ASP.NET MVC which is just a beta bit is not really worth. But in the end, Microsoft will push its AJAX product towards ASP.NET MVC as soon as the latter is ready for production. This can be seen in Nikhil’s post about AJAX in ASP.NET MVC. Though Nikhil uses his Script# to create the javascript library, the concept utilizes the behavior/component things which is the concept of ASP.NET AJAX/AJAX Control toolkit. No doubt that ASP.NET AJAX will be the one that shipped with ASP.NET MVC.

As for alternative, jQuery seems to be the winner at the time of writing. Moreover, The latest survey reveals that the most favorite alternative javascript library among ASP.NET developer is jQuery and I don’t think ASP.NET MVC will differs from its counterpart. jQuery, in fact , is the fourth but the first two places are in paired, the second actually is not the javascript library so I count jQuery as the most favorite one. However, mootools, prototype/script.aculo.us will gaining their share in MVC as many people will go to ASP.NET for its MVC.