หลังจากติดค้างพี่ป๊อกมานาน ก็ได้เวลาใช้หนี้ซักที
เรามาดูกันดีกว่าว่าไอ้เจ้า 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 เวลาเจ็ดโมงสี่สิบ
เย้ เย เสร็จซะที ง่ายจัง

Comments
หรูสุดๆ มีปุ่มตัวอักษรเด้งดึ๋งด้วย
แต่ผมว่า มันน่าจะทำเป็น component หรือ widget ได้นะ
แล้วค่อยเอามาสร้างเป็น ปุ่ม a-z อีกที
ไว้ผมจะดูใน flex บ้างว่าสามารถสร้าง effect แบบที่คุณทำได้ไหม
@pphetra ทำเป็น component ได้แน่ๆครับ แต่คราวก่อนผมลองแล้วมันไม่สามารถ (จริงๆแล้วคือผมไม่สามารถ)
I think this article done a great job.What a best way to describe your view. Thanks for sharing with us. Really like your informative article. Hopefully we will get more interesting topic from you in future.
Post new comment