<?xml version="1.0" encoding="UTF-8" ?><rdf:RDF 
  xmlns="http://purl.org/rss/1.0/"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xml:lang="ja">
  <channel rdf:about="http://w.atwiki.jp/wesnoth/">
    <title>Create of Wesnoth@wiki</title>
    <link>http://w.atwiki.jp/wesnoth/</link>
    <atom:link href="https://w.atwiki.jp/wesnoth/rss10.xml" rel="self" type="application/rss+xml" />
    <atom:link rel="hub" href="https://pubsubhubbub.appspot.com" />
    <description>Create of Wesnoth@wiki</description>

    <dc:language>ja</dc:language>
    <dc:date>2009-02-18T13:59:49+09:00</dc:date>
    <utime>1234933189</utime>

    <items>
      <rdf:Seq>
                <rdf:li rdf:resource="https://w.atwiki.jp/wesnoth/pages/20.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/wesnoth/pages/2.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/wesnoth/pages/1.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/wesnoth/pages/14.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/wesnoth/pages/15.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/wesnoth/pages/18.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/wesnoth/pages/19.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/wesnoth/pages/17.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/wesnoth/pages/16.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/wesnoth/pages/13.html" />
              </rdf:Seq>
    </items>
	
		
    
  </channel>
    <item rdf:about="https://w.atwiki.jp/wesnoth/pages/20.html">
    <title>sample.py</title>
    <link>https://w.atwiki.jp/wesnoth/pages/20.html</link>
    <description>
      このページはais/python/sample.pyのコメントを訳したものです。

**sample.py

#codehighlight(python){{

#!WPY

&quot;&quot;&quot;これは必要最低限のpythonAIの例です&quot;&quot;&quot;

import ai as wesnoth
import heapq

def pos(location):
    &quot;&quot;&quot;デバッグメッセージでポジションを表示するためだけの関数&quot;&quot;&quot;
    return &quot;(%d, %d)&quot; % (1 + location.x, 1 + location.y)

def debug(string):
    pass

class AI:
    &quot;&quot;&quot;AIのメインクラス&quot;&quot;&quot;

    def __init__(self):
        &quot;&quot;&quot;このクラスはAIのターン毎に構成されます。
        定期的に値を取得し続けるため、またゲームをセーブした時のために、
        set_variableとget_variableを使用します。&quot;&quot;&quot;

        self.team = wesnoth.get_current_team()
        self.village_radius = 25
        self.scout_villages = 3

        self.recruit()

        self.fight()

        self.conquer()

    def conquer(self):
        &quot;&quot;&quot;村を占領しようとします&quot;&quot;&quot;
        villages = self.find_villages()
        units =  wesnoth.get_destinations_by_unit().keys()

        #(distance, unit, village)というリストを作成
        queue = []
        for village in villages:
            for unit in units:
                d = self.get_distance(unit, village)
                if d != None: heapq.heappush(queue, (d, unit, village))

        #次にユニットを村に指定し、村まで移動させます
        while queue:
            d, unit, village = heapq.heappop(queue)
            if unit in units and village in villages:
                units.remove(unit)
                villages.remove(village)
                self.go_to(unit, village)

                if not units: break
                if not villages: break

    def cumulate_damage(self, cumulated, hitpoints, new_damage):
        cumulated2 = {}
        for already, ap in cumulated.iteritems():
            for hp, probability in new_damage.iteritems():
                damage = int(already + hitpoints - hp)
                cumulated2[damage] = cumulated2.get(damage, 0) + ap * probability
        return cumulated2

    def danger_estimate(self, unit, where, enemy):
        &quot;&quot;&quot;敵がどこにいて攻撃されるかどうかの危険度の情報を得ます&quot;&quot;&quot;

        scores = []
        u = wesnoth.get_units()[unit]
        e = wesnoth.get_units()[enemy]
        u_defense = u.defense_modifier(wesnoth.get_map(), where)
        e_defense = e.defense_modifier(wesnoth.get_map(), enemy)

        u_bonus = 100 - (u.type().alignment - 1) * wesnoth.get_gamestatus().lawful_bonus
        e_bonus = 100 - (e.type().alignment - 1) * wesnoth.get_gamestatus().lawful_bonus

        for attack in e.attacks():
            score = attack.damage * attack.num_attacks * e_bonus / 100
            score *= u_defense
            score *= u.damage_against(attack) / 100

            back = []
            for retaliation in u.attacks():
                if attack.range == retaliation.range:
                    x = retaliation.damage * retaliation.num_attacks * u_bonus / 100
                    x *= e_defense
                    x *= e.damage_against(retaliation) / 100
                    back.append(x)

            if back:
                r = max(back)
                score -= r
            heapq.heappush(scores, score)

        return scores[0]

    def danger(self, unit, location):
        &quot;&quot;&quot;ユニットがその場所を動いた時の危険度を見積もります。&quot;&quot;&quot;
        attackers = []
        for enemy, destinations in wesnoth.get_enemy_destinations_by_unit():
            for tile in wesnoth.get_adjacent_tiles(unit):
                if tile in destinations:
                    heuristic = danger_estimate(unitm, location, enemy)
                    if heuristic &gt; 0:
                        heapq.heappush(attackers, (-heuristic, enemy, tile))
        result = 0
        already = {}
        while attackers:
            danger, enemy, tile = heapq.heappop(attackers)
            if not already[enemy] and not already[tile]:
                danger = -danger
                result += danger
                already[enemy] = 1
                already[tile] = 1
        return result

    def fight(self):
        &quot;&quot;&quot;敵に攻撃します&quot;&quot;&quot;
        enemies =  wesnoth.get_enemy_destinations_by_unit().keys()
        units =  wesnoth.get_destinations_by_unit().keys()

        # 味方が倒す事が可能かつ倒せる可能性がある全てのユニットのリストを取得します。
        # これはヒューリスティックであり、ZoCとユニットの配置は無視します。
        kills = []
        for enemy in enemies:
            e = wesnoth.get_units()[enemy]
            k = {0: 1.0}
            for unit, destinations in wesnoth.get_destinations_by_unit().iteritems():
                u = wesnoth.get_units()[unit]
                for tile in wesnoth.get_adjacent_tiles(enemy):
                    if tile in destinations:
                        own_hp, enemy_hp = u.attack_statistics(tile, enemy)
                        k = self.cumulate_damage(k, e.hitpoints, enemy_hp)
            ctk = 0
            for damage, p in k.iteritems():
                if damage &gt;= e.hitpoints:
                    ctk += p
            if ctk:
                heapq.heappush(kills, (-ctk, enemy))

        # 現在の自身のユニットが敵を倒せる場所を見つけます
        attacks = []
        while kills:
            ctk, enemy = heapq.heappop(kills)
            e = wesnoth.get_units()[enemy]
            ctk = -ctk
            for tile in wesnoth.get_adjacent_tiles(enemy):
                for unit in wesnoth.get_units_by_destination().get(tile, []):
                    u = wesnoth.get_units()[unit]
                    own_hp, enemy_hp = u.attack_statistics(tile, enemy)
                    score = e.hitpoints - sum([x[0] * x[1] for x in enemy_hp.iteritems()])
                    score -= u.hitpoints - sum([x[0] * x[1] for x in own_hp.iteritems()])

                    # 攻撃の確率が同じ場所が2つあった場合、地勢のいい方を選択します
                    score *= 50 / u.defense_modifier(tile)

                    heapq.heappush(attacks, (-score, unit, tile, enemy))
                    #own_hpとenemy_hpを表示
                    debug(&quot;Score for %s at %s: %s&lt;-&gt;%s: %f [%s]&quot; % (u.name,
                        pos(unit), pos(tile), pos(enemy), score, e.name))

        # 次にユニットを敵に指定して、移動、攻撃させます
        while attacks:
            score, unit, tile, enemy = heapq.heappop(attacks)
            score = -score

            if unit in units and enemy in enemies:
                #try:
                loc = wesnoth.move_unit(unit, tile)
                #except ValueError:
                #    loc = None
                if loc == tile:
                    e = wesnoth.get_units()[enemy]
                    wesnoth.attack_unit(tile, enemy)
                    if not e.is_valid:
                        enemies.remove(enemy)
                    units.remove(unit)
                    if not units: break

    def recruit(self):
        &quot;&quot;&quot;ユニットを召還します&quot;&quot;&quot;

        # 最初に残っている金があるか確認します
        cheapest = min([x.cost for x in self.team.recruits()])
        if self.team.gold &lt; cheapest: return

        # マップ上での主塔を確認します
        keeps = self.find_keeps()

        #リーダーを取得
        leader = None
        for location, unit in wesnoth.get_units().iteritems():
            if unit.side == self.team.side and unit.can_recruit:
                leader = location
                break

        # リーダーの近くにある占領した村の数を取得
        villages = len([x for x in self.find_villages()
            if leader.distance_to(x) &lt; self.village_radius])

        units_recruited = int(wesnoth.get_variable(&quot;units_recruited&quot;) or 0)

        def attack_score(u1, u2):
            &quot;&quot;&quot;u1が u2に攻撃した時の大体のスコア&quot;&quot;&quot;
            maxdeal = 0
            for attack in u1.attacks():
                deal = attack.damage * attack.num_attacks
                deal *= u2.damage_from(attack) / 100.0
                for defense in u2.attacks():
                    if attack.range == defense.range:
                        receive = defense.damage * defense.num_attacks
                        receive *= u1.damage_from(defense) / 100.0
                        deal -= receive
                if deal &gt; maxdeal: maxdeal = deal
            return maxdeal

        def recruit_score(recruit, speed, defense, aggression, resistance):
            &quot;&quot;&quot;あるユニットのタイプを召還するためのスコア&quot;&quot;&quot;
            need_for_speed = 3 * (villages / self.scout_villages -
                units_recruited)
            if need_for_speed &lt; 0: need_for_speed = 0
            v = speed * need_for_speed + defense * 0.1 + aggression + resistance
            v += 1
            if v &lt; 1: v = 1
            return v

        # このマップでユニットが効果的かどうか計算します
        map = wesnoth.get_map()
        recruits = self.team.recruits()
        recruits_list = []
        for recruit in recruits:
            speed = 0.0
            defense = 0.0
            n = map.x * map.y
            for y in range(map.y):
                for x in range(map.x):
                    location = wesnoth.get_location(x, y)
                    speed += recruit.movement_cost(location)
                    defense += 100 - recruit.defense_modifier(location)
            speed = recruit.movement * n / speed
            defense /= n

            aggression = 0.0
            resistance = 0.0
            enemies = wesnoth.get_enemy_destinations_by_unit().keys()
            n = len(enemies)
            for location in enemies:
                enemy = wesnoth.get_units()[location]
                aggression += attack_score(recruit, enemy)
                resistance -= attack_score(enemy, recruit)
            aggression /= n
            resistance /= n

            debug(&quot;%s: speed: %f, defense: %f, aggression: %f, resistance: %f&quot; %
                (recruit.name, speed, defense, aggression, resistance))

            recruits_list.append((recruit, speed, defense, aggression, resistance))

        # 召還します
        for location, unit in wesnoth.get_units().iteritems():
            if unit.side == self.team.side and unit.can_recruit:

                keepsort = []
                for keep in keeps:
                    heapq.heappush(keepsort, (location.distance_to(keep), keep))

                keep = keepsort[0][1]

                self.go_to(location, keep)
                for i in range(6): # up to 6 units (TODO: can be more)
                    # Get a random, weighted unit type from the available.
                    heap = []
                    total_v = 0
                    for r in recruits_list:
                        v = recruit_score(*r)
                        v *= v * v
                        total_v += v
                        heapq.heappush(heap, (-v, r[0]))
                    r = wesnoth.get_random(0, total_v)
                    while 1:
                        v, recruit = heapq.heappop(heap)
                        debug(&quot;%d %d&quot; % (r, v))
                        r += v
                        if r &lt;= 0: break

                    # タイルが隣り合ったところに召還します
                    # TODO: 実際は, 可能な限り最も近い位置を使うべき
                    for position in wesnoth.get_adjacent_tiles(location):
                        if wesnoth.recruit_unit(recruit.name, position):
                            break
                    else:
                        # was not possible -&gt; we&#039;re done
                        break
                    units_recruited += 1
                    wesnoth.set_variable(&quot;units_recruited&quot;, str(units_recruited))

    def find_villages(self):
        &quot;&quot;&quot;自分のではないか、または敵の村を全て取得&quot;&quot;&quot;
        villages = []
        m = wesnoth.get_map()
        for x in range(m.x):
            for y in range(m.y):
                location = wesnoth.get_location(x, y)
                if wesnoth.get_map().is_village(location):
                    for team in wesnoth.get_teams():
                        # 既に自軍が持っている村か？
                        if team.owns_village(location) and not team.is_enemy:
                            break
                    else:
                        # そうでなければ誰もいないか、敵の村ということになる
                        villages.append(location)

        return villages

    def find_keeps(self):
        &quot;&quot;&quot;主塔を見つけます&quot;&quot;&quot;
        keeps = []
        m = wesnoth.get_map()
        for x in range(m.x):
            for y in range(m.y):
                location = wesnoth.get_location(x, y)
                if wesnoth.get_map().is_keep(location):
                    keeps.append(location)
        return keeps

    def get_distance(self, location, target, must_reach = False):
        &quot;&quot;&quot;ユニットが任意のターゲットや場所まで何ターンかかるかを取得します&quot;&quot;&quot;
        if location == target: return 0
        unit = wesnoth.get_units()[location]
        path = unit.find_path(location, target, 100)
        extra = 0
        if not path:
            extra = 1
            if must_reach: return None
            for adjacent in wesnoth.get_adjacent_tiles(target):
                #ユニットが5ターン移動に費やす事に価値があるかどうか
                path = unit.find_path(location, adjacent,
                    unit.type().movement * 5)
                if path: break
            else:
                return None
        l = 0
        for location in path:
            l += unit.movement_cost(location)
        l -= unit.movement_left
        l /= unit.type().movement
        l += 1 + extra
        return l

    def attack(self, location, enemy):
        &quot;&quot;&quot;敵に攻撃します&quot;&quot;&quot;
        wesnoth.attack_unit(location, enemy)

    def go_to(self, location, target, must_reach = False):
        &quot;&quot;&quot;任意の位置のユニットを任意の位置のターゲットまで移動させ、そのポジションまで行けるかどうかを返します。
        &quot;&quot;&quot;
        if location == target: return location

        # もし道が塞がっていたら、近くを通る
        unit_locations = wesnoth.get_units().keys()
        if target in unit_locations:
            if must_reach: return location
            adjacent = wesnoth.get_adjacent_tiles(target)
            targets = [x for x in adjacent if not x in unit_locations]
            if targets:
                target = targets[0]
            else:
                return location

        # パスを見つける
        for l, unit in wesnoth.get_units().iteritems():
            if location ==  l:
                path = unit.find_path(location, target, unit.type().movement * 5)
                break
        else:
            return location

        if path:
            possible_destinations = wesnoth.get_destinations_by_unit().get(location, [])
            if must_reach:
                if not target in path: return location
                if not target in possible_destinations: return location

            # 逆にしたパスの最初の到達可能な位置を見つけます。
            path.reverse()

            for p in path:
                if p in possible_destinations and not p in unit_locations:
                    location = wesnoth.move_unit(location, p)
                    return location
        return location

import sys
print &quot;Running sample ai.&quot;
print &quot;Wesnoth&quot;, wesnoth.get_version()
print &quot;Python&quot;, sys.version

AI()


}}

**コメント
- ゲーム内の専門用語を適切に訳せていない気がします。  -- shin@管理者  (2009-02-18 13:59:49)
#comment


total:&amp;counter()
today:&amp;counter(today)
yesterday:&amp;counter(yesterday)    </description>
    <dc:date>2009-02-18T13:59:49+09:00</dc:date>
    <utime>1234933189</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/wesnoth/pages/2.html">
    <title>メニュー</title>
    <link>https://w.atwiki.jp/wesnoth/pages/2.html</link>
    <description>
      **メニュー
-[[トップページ]]
-[[Create]]
--[[EditingWesnoth]]
-[[AIの自作について&gt;WritingYourOwnAI]]
--[[なぜWesnothのAIを作る事は難しいのか&gt;WhyWritingAWesnothAIIsHard]]
--[[PythonAPIリファレンス&gt;ReferencePythonAPI]]
--[[FormulaAI]]
-ソースコード
--[[sample.py]]
-[[要望・意見]]

----

**リンク
-[[@wiki&gt;&gt;http://atwiki.jp]]
-[[@wikiご利用ガイド&gt;&gt;http://atwiki.jp/guide/]]

**他のサービス
-[[無料ホームページ作成&gt;&gt;http://atpages.jp]]
-[[無料ブログ作成&gt;&gt;http://atword.jp]]
-[[2ch型掲示板レンタル&gt;&gt;http://atchs.jp]]
-[[無料掲示板レンタル&gt;&gt;http://atbbs.jp]]
-[[お絵かきレンタル&gt;&gt;http://atpaint.jp/]]
-[[無料ソーシャルプロフ&gt;&gt;http://sns.atfb.jp/]]

// リンクを張るには &quot;[&quot; 2つで文字列を括ります。
// &quot;&gt;&quot; の左側に文字、右側にURLを記述するとリンクになります


//**更新履歴
//#recent(20)

&amp;link_editmenu(text=ここを編集)    </description>
    <dc:date>2009-02-18T13:58:55+09:00</dc:date>
    <utime>1234933135</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/wesnoth/pages/1.html">
    <title>トップページ</title>
    <link>https://w.atwiki.jp/wesnoth/pages/1.html</link>
    <description>
      *Create of Wesnoth@wiki

このサイトはHex型ターン制戦術シミュレーションゲームThe Battle for Wesnothについてのwikiです。
本家wikiのシナリオやキャンペーン作成についての内容を訳したものが中心になると思います。



**関連リンク
-本家
--http://www.wesnoth.org/
-日本語wiki
--http://wikiwiki.jp/wesnoth/


total:&amp;counter()
today:&amp;counter(today)
yesterday:&amp;counter(yesterday)    </description>
    <dc:date>2009-02-12T22:08:41+09:00</dc:date>
    <utime>1234444121</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/wesnoth/pages/14.html">
    <title>WritingYourOwnAI</title>
    <link>https://w.atwiki.jp/wesnoth/pages/14.html</link>
    <description>
      このページは本家wikiの[[WritingYourOwnAI&gt;http://www.wesnoth.org/wiki/WritingYourOwnAI]]のページを訳したものです。

*WritingYourOwnAI(AIの自作について)

**Writing your own AI

WesnothはC++、またはPythonでAIを自作できるプラガブルなAIシステムをサポートしています。始める前に[[WhyWritingAWesnothAIIsHard(なぜWesnothのAIを作る事は難しいのか)&gt;WhyWritingAWesnothAIIsHard]]を読んでおくといいでしょう。

**Python

PythonでのAIの情報については、[[ReferencePythonAPI]]のAPIを見てください。

**C++

**コメント

- 現在は[[FormulaAI]]というページの方が役立つと思います。  -- shin@管理者  (2009-02-12 15:51:36)
#comment    </description>
    <dc:date>2009-02-12T15:51:36+09:00</dc:date>
    <utime>1234421496</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/wesnoth/pages/15.html">
    <title>ReferencePythonAPI</title>
    <link>https://w.atwiki.jp/wesnoth/pages/15.html</link>
    <description>
      *ReferencePythonAPI

#contents

PythonAIはターンごとにpython scriptを実行し、ユニットの移動や攻撃を許可します。このドキュメントでは、AIを有効にする方法、Python scriptで利用できるプロパティとメソッドについて解説します。

特に指定しない限り、全てのプロパティは読み込み専用です。

全てのオブジェクトはwesnoth.gamemapを除いて==演算子がサポートされています。実際には比較はサポートされていますが、結果はイコールテストのみ意味があります。

**Python AIのオプションの指定方法 

Python support will be built if HAVE_PYTHON is defined during the build process. Under Unix, this is disabled with the --disable-python configure switch. 

Pythonのサポートを一度有効にすると、PythonAIを使う方法が3つできます。いずれにしても、.pyファイルはdataフォルダかuserdataフォルダ内のdata/aisに入っているはずです。例えば、Unix系ならそれらのスクリプトは~/.wesnoth/data/ais/my_ai.pyか、/usr/share/wesnoth/data/ais/my_ai.pyにあるはずです。また、Windowsなら、wesnoth/userdata/data/ais/my_ai.pyか、wesnoth/data/ais/my_ai.pyにあるはずです。


***方法その1
マルチプレイヤーゲームを始める場合は、PythonAIを選択して使う事ができます。そのAIは全てのプレイヤーに提供できるはずです。これを動かすために、メインで使うAIの.pyファイルは最初の1行目に以下のようなコメントを入れる必要があります。

#highlight(python){{
#!WPY
}}

また、安全なPythonAIのみを実行したい場合は、以下のようにコメントを入れます。
#highlight(python){{
&gt;#!UNSAFE_WPY
}}

***方法その2

コマンドラインからゲームを始める場合、--multiplayerオプションを使うことが出来ます。以下が例です(実際は1行です)。

#highlight(){{
wesnoth
--multiplayer 
--scenario=multiplayer_Fallenstar_Lake
--controller2=ai
--controller3=ai
--algorithm2=python_ai
--parm2=python_script:test.py
--side2=&quot;Knalgan Alliance&quot;
--side3=&quot;Rebels&quot;
}}

これはシナリオをidで与え、2人のプレイヤーは&quot;test.py&quot;というPythonAIを使うことを示しています。

***方法その3

マップの中に直接pythonAIを指定します。[side]タグの中に、以下のように挿入します。

#highlight(){{
controller=ai
[ai]
ai_algorithm=python_ai
python_script=path to script
[/ai]
}}

この場合、campaignフォルダ内のdata/aisにスクリプトを置く必要があります。

**Pythonでできること 

WesnothはPythonが入っていなくても実行できますが、その場合PythonAIは読み込まれません。あなたはpythonのプリプロセッサシンボル使う事ができます(たとえば、ユーザへのメッセージを表示する時など)。[[PreprocessorRef]]を見てください。次のセクションも参考になります。

**PythonAIを実現するために

AIを書く上で気をつけておきたい、いくつかの細かい点があります。ひとつのPythonインタプリタは全てのAIで使われ、ゲームで一度初期化されます。このPythonインタプリタは全てのターンでAIスクリプトを実行します。

この結果、AIスクリプトは全てのターンでリロードされるため、メインスクリプトはキャッシュデータをグローバル変数に格納する事ができません。データをターンの間(またはゲームのセーブ、ロードの際)に保存する方法は、get_variable()とset_variable()の項を見てください。

**wesnothモジュールリファレンス

これは自動的に生成されたリファレンスなので、遠慮なく編集して構いません。

これはWesnothAIのモジュールです。pythonスクリプトは、PythonAIがスクリプトを使っている時のターンごとに一度実行されます。

***関数
***アタックタイプ
***ゲームマップ
***ゲームステータス
***ロケーション
***チーム
***ユニット
***ユニットタイプ
***    </description>
    <dc:date>2009-02-12T15:49:42+09:00</dc:date>
    <utime>1234421382</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/wesnoth/pages/18.html">
    <title>FormulaAI</title>
    <link>https://w.atwiki.jp/wesnoth/pages/18.html</link>
    <description>
      このページは本家wikiの[[FormulaAI&gt;http://www.wesnoth.org/wiki/FormulaAI]]のページを訳したものです。

*FormulaAI

#contents

**概要

Wesnoth Formula AIは、WesnothのAIを簡単に開発出来るAIフレームワークです。

WesnothはすでにPythonでのAI作成をサポートしていますが、これは2つの問題を抱えています。

-AIやPyhtonに詳しくない人(特にプログラマでない人)には難解
-AIにみせかけた、悪意のあるトロイの木馬スクリプトによって、甚大なダメージを与える可能性がある

Wesnoth Formula AIの狙いは、AIを実行できる、簡単で純粋な関数型言語をつくることであり、また、比較的専門的なスキルがあまり無いような人でも、AIの変更や微調整が可能なことです。WMLを使う事ができる人たちが、シナリオと共に、シナリオに沿ったAIを作成できるようになるべきではないでしょうか。

Wesnoth Formula AIは現在、開発の実験段階にあり、基礎的なAIであればプレイする事が出来ますので、フィードバックがもらえたら幸いです。

Formula AIを使っての開発には、[side]内で、ai_algorithm=formula_aiをセットします。

**アプローチ

Formula AIを使うためには、[side]タグ内に[ai]タグをひとつ入れる必要があります。[ai]タグの中には、どのようにAIが動くのかを&#039;move&#039;属性で指定する必要があります。AIが動くたびに、この式が作動すると、moveが実行されます。移動が可能な限り式は作動し続け、移動できなくなった時点でAIはターンを終了します。また、ターンが終わってすぐの時点で式を返すコマンドがあります。

Wolf Riderを動かすだけのサンプルAIは以下のようになります。

&gt;&gt;
[side]~
...~
ai_algorithm=formula_ai~
[ai]~
move=&quot;recruit(&#039;Wolf Rider&#039;)&quot;~
[/ai]~
[/side]~
&lt;&lt;

**Formulaコマンドライン

formulaをデバッグするのに便利なのは、Wesnoth内からformulaを作動させ、結果を見る事です。formulaを作動させるには、ゲームが始まったときに&#039;f&#039;キーを押します。コマンドテキストボックスが現れ、そこに式を打つ事が可能になります。そして、結果が表示されます。例えば、
&gt;&gt;
8 + 4
&lt;&lt;
と打つと、&#039;12&#039;が表示されます。あなたは今、Wesnothを電卓のように使えたわけです。((ver1.5.9 Mac OS Xではfキーを押しても変化はありませんでした。))

**基本的な式

Formula言語は+, -, *, /, %, ^のような、基本的な算術演算はサポートしていますが、小数や浮動小数はサポートしていません。

例
&gt;&gt;
4 + 8*7 #evaluates to 60~
(4 + 8)*7 #evaluates to 84~
8 % 6 #evaluates to 2~
5 / 2 #evaluates to 2~
3 ^ 2 #evaluates to 9~
&lt;&lt;

また、等式をサポートしており、=と!=、それと比較演算子として、&lt;, &gt;, &lt;=, &gt;=が使用可能です。&#039;false&#039;は0かnullになります。他の値はtrueになります。また、and,or,notもサポートしています。

&gt;&gt;
2 = 4 #evaluates to 0~
2 &lt;= 3 #evaluates to 1~
0 != 1 #evaluates to 1~
not 4 #evaluates to 0~
not 0 #evaluates to 1~
(2 &lt; 4) and (3 &gt; 6) #evaluates to 1 and 0 which evaluates to 0~
(2 &lt; 4) or (3 &gt; 6) #evaluates to 1 or 0 which evaluates to 1~
&lt;&lt;

Formula言語は&#039;dice&#039;演算子をサポートしています。使用法としては、

&gt;3d5

とすると、5面のサイコロを3回振った値が結果として与えられます。意味としては、3から15までのランダムな値と同等です。

**データタイプ

Formulaシステムは、変数を格納できるものと、計算を行うものという、違うタイプのデータをサポートしています。

-数字:0,1,2といったもの。浮動小数はサポートしていません。0は&#039;flase&#039;と同等で、他の値はtrueになります。
-文字列:

&gt;&#039;this is a text string&#039;

-リスト:
リストは配列です。例えば、ai.my_uitsはユニットオブジェクトのリストです。リストは[]括弧で表現し、カンマで区切ります。例えば、

&gt;[4,8,7]

のようにします。また、

&gt;[]

で空のリストになります。様々な関数はリストで機能します。

リストの特定の要素にアクセスする場合は、

&gt;my_list[0]

のようにします。これはリストの最初の要素を返します。

&gt;[ 10, 20, 30, 40][2]

とすると、「30」を返します。

-辞書型

辞書は対になった配列で、キーに値を割り当てます。例えば、

&gt;&gt;
[ &#039;Elvish Fighter&#039; -&gt; 50, &#039;Elvish Archer&#039; -&gt; 60 ]
&lt;&lt;

とすると、辞書は二つのペアで定義されます。&#039;Elvish Fighter&#039;には50という値が割り当てられ、&#039;Elvish Archer&#039;には60が割り当てられます。

キーから割り当てられた値を参照したい時は、[]を使い、

&gt;&gt;
[ &#039;Elvish Fighter&#039; -&gt; 50, &#039;Elvish Archer&#039; -&gt; 60 ][ &#039;Elvish Fighter&#039; ]
&lt;&lt;

とすると、「50」を返します。

**AI Formula Language
***概要

Formula言語は、知的な決定をするようにプレイできるシナリオについての情報が利用できるようにしなければなりません。従って、様々な入力が利用できます。入力の簡単な例として、現在のターン数なら、turnと入力します。&#039;f&#039;キーを使ってformulaコマンドラインを起動して、

&gt;turn

と入力すれば、AIのターン数が表示されます。

&#039;turn&#039;は単純な数字です。しかしながら、入力には他の入力を含んだ複雑なタイプや、リスト型の入力もあります。例えば、&#039;my_units&#039;には全てのAIユニットのリストが含まれています。

複雑な入力とは、様々な入力が含まれたユニットのようなものを指します。ユニットのインスタンスを&#039;u&#039;とすると、&#039;u.loc.x&#039;とすることでユニットのx座標を取得できます。
unitオブジェクトにはlocationというオブジェクトが入っていて、locationオブジェクトにはxとyという入力が入っています。こうしてユニットのx,y座標が取得できるという訳です。

利用可能な値

これらの値は、AI formulaか、コマンドラインから呼び出す事ができます。

-turn
--現在のターン数
-units
--全てのユニットの完全なリスト
-my_units
--現在のプレイヤーが持っている全てのユニットの完全なリスト
-enemy_units
--現在のプレイヤーの敵の全てのユニットの完全なリスト
-villagesall
--マップ上の全ての村
-my_villages
--現在のプレイヤーが持っている村
-enemy_and_unowned_villages
--あなたのものではない村
-map
--マップについての全てのデータ
-teams
--全てのチームについての全てのデータ

***組み込み関数

Formula言語には多くの複雑なタスクを実行する組み込み関数が数多く含まれています。これらの関数については、[[こちら&gt;http://www.wesnoth.org/wiki/FormulaAI_Functions]]で簡単な例と用法が参照できます。

***自作の関数

あなたは自分の関数を定義できます。関数はパラメーターとしていくつかの入力を行える式です。ユニットにいくつかの値を与えた関数を定義したいなら、[ai]タグの次に、

&gt;&gt;
[function]~
name=value_unit~
inputs=&quot;unit&quot;~
formula=&quot;unit.hitpoints + unit.level*4&quot;~
[/function]~
&lt;&lt;

のように書き加えます。定義したこの新しい関数は、入力としてユニットを取り、その上で計算を実行します。

複数の入力を関数で使いたい時は、カンマで区切ってリストにします。

&gt;inputs=&quot;attacker,defender&quot;

この場合、&#039;attacker&#039;と&#039;defender&#039;という入力を新しい関数で定義したことになります。

インプットをよく使う場合は、以下のようにできます。先ほどの例を改善すると、

&gt;&gt;
[function]~
name=value_unit~
inputs=&quot;unit*&quot;~
formula=&quot;hitpoints + level*4&quot;~
[/function]~
&lt;&lt;

のようになります。入力の最後に&#039;*&#039;を入れると、その入力がデフォルトになりますので、&#039;unit.hitpoints&#039;のunitが省略できるというわけです。ただし、これは一つの関数に一回しか使えません。

知っておくべき重要なことは、「自作の関数で定義した式」と、「[ai]タグ内の&#039;move=&#039;で定義した式」の違いです。例えば、AIメンバー内のリーダーについての情報を得たいと思った場合、&#039;my_leader&#039;という式を書きます。自作の関数内で&#039;my_leader&#039;を使うなら、関数内での入力に&#039;ai&#039;を追加します。

&gt;inputs=&quot;ai&quot;

こうすることで、リーダーの情報である&#039;ai.my_leader&#039;を利用できます。もしくは、

&gt;inputs=&quot;ai*&quot;

と書けば、シンプルに&#039;my_leader&#039;でも情報を利用できます。

自作の関数で定義できる&#039;def&#039;については、keywordの項を見てください。

***コメント

Formula AI スクリプトでは、#で囲むとコメントになります。

&gt;&gt;
#Define opening move#~
def opening(*ai) ~
if(turn = 1,~
move(loc(11,23), loc(14,22)),~
[])~
&lt;&lt;

コメントは、行の最後に入れる事も出来ます。
&gt;&gt;
def opening(*ai) ~
if(turn = 1,~
move(loc(11,23), loc(14,22)), #capture village#~
[]) #do nothing#~
&lt;&lt;

式の中に入れる事も出来ます。
&gt;&gt;
def opening(*ai)~
if(turn = 1,~
move(loc(11,23) #my_leader#, loc(14,24) #closest village#),~
[] #do nothing# )~
&lt;&lt;

**キーワード

Formula言語には、プリミティブな機能のための予約語があります。いまのところ、以下のようになっています。

***where

式の中のステートメントを定義します。カンマで区切ると複数定義できます。

&gt;&gt;
&lt;formula&gt; where &lt;comma-separated list of statements&gt;
&lt;&lt;

式の例は以下のようになります。

&gt; a + b where a = 2, b = 4

結果は6になります。

***functions
AIが利用できる全ての組み込み関数、自作の関数のリストを返します。

***def

以下のような構文を使うと、関数を作成できます。

&gt;&gt;
def function_name(arg1, arg2, ....) function_body
&lt;&lt;

例

&gt; def sum(x,y) x + y

この場合、2つの引数を与えると、合計を返します。

**ユニットの式

いくつかのユニットには式を指定できます。それを実現する簡単な方法は、以下のようにします。
&gt;&gt;
[unit]~
  ...~
  formula=&quot;move(me.loc, loc(me.loc.x, me.loc.y - 1))&quot;~
[/unit]~

自作のユニット式はターンの最初に実行されます。上記の式は毎ターンごとにユニットを1hex北に移動させるというものです。&#039;me&#039;を使うと、そのユニット自身の情報を利用できます。

また、特定のユニットのAIを式で定義できます。

&gt;&gt;
[unit]~
 ...~
 formula=&quot;if(attack, attack, move(me.loc, choose(unit_moves(me.loc), -distance_between(self, me.vars.guard_loc))))~
 where attack = choose(filter(attacks, units = [me.loc] and distance_between(me.vars.guard_loc, target) &lt;= me.vars.guard_radius and unit_at(target).side=me.vars.hostile_side-1 ), avg_damage_inflicted)&quot;~
 [ai_vars]~
   guard_radius=3~
   guard_loc=&quot;loc(8,5)&quot;~
   hostile_side=1~
 [/ai_vars]~
[/unit]~
&lt;&lt;

この式は、guard_locで位置情報を取得して、ユニットがguard_lecから3hex以内に1面からのみ攻撃できる相手がいるかどうか確かめます。

サポートしている値のタイプは以下のものです。

***number: 

&gt;variable=3

***text (※&quot; &quot;内の&#039;&#039;): 

&gt;name=&quot;&#039;I am variable&#039;&quot;

***list: 

&gt;number_list=[ 1, 2, 3]

***map: 

&gt;map=[ &#039;Elvish Archer&#039; -&gt; 70, &#039;Elvish Shaman&#039; -&gt; 60 ]

***location: 

&gt;place=&quot;loc(X,Y)&quot;

**ファイルとformula

式は違うファイルとして分割する事が出来ます。例えば、

&gt;&gt;
[unit]~
 ...~
 formula={my_unit_formula.fai}~
[/unit]~
&lt;&lt;

とすると、ユニットの式はmy_unit_formula.faiというファイルを参照します。

必須ではありませんが、faiファイル内では次のようにするのがおすすめです。

&gt;&gt;
faifile &#039;&lt;filename&gt;&#039;~
...~
faiend~
&lt;&lt;

こうすると、formulaシステムがファイルを実行したときにエラーを報告してくれるので、大変便利です。my_unit_formula.faiというファイルであれば、

&gt;&gt;
faifile &#039;my_unit_formula.fai&#039;~
...~
faiend~
&lt;&lt;

とします。

**ツールサポート

***ctags

ctagsの基礎的なサポートとして、次の内容を.ctagsに追加できます。(ファイルが無い場合は作成してください)


 --langdef=formulaai
 --langmap=formulaai:.fai
 --regex-formulaai=/^def[ \t]*([a-zA-Z0-9_]+)/\1/d,definition/


Vimのようなctagsのプラグインを使えるエディタでは特に有効です。

**Vim

***シンタックスハイライト
***タグリストサポート


**コメント
- 適当なプラグインが見つけられずうまくコードを表示できていません…  -- shin@管理者  (2009-02-12 15:47:29)
#comment


total:&amp;counter()
today:&amp;counter(today)
yesterday:&amp;counter(yesterday)    </description>
    <dc:date>2009-02-12T15:47:29+09:00</dc:date>
    <utime>1234421249</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/wesnoth/pages/19.html">
    <title>要望・意見</title>
    <link>https://w.atwiki.jp/wesnoth/pages/19.html</link>
    <description>
      *要望・意見

このページを訳してほしい、この訳は間違っているのではないか、など、要望や意見があれば書いていくと幸せになれるかもしれません。

#comment    </description>
    <dc:date>2009-02-12T09:29:14+09:00</dc:date>
    <utime>1234398554</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/wesnoth/pages/17.html">
    <title>template</title>
    <link>https://w.atwiki.jp/wesnoth/pages/17.html</link>
    <description>
      このページは本家wikiの[[&gt;http://www.wesnoth.org/wiki/]]のページを訳したものです。


**コメント
#comment


total:&amp;counter()
today:&amp;counter(today)
yesterday:&amp;counter(yesterday)    </description>
    <dc:date>2009-02-05T15:28:12+09:00</dc:date>
    <utime>1233815292</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/wesnoth/pages/16.html">
    <title>WhyWritingAWesnothAIIsHard</title>
    <link>https://w.atwiki.jp/wesnoth/pages/16.html</link>
    <description>
      このページは本家wikiの[[WhyWritingAWesnothAIIsHard&gt;http://www.wesnoth.org/wiki/WhyWritingAWesnothAIIsHard]]を訳したものです。

*なぜWesnothのAIをつくる事は難しいのか?

**概要
私はこの問いの答えとして、主にWesnothのAIを伝統的に書いていた数人と、Summer of Codeの一部としてAIの開発をした学生について書く。

WesnothのAIは非常に役に立つものだが、その難しさを侮ってはいけない。ほとんどの伝統的なAIメソッドをWesnothに適用させることはできず、非常に革新的な解決策を使う事が必要とされた。

多くの人はWesnothのとても強力なAIをかなり簡単なはずだと思っている。結局のところ、コンピュータはチェスやバックギャモンの世界チャンピオンを負かしている。どちらもターン制のゲームなのに、Wesnothはなぜこれらとは違うのだろうか？このドキュメントでは、なぜWesnothがチェスやバックギャモンのテクニックを使わずに成功に近づいたのかについて示そう。

**Wesnothの柔軟さは我々を苦しめる

Wesnothは非常に柔軟だ。それは様々なサイズやユニットで作られた、多くの異なるマップに現れている。プレイヤーは自分のユニットをどこにでも置く事が出来る。これで、一般的なAIを作る事はより難しくなる。

コンピュータがチェスがとてもうまいのは、チェスが比較的小さなボードで、いつも同じであり、こまが同じポジションからスタートするからである。これは、コンピュータが処理しなければならない盤面が、比較的制限されていることを意味する。そして、特にコンピュータが処理しなければならない盤面のタイプはかなり限られている。例えば、チェスで城のキングの前に3つの連続したポーン(将棋で言うと歩)があるパターンはチェスではよくあるパターンだ。そしてそのパターンをチェスの評価エンジンが認識して、強さを判定するのは比較的簡単なことなのだ。

Wesnothは全く違う。Wesnothとチェスを比べるまでもなく、もしチェスが違う形や大きさだったら、その都度チェスのプログラムが書かれるだろう、とあなだって思うはずだ。真ん中が通れない正方形かもしれないいし、近づけない角があるかもしれない。その他いろいろ考えられる。しかも、プレイヤーは複雑なルールに従って、様々な「空想のコマ」を考えるかもしれない。ナイトのように動くが、特別に1歩進めるコマ、とか、ナイトとビショップのように動き、前方に進めるコマ、とか。

そんなプログラムは熟練した人間のようにはプレイできないだろうし、人間の方が新しいコマに対して、コンピュータのプログラムよりもはるかに進んだ対応ができるだろう。

しかし、これではまだWesnothの複雑さにはアプローチしていない。Wesnothは、不安定な情報を持ち、ランダムな結果を出す。チェスのプログラムはかなりチェスの決定性を頼りにしている。Wesnothは、「あなたには結果がどうなるか分からない」という点ではバックギャモンに近い。しかし、バックギャモンの盤面の状態は小さいが、Wesnothは巨大だ。

**Wesnothの計算複雑性

Wesnothの計算複雑性はチェス、バックギャモン、将棋、碁などのボードゲームと比べると膨大だ。30×20のマップを考えてみよう。Wesnothのマップでは典型的なサイズだ。さらに16の標準的な地形のタイプを考えてみる。これを表現するには2400ビットのデータが必要だ。従って、このサイズのマップは2の2400乗通り作る事が可能だ。まっさらなものを除いて、多くのユニットの組み合わせが可能になり、違った場所、違った状態などを作る事が出来る。可能な位置の数は本当に膨大な量になる。Wesnothの盤面の状態は10の1000乗をゆうに超え、おそらく10の10000乗にもなる。これでは碁の複雑性さえもまるで小人だ。碁の盤面の状態は&quot;たった&quot;10の171乗なのだから。

Wesnothは分枝度も巨大だ。たいていの伝統的なボードゲームでは、コンピュータの分析ができるが、それは大体のゲームにおいて、プレイヤーが動かすコマが1つで、もう一方のプレイヤーも動かすコマが1つだからだ。Wesnothでは、別のプレイヤーが動かす前に、一度に全てのコマを動かす。また、Wesnothではたいていコマを動かしたあと、多くのオプションを持っている。20以上のタイルを移動できるコマも珍しくなく、プレイヤーはコマを12人かそれ以上持つ事が出来る。コマは攻撃を選択することもでき、武器での攻撃など様々だ。攻撃が発生すると、ヒットした攻撃に基づいて多くの結果が出される。

Wesnothの分岐度はこうして巨大になっていく。Wesnothの差し手の過程の組み合わせ(GTC)は計り知れないほど大きい。先々の多くの移動を考える事は難しいのだ。

**ファジーゲームとしてのWesnoth

Wesnothでの離散分析は失敗する。厳密にはWesnothも離散ゲームだが、とても多くの位置があるため、離散分析の技術では失敗するからだ。

幸運にも、Wesnothは長くプレイできるゲーム(連続ゲーム)として扱う事ができる。なぜなら、多くのポジションがお互いに酷似している傾向があるからだ。加えて、離散ボードゲームと違って、Wesnothでは小さな間違いの移動が命取りになる、ということはそうそう無いからだ。

**コメント

- AIの専門用語をうまく訳せていません。  -- shin@管理者  (2009-02-05 15:17:53)
#comment

total:&amp;counter()
today:&amp;counter(today)
yesterday:&amp;counter(yesterday)    </description>
    <dc:date>2009-02-05T15:18:54+09:00</dc:date>
    <utime>1233814734</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/wesnoth/pages/13.html">
    <title>Create</title>
    <link>https://w.atwiki.jp/wesnoth/pages/13.html</link>
    <description>
      このページは本家wikiの[[Create&gt;http://www.wesnoth.org/wiki/Create]]ページの訳です。

*Create

シナリオやキャンペーンの作成に興味があるでしょうか？Wesnothの最も良い特徴の一つとして、その拡張性の高さが挙げられます。プレイヤーは地図やユニット、シナリオなどに加えて、全体のキャンペーンも作成する事ができます。ゲームのコア部分へのアクセスは、簡単であり、難しくもあります。テキストエディタでも作成は可能ですが、WML(the Wesnoth Markup Language)を使うと簡単です。このセクションでは作成から配布までのプロセスを解説していきます。

気をつけておいてほしいのは、ゲームのコア部分において、アートワークの作成にはかなりの手間がかかるということです。現在のプロジェクトはここに載っています。

-[[Read This First -+- A list of things we need help with(英語)&gt;http://www.wesnoth.org/forum/viewtopic.php?t=2014&amp;start=0&amp;postdays=0&amp;postorder=asc&amp;highlight=]]

***最初に読むべき事 [#b252bf84]

あなたが何か変更したり追加したりする前に、ゲームがどのようにデータを格納、整理しているかを把握しておくといいでしょう。以下のページにはゲームのディレクトリ構造とusrdataディレクトリについて説明されています。

-[[EditingWesnoth]]

***何が作成できるのか [#e4f25d76]

-[[地図&gt;http://www.wesnoth.org/wiki/BuildingMaps]]
--タイルのレイアウトについて
-[[シナリオ&gt;http://www.wesnoth.org/wiki/BuildingScenarios]]
-[[キャンペーン&gt;http://www.wesnoth.org/wiki/BuildingCampaigns]]
-[[マルチプレイヤーマップとシナリオ&gt;http://www.wesnoth.org/wiki/BuildingMultiplayer]]
-[[マルチプレイヤーキャンペーン&gt;http://www.wesnoth.org/wiki/MultiplayerCampaigns]]
-[[ユニット&gt;http://www.wesnoth.org/wiki/BuildingUnits]]
-[[マルチプレイヤーの種族とエリア&gt;http://www.wesnoth.org/wiki/BuildingFactions]]
-[[アート&gt;http://www.wesnoth.org/wiki/Create_Art]]
-[[音楽&gt;http://www.wesnoth.org/wiki/Create_Music]]
-[[翻訳&gt;http://www.wesnoth.org/wiki/WesnothTranslations]]
-[[コンテンツの配布&gt;http://www.wesnoth.org/wiki/Distributing_content]]
-[[オーサリングツール&gt;http://www.wesnoth.org/wiki/Authoring_tools]]
--WMLでの作成を補助するツール
-[[メンテナンスツール&gt;http://www.wesnoth.org/wiki/Maintenance_tools]]
--ソースチェックとキャンペーンのメンテナンスについて

***これまでの例 [#x7701de4]

マルチプレイヤーマップやキャンペーンについての議論が本家の[[Wesnoth Forum&gt;http://www.wesnoth.org/forum]]で多数行われています。

-[[マルチプレーヤー開発フォーラム&gt;http://www.wesnoth.org/forum/viewforum.php?f=15]]
-[[シナリオとキャンペーン開発フォーラム&gt;http://www.wesnoth.org/forum/viewforum.php?f=8]]
-[[種族リスト&gt;http://www.wesnoth.org/wiki/Faction]]

新しいキャンペーンをゼロから作るのはちょっと、という場合でも、[[捨てられたキャンペーンのスレッド&gt;http://www.wesnoth.org/forum/viewtopic.php?t=17171]]からどれかを選んで、手を加えるという方法もあります。

***Wesnothの世界観 [#a5c4c903]

すべてのキャンペーンがWesnothで行われる訳ではありませんが、Wesnothでのものがほとんどです。当然、Wesnothの世界観に沿った内容の方が、それらしいものになりますので、歴史や地理、ストーリーについてよく読んでおいた方がよいでしょう。

-[[Wesnothの歴史(英語)&gt;http://www.wesnoth.org/wiki/History_of_Wesnoth]]
-[[Wesnothの地理&gt;http://wikiwiki.jp/wesnoth/?Wesnoth%A4%CE%C3%CF%CD%FD]]
-[[Wesnothのクリーチャーの種類(英語)&gt;http://www.wesnoth.org/wiki/Races]]
-[[Wesnothの詩(英語)&gt;http://www.wesnoth.org/wiki/Poetry_of_Wesnoth]]
-[[WesnothのSS(英語)&gt;http://www.wesnoth.org/wiki/Fan_fiction]]

***その他 [#c069b3b4]

-[[External Utilities&gt;http://www.wesnoth.org/wiki/ExternalUtilities]]
-[[WMLリファレンス&gt;http://www.wesnoth.org/wiki/ReferenceWML]]
-[[FAQ&gt;http://www.wesnoth.org/wiki/FAQ#Scenario_and_Campaigns]]
-[[Campaign server web interface&gt;http://wolff.to/campaigns/list.html]]
--ユーザキャンペーンのwebからのダウンロード


**コメント

- test  -- test  (2009-02-05 09:19:11)
#comment    </description>
    <dc:date>2009-02-05T09:19:11+09:00</dc:date>
    <utime>1233793151</utime>
  </item>
  </rdf:RDF>
