ผู้เล่นที่เร็วที่สุดสำหรับจุดและกล่อง


16

ความท้าทายคือการเขียนแก้สำหรับคลาสสิกเกมดินสอและกระดาษจุดและกล่อง รหัสของคุณควรใช้จำนวนเต็มสองจำนวนmและใช้nเป็นอินพุตซึ่งระบุขนาดของกระดาน

เริ่มต้นด้วยจุดที่ว่างเปล่าของตารางผู้เล่นผลัดกันเพิ่มเส้นแนวนอนหรือแนวตั้งเดียวระหว่างจุดที่อยู่ติดกันสองจุด ผู้เล่นที่จบด้านที่สี่ของกล่อง 1 × 1 จะได้รับหนึ่งแต้มและเปลี่ยนอีกหนึ่งรอบ (โดยทั่วไปจะมีการบันทึกคะแนนโดยการวางเครื่องหมายในกล่องของผู้เล่นเช่นการเริ่มต้น) เกมจะจบลงเมื่อไม่สามารถวางสายได้อีก ผู้ชนะของเกมคือผู้เล่นที่มีคะแนนมากที่สุด

ป้อนคำอธิบายรูปภาพที่นี่

คุณสามารถคิดว่าอย่างใดอย่างหนึ่งn = mหรือn = m - 1และmอย่างน้อย 2

ความท้าทายคือsolveเกม Dots and Boxes ที่ใหญ่ที่สุดที่เป็นไปได้ภายในไม่กี่นาที n*mขนาดของเกมเป็นเพียง การส่งออกของรหัสของคุณควรจะเป็นwin, drawหรือloseที่ควรจะเป็นผลสำหรับผู้เล่นคนแรกสมมติว่าผู้เล่นทั้งสองเล่นได้อย่างดีที่สุด

รหัสของคุณจะต้องคอมไพล์ได้ / รันได้บน Ubuntu โดยใช้เครื่องมือที่ติดตั้งได้ง่ายและฟรี กรุณารายงานคะแนนของคุณเป็นพื้นที่ที่ใหญ่ที่สุดที่คุณสามารถแก้ไขได้ในคอมพิวเตอร์ของคุณใน 1 นาทีพร้อมกับเวลา จากนั้นฉันจะทดสอบโค้ดบนคอมพิวเตอร์ของฉันและทำอันดับของรายการที่เรียงลำดับ

ในกรณีที่มีการเสมอกันผู้ชนะจะเป็นรหัสที่เร็วที่สุดบนกระดานขนาดใหญ่ที่สุดซึ่งสามารถแก้ไขได้ภายในไม่กี่นาที


มันจะดีกว่าถ้ารหัสเอาท์พุทไม่เพียง แต่ชนะหรือแพ้ แต่ยังให้คะแนนจริงเช่นกัน ทำให้การตรวจสติถูกต้อง


2
เราต้องใช้ minimax หรือไม่?
qwr

@qwr คุณช่วยให้ฉันรู้ว่ามีตัวเลือกอื่น ๆ ที่คุณนึกไว้อีกไหม?

รอมีผู้ชนะที่คาดเดาได้ในเกมนี้ตามขนาดกริดหรือไม่
ไม่ใช่ว่า Charles

@Charles ใช่ถ้าผู้เล่นทั้งสองเล่นอย่างเหมาะสมที่สุด

1
@ PeterTaylor ฉันคิดว่าคุณได้รับสองคะแนน แต่เพียงหนึ่งเทิร์นเสริม

คำตอบ:


15

C99 - บอร์ด 3x3 ใน 0.084 วินาที

แก้ไข:ฉันปรับโครงสร้างโค้ดของฉันอีกครั้งและทำการวิเคราะห์ในเชิงลึกเกี่ยวกับผลลัพธ์

การแก้ไขเพิ่มเติม:เพิ่มการตัดแต่งโดยสมมาตร สิ่งนี้ทำให้การกำหนดค่าอัลกอริทึม 4 แบบ: มีหรือไม่มี symmetries X ที่มีหรือไม่มีการตัดแต่งอัลฟาเบต้า

การแก้ไขขั้นสุดท้าย:เพิ่มการบันทึกช่วยจำโดยใช้ตารางแฮชในที่สุดก็บรรลุสิ่งที่เป็นไปไม่ได้: การแก้ไขกระดาน 3x3!

คุณสมบัติหลัก:

  • การใช้งาน minimax แบบตรงไปตรงมาพร้อมการตัดแต่งกิ่งด้วย alpha-beta
  • การจัดการหน่วยความจำน้อยมาก (รักษา dll ของการเคลื่อนไหวที่ถูกต้อง; O (1) อัปเดตต่อสาขาในการค้นหาแบบต้นไม้)
  • ไฟล์ที่สองที่มีการตัดแต่งโดยสมมาตร ยังคงได้รับการอัปเดต O (1) ต่อสาขา (ในทางเทคนิค O (S) โดยที่ S คือจำนวนสมมาตรนี่คือ 7 สำหรับแผงสี่เหลี่ยมและ 3 สำหรับแผงที่ไม่ใช่ตาราง)
  • ไฟล์ที่สามและสี่เพิ่มการบันทึกความจำ คุณสามารถควบคุมขนาดของ hashtable ( #define HASHTABLE_BITWIDTH) เมื่อขนาดนี้มากกว่าหรือเท่ากับจำนวนกำแพงจะรับประกันได้ว่าไม่มีการชนและการอัปเดต O (1) แฮชเทเบิลขนาดเล็กจะมีการชนกันมากขึ้นและช้าลงเล็กน้อย
  • รวบรวมด้วย-DDEBUGสำหรับงานพิมพ์

การปรับปรุงที่มีศักยภาพ:

  • แก้ไขการรั่วไหลของหน่วยความจำขนาดเล็กได้รับการแก้ไขในการแก้ไขครั้งแรก
  • การตัดแต่งอัลฟ่า / เบต้าเพิ่มในการแก้ไขที่ 2
  • ลูกพรุนสมมาตรที่เพิ่มเข้ามาในการแก้ไขครั้งที่ 3 (โปรดทราบว่าการจัดการความสมมาตรไม่ได้รับการจัดการโดยการจำดังนั้นจึงยังคงเป็นการเพิ่มประสิทธิภาพแยกต่างหาก)
  • เพิ่มบันทึกช่วยจำในการแก้ไขครั้งที่ 4
  • การบันทึกในปัจจุบันใช้บิตตัวบ่งชี้สำหรับแต่ละกำแพง บอร์ด 3x4 มีกำแพง 31 หลังดังนั้นวิธีนี้จึงไม่สามารถจัดการกับบอร์ด 4x4 ได้โดยไม่ จำกัด เวลา การปรับปรุงจะเลียนแบบจำนวนเต็ม X-bit โดยที่ X อย่างน้อยใหญ่เท่ากับจำนวนกำแพง

รหัส

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

ผล

พล็อตการเข้าสู่ระบบของการดำเนินการครั้ง

หมายเหตุเกี่ยวกับความซับซ้อน

แรงเดรัจฉานวิธีการจุดและกล่องระเบิดขึ้นในความซับซ้อนได้อย่างรวดเร็ว

พิจารณากระดานที่มีRแถวและCคอลัมน์ มีR*Cสี่เหลี่ยมR*(C+1)ผนังแนวตั้งและC*(R+1)ผนังแนวนอน W = 2*R*C + R + Cนั่นคือทั้งหมด

เนื่องจาก Lembik ขอให้เราแก้เกมด้วย minimax เราต้องข้ามไปที่ใบของเกม ตอนนี้เราไม่สนใจการตัดแต่งกิ่งเพราะสิ่งที่สำคัญคือขนาดของคำสั่ง

มีWตัวเลือกสำหรับการย้ายครั้งแรก สำหรับแต่ละผู้เล่นคนต่อไปสามารถเล่นใด ๆ ของW-1ผนังที่เหลือ ฯลฯ .. ที่จะช่วยให้เราค้นหาพื้นที่หรือSS = W * (W-1) * (W-2) * ... * 1 SS = W!โรงงานมีขนาดใหญ่มาก แต่นั่นเป็นเพียงจุดเริ่มต้น SSคือจำนวนโหนดใบไม้ในพื้นที่การค้นหา ที่เกี่ยวข้องกับการวิเคราะห์ของเราคือจำนวนการตัดสินใจทั้งหมดที่ต้องทำ (เช่นจำนวนสาขา Bในต้นไม้) ชั้นแรกของสาขามีWตัวเลือก สำหรับแต่ละระดับระดับถัดไปW-1เป็นต้น

B = W + W*(W-1) + W*(W-1)*(W-2) + ... + W!

B = SUM W!/(W-k)!
  k=0..W-1

มาดูขนาดโต๊ะเล็กกันบ้าง:

Board Size  Walls  Leaves (SS)      Branches (B)
---------------------------------------------------
1x1         04     24               64
1x2         07     5040             13699
2x2         12     479001600        1302061344
2x3         17     355687428096000  966858672404689

ตัวเลขเหล่านี้กำลังไร้สาระ อย่างน้อยพวกเขาก็อธิบายว่าทำไมรหัสเดรัจฉานบังคับดูเหมือนจะแขวนตลอดไปบนกระดาน 2x3 การค้นหาพื้นที่ของคณะกรรมการ 2x3 เป็น 742,560 ครั้งใหญ่กว่า 2x2 หาก 2x2 ใช้เวลา 20 วินาทีจึงจะเสร็จสิ้นการคาดการณ์เชิงอนุลักษณ์จะทำนายเวลาดำเนินการเกิน 100 วันเป็นเวลา 2x3 เห็นได้ชัดว่าเราต้องตัด

การวิเคราะห์การตัดแต่งกิ่ง

ฉันเริ่มต้นด้วยการเพิ่มการตัดแต่งกิ่งที่ง่ายมากโดยใช้อัลกอริทึมอัลฟ่าเบต้า โดยพื้นฐานแล้วมันจะหยุดค้นหาถ้าคู่ต่อสู้ในอุดมคติจะไม่ให้โอกาสกับมันในปัจจุบัน "เฮ้ดู - ฉันชนะมากถ้าคู่ต่อสู้ของฉันให้ฉันได้ทุกช่อง!", ไม่คิดว่า AI

แก้ไขฉันได้เพิ่มการตัดแต่งกิ่งตามกระดานสมมาตร ฉันไม่ได้ใช้วิธีการบันทึกความจำในกรณีใดวันหนึ่งฉันจะเพิ่มการบันทึกและต้องการแยกการวิเคราะห์นั้นออก แต่มันกลับเป็นแบบนี้เส้นส่วนใหญ่จะมี "คู่สมมาตร" ที่อื่นในตาราง มีได้สูงสุด 7 symmetries (แนวนอน, แนวตั้ง, การหมุน 180 ครั้ง, การหมุน 90 ครั้ง, การหมุน 270 ครั้ง, เส้นทแยงมุมและเส้นทแยงมุมอื่น ๆ ) ทั้ง 7 นำไปใช้กับแผงสี่เหลี่ยม แต่ 4 สุดท้ายไม่ได้ใช้กับแผงที่ไม่ใช่ตาราง ผนังแต่ละด้านมีตัวชี้เป็น "คู่" สำหรับแต่ละสมมาตรเหล่านี้ ถ้าหากถึงคราวคณะกรรมการมีความสมมาตรในแนวนอนดังนั้นจะต้องเล่นคู่แนวนอนคนเดียวเท่านั้น

แก้ไขแก้ไขการบันทึก! แต่ละกำแพงจะมีรหัสที่ไม่ซ้ำกันซึ่งฉันตั้งให้เป็นบิตตัวบ่งชี้ ผนังที่ n 1 << nมีประชาชนได้ ความยุ่งเหยิงของคณะกรรมการจึงเป็นเพียงหรือของกำแพงทั้งหมดที่เล่น สิ่งนี้ได้รับการปรับปรุงในแต่ละสาขาใน O (1) ขนาดของ Hashtable #defineมีการตั้งค่าใน การทดสอบทั้งหมดรันด้วยขนาด 2 ^ 12 เพราะเหตุใด เมื่อมีกำแพงมากกว่าบิตที่ทำดัชนี hashtable (12 บิตในกรณีนี้) 12 ที่สำคัญที่สุดจะถูกปกปิดและใช้เป็นดัชนี มีการจัดการความขัดแย้งกับรายการที่เชื่อมโยงที่แต่ละดัชนี hashtable แผนภูมิต่อไปนี้คือการวิเคราะห์ที่รวดเร็วและสกปรกของฉันว่าขนาด hashtable มีผลต่อประสิทธิภาพอย่างไร บนคอมพิวเตอร์ที่มี RAM ไม่สิ้นสุดเราจะตั้งขนาดของตารางเป็นจำนวนกำแพงเสมอ กระดาน 3x4 จะมี hashtable ยาว 2 ^ 31 อนิจจาเราไม่ได้หรูหราขนาดนั้น

ผลของขนาด Hashtable

ตกลงกลับไปที่การตัดแต่งกิ่ง .. โดยการหยุดการค้นหาสูงในต้นไม้เราสามารถประหยัดเวลาได้มากโดยไม่ลงไปที่ใบไม้ 'ปัจจัยการตัดแต่งกิ่ง' เป็นส่วนของสาขาที่เป็นไปได้ทั้งหมดที่เราต้องไปเยี่ยมชม แรงเดรัจฉานมีค่าการตัดแต่งกิ่งที่ 1 ยิ่งเล็กยิ่งดี

บันทึกพล็อตของสาขาที่นำมา

พล็อตล็อกของปัจจัยการตัด


ดูเหมือนว่ายุค 23 ช้าอย่างเห็นได้ชัดสำหรับภาษาที่รวดเร็วเช่น C. คุณกำลังดุร้ายหรือไม่?
qwr

กำลังดุร้ายกับการตัดแต่งกิ่งจำนวนเล็กน้อยจากอัลฟ่าเบต้า ฉันยอมรับว่ายุค 23 เป็นที่น่าสงสัย แต่ฉันไม่เห็นเหตุผลใด ๆ ในรหัสของฉันว่ามันจะไม่สอดคล้องกัน .. ในคำอื่น ๆ มันเป็นความลึกลับ
misu

1
อินพุตถูกจัดรูปแบบตามที่ระบุโดยคำถาม จำนวนเต็มสองจำนวนคั่นด้วยช่องว่างrows columnsระบุขนาดของคณะกรรมการ
misu

1
@ Lembik ฉันไม่คิดว่ามีอะไรเหลือให้ทำ ฉันทำโปรเจ็คต์สุดเพี้ยนนี้แล้ว!
misu

1
ฉันคิดว่าคำตอบของคุณสมควรได้รับสถานที่พิเศษ ฉันค้นหามันและขนาด 3 คูณ 3 เป็นปัญหาใหญ่ที่สุดที่เคยแก้ไขมาก่อนและโค้ดของคุณเกือบจะทันที หากคุณสามารถแก้ปัญหา 3 โดย 4 หรือ 4 โดย 4 คุณสามารถเพิ่มผลลัพธ์ไปยังหน้า wiki และมีชื่อเสียง :)

4

Python - 2x2 ใน 29 วินาที

ข้ามการโพสต์จากปริศนา ไม่ได้รับการปรับให้เหมาะสมเป็นพิเศษ แต่อาจทำให้เป็นจุดเริ่มต้นที่มีประโยชน์สำหรับผู้เข้าร่วมรายอื่น

from collections import defaultdict

VERTICAL, HORIZONTAL = 0, 1

#represents a single line segment that can be drawn on the board.
class Line(object):
    def __init__(self, x, y, orientation):
        self.x = x
        self.y = y
        self.orientation = orientation
    def __hash__(self):
        return hash((self.x, self.y, self.orientation))
    def __eq__(self, other):
        if not isinstance(other, Line): return False
        return self.x == other.x and self.y == other.y and self.orientation == other.orientation
    def __repr__(self):
        return "Line({}, {}, {})".format(self.x, self.y, "HORIZONTAL" if self.orientation == HORIZONTAL else "VERTICAL")

class State(object):
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.whose_turn = 0
        self.scores = {0:0, 1:0}
        self.lines = set()
    def copy(self):
        ret = State(self.width, self.height)
        ret.whose_turn = self.whose_turn
        ret.scores = self.scores.copy()
        ret.lines = self.lines.copy()
        return ret
    #iterate through all lines that can be placed on a blank board.
    def iter_all_lines(self):
        #horizontal lines
        for x in range(self.width):
            for y in range(self.height+1):
                yield Line(x, y, HORIZONTAL)
        #vertical lines
        for x in range(self.width+1):
            for y in range(self.height):
                yield Line(x, y, VERTICAL)
    #iterate through all lines that can be placed on this board, 
    #that haven't already been placed.
    def iter_available_lines(self):
        for line in self.iter_all_lines():
            if line not in self.lines:
                yield line

    #returns the number of points that would be earned by a player placing the line.
    def value(self, line):
        assert line not in self.lines
        all_placed = lambda seq: all(l in self.lines for l in seq)
        if line.orientation == HORIZONTAL:
            #lines composing the box above the line
            lines_above = [
                Line(line.x,   line.y+1, HORIZONTAL), #top
                Line(line.x,   line.y,   VERTICAL),   #left
                Line(line.x+1, line.y,   VERTICAL),   #right
            ]
            #lines composing the box below the line
            lines_below = [
                Line(line.x,   line.y-1, HORIZONTAL), #bottom
                Line(line.x,   line.y-1, VERTICAL),   #left
                Line(line.x+1, line.y-1, VERTICAL),   #right
            ]
            return all_placed(lines_above) + all_placed(lines_below)
        else:
            #lines composing the box to the left of the line
            lines_left = [
                Line(line.x-1, line.y+1, HORIZONTAL), #top
                Line(line.x-1, line.y,   HORIZONTAL), #bottom
                Line(line.x-1, line.y,   VERTICAL),   #left
            ]
            #lines composing the box to the right of the line
            lines_right = [
                Line(line.x,   line.y+1, HORIZONTAL), #top
                Line(line.x,   line.y,   HORIZONTAL), #bottom
                Line(line.x+1, line.y,   VERTICAL),   #right
            ]
            return all_placed(lines_left) + all_placed(lines_right)

    def is_game_over(self):
        #the game is over when no more moves can be made.
        return len(list(self.iter_available_lines())) == 0

    #iterates through all possible moves the current player could make.
    #Because scoring a point lets a player go again, a move can consist of a collection of multiple lines.
    def possible_moves(self):
        for line in self.iter_available_lines():
            if self.value(line) > 0:
                #this line would give us an extra turn.
                #so we create a hypothetical future state with this line already placed, and see what other moves can be made.
                future = self.copy()
                future.lines.add(line)
                if future.is_game_over(): 
                    yield [line]
                else:
                    for future_move in future.possible_moves():
                        yield [line] + future_move
            else:
                yield [line]

    def make_move(self, move):
        for line in move:
            self.scores[self.whose_turn] += self.value(line)
            self.lines.add(line)
        self.whose_turn = 1 - self.whose_turn

    def tuple(self):
        return (tuple(self.lines), tuple(self.scores.items()), self.whose_turn)
    def __hash__(self):
        return hash(self.tuple())
    def __eq__(self, other):
        if not isinstance(other, State): return False
        return self.tuple() == other.tuple()

#function decorator which memorizes previously calculated values.
def memoized(fn):
    answers = {}
    def mem_fn(*args):
        if args not in answers:
            answers[args] = fn(*args)
        return answers[args]
    return mem_fn

#finds the best possible move for the current player.
#returns a (move, value) tuple.
@memoized
def get_best_move(state):
    cur_player = state.whose_turn
    next_player = 1 - state.whose_turn
    if state.is_game_over():
        return (None, state.scores[cur_player] - state.scores[next_player])
    best_move = None
    best_score = float("inf")
    #choose the move that gives our opponent the lowest score
    for move in state.possible_moves():
        future = state.copy()
        future.make_move(move)
        _, score = get_best_move(future)
        if score < best_score:
            best_move = move
            best_score = score
    return [best_move, -best_score]

n = 2
m = 2
s = State(n,m)
best_move, relative_value = get_best_move(s)
if relative_value > 0:
    print("win")
elif relative_value == 0:
    print("draw")
else:
    print("lose")

สามารถเร่งความเร็วได้สูงสุด 18 วินาทีโดยใช้ pypy

2

Javascript - บอร์ด 1x2 ใน 20ms

มีตัวอย่างออนไลน์ที่นี่ (คำเตือน - ช้ามากหากใหญ่กว่า 1x2 พร้อมความลึกของการค้นหาแบบเต็ม ): https://dl.dropboxusercontent.com/u/141246873/minimax/index.html

ได้รับการพัฒนาสำหรับเกณฑ์การชนะดั้งเดิม (code golf) และไม่ใช่เพื่อความเร็ว

ทดสอบใน google chrome v35 บน windows 7

//first row is a horizontal edges and second is vertical
var gameEdges = [
    [false, false],
    [false, false, false],
    [false, false]
]

//track all possible moves and score outcome
var moves = []

function minimax(edges, isPlayersTurn, prevScore, depth) {

    if (depth <= 0) {
        return [prevScore, 0, 0];
    }
    else {

        var pointValue = 1;
        if (!isPlayersTurn)
            pointValue = -1;

        var moves = [];

        //get all possible moves and scores
        for (var i in edges) {
            for (var j in edges[i]) {
                //if edge is available then its a possible move
                if (!edges[i][j]) {

                    //if it would result in game over, add it to the scores array, otherwise, try the next move
                    //clone the array
                    var newEdges = [];
                    for (var k in edges)
                        newEdges.push(edges[k].slice(0));
                    //update state
                    newEdges[i][j] = true;
                    //if closing this edge would result in a complete square, get another move and get a point
                    //square could be formed above, below, right or left and could get two squares at the same time

                    var currentScore = prevScore;
                    //vertical edge
                    if (i % 2 !== 0) {//i === 1
                        if (newEdges[i] && newEdges[i][j - 1] && newEdges[i - 1] && newEdges[i - 1][j - 1] && newEdges[parseInt(i) + 1] && newEdges[parseInt(i) + 1][j - 1])
                            currentScore += pointValue;
                        if (newEdges[i] && newEdges[i][parseInt(j) + 1] && newEdges[i - 1] && newEdges[i - 1][j] && newEdges[parseInt(i) + 1] && newEdges[parseInt(i) + 1][j])
                            currentScore += pointValue;
                    } else {//horizontal
                        if (newEdges[i - 2] && newEdges[i - 2][j] && newEdges[i - 1][j] && newEdges[i - 1][parseInt(j) + 1])
                            currentScore += pointValue;
                        if (newEdges[parseInt(i) + 2] && newEdges[parseInt(i) + 2][j] && newEdges[parseInt(i) + 1][j] && newEdges[parseInt(i) + 1][parseInt(j) + 1])
                            currentScore += pointValue;
                    }

                    //leaf case - if all edges are taken then there are no more moves to evaluate
                    if (newEdges.every(function (arr) { return arr.every(Boolean) })) {
                        moves.push([currentScore, i, j]);
                        console.log("reached end case with possible score of " + currentScore);
                    }
                    else {
                        if ((isPlayersTurn && currentScore > prevScore) || (!isPlayersTurn && currentScore < prevScore)) {
                            //gained a point so get another turn
                            var newMove = minimax(newEdges, isPlayersTurn, currentScore, depth - 1);

                            moves.push([newMove[0], i, j]);
                        } else {
                            //didnt gain a point - opponents turn
                            var newMove = minimax(newEdges, !isPlayersTurn, currentScore, depth - 1);

                            moves.push([newMove[0], i, j]);
                        }
                    }



                }


            }

        }//end for each move

        var bestMove = moves[0];
        if (isPlayersTurn) {
            for (var i in moves) {
                if (moves[i][0] > bestMove[0])
                    bestMove = moves[i];
            }
        }
        else {
            for (var i in moves) {
                if (moves[i][0] < bestMove[0])
                    bestMove = moves[i];
            }
        }
        return bestMove;
    }
}

var player1Turn = true;
var squares = [[0,0],[0,0]]//change to "A" or "B" if square won by any of the players
var lastMove = null;

function output(text) {
    document.getElementById("content").innerHTML += text;
}

function clear() {
    document.getElementById("content").innerHTML = "";
}

function render() {
    var width = 3;
    if (document.getElementById('txtWidth').value)
        width = parseInt(document.getElementById('txtWidth').value);
    if (width < 2)
        width = 2;

    clear();
    //need to highlight the last move taken and show who has won each square
    for (var i in gameEdges) {
        for (var j in gameEdges[i]) {
            if (i % 2 === 0) {
                if(j === "0")
                    output("*");
                if (gameEdges[i][j] && lastMove[1] == i && lastMove[2] == j)
                    output(" <b>-</b> ");
                else if (gameEdges[i][j])
                    output(" - ");
                else
                    output("&nbsp;&nbsp;&nbsp;");
                output("*");
            }
            else {
                if (gameEdges[i][j] && lastMove[1] == i && lastMove[2] == j)
                    output("<b>|</b>");
                else if (gameEdges[i][j])
                    output("|");
                else
                    output("&nbsp;");

                if (j <= width - 2) {
                    if (squares[Math.floor(i / 2)][j] === 0)
                        output("&nbsp;&nbsp;&nbsp;&nbsp;");
                    else
                        output("&nbsp;" + squares[Math.floor(i / 2)][j] + "&nbsp;");
                }
            }
        }
        output("<br />");

    }
}

function nextMove(playFullGame) {
    var startTime = new Date().getTime();
    if (!gameEdges.every(function (arr) { return arr.every(Boolean) })) {

        var depth = 100;
        if (document.getElementById('txtDepth').value)
            depth = parseInt(document.getElementById('txtDepth').value);

        if (depth < 1)
            depth = 1;

        var move = minimax(gameEdges, true, 0, depth);
        gameEdges[move[1]][move[2]] = true;
        lastMove = move;

        //if a square was taken, need to update squares and whose turn it is

        var i = move[1];
        var j = move[2];
        var wonSquare = false;
        if (i % 2 !== 0) {//i === 1
            if (gameEdges[i] && gameEdges[i][j - 1] && gameEdges[i - 1] && gameEdges[i - 1][j - 1] && gameEdges[parseInt(i) + 1] && gameEdges[parseInt(i) + 1][j - 1]) {
                squares[Math.floor(i / 2)][j - 1] = player1Turn ? "A" : "B";
                wonSquare = true;
            }
            if (gameEdges[i] && gameEdges[i][parseInt(j) + 1] && gameEdges[i - 1] && gameEdges[i - 1][j] && gameEdges[parseInt(i) + 1] && gameEdges[parseInt(i) + 1][j]) {
                squares[Math.floor(i / 2)][j] = player1Turn ? "A" : "B";
                wonSquare = true;
            }
        } else {//horizontal
            if (gameEdges[i - 2] && gameEdges[i - 2][j] && gameEdges[i - 1] && gameEdges[i - 1][j] && gameEdges[i - 1] && gameEdges[i - 1][parseInt(j) + 1]) {
                squares[Math.floor((i - 1) / 2)][j] = player1Turn ? "A" : "B";
                wonSquare = true;
            }
            if (gameEdges[i + 2] && gameEdges[parseInt(i) + 2][j] && gameEdges[parseInt(i) + 1] && gameEdges[parseInt(i) + 1][j] && gameEdges[parseInt(i) + 1] && gameEdges[parseInt(i) + 1][parseInt(j) + 1]) {
                squares[Math.floor(i / 2)][j] = player1Turn ? "A" : "B";
                wonSquare = true;
            }
        }

        //didnt win a square so its the next players turn
        if (!wonSquare)
            player1Turn = !player1Turn;

        render();

        if (playFullGame) {
            nextMove(playFullGame);
        }
    }

    var endTime = new Date().getTime();
    var executionTime = endTime - startTime;
    document.getElementById("executionTime").innerHTML = 'Execution time: ' + executionTime;
}

function initGame() {

    var width = 3;
    var height = 2;

    if (document.getElementById('txtWidth').value)
        width = document.getElementById('txtWidth').value;
    if (document.getElementById('txtHeight').value)
        height = document.getElementById('txtHeight').value;

    if (width < 2)
        width = 2;
    if (height < 2)
        height = 2;

    var depth = 100;
    if (document.getElementById('txtDepth').value)
        depth = parseInt(document.getElementById('txtDepth').value);

    if (depth < 1)
        depth = 1;

    if (width > 2 && height > 2 && !document.getElementById('txtDepth').value)
        alert("Warning. Your system may become unresponsive. A smaller grid or search depth is highly recommended.");

    gameEdges = [];
    for (var i = 0; i < height; i++) {
        if (i == 0) {
            gameEdges.push([]);
            for (var j = 0; j < (width - 1) ; j++) {
                gameEdges[i].push(false);
            }
        }
        else {
            gameEdges.push([]);
            for (var j = 0; j < width; j++) {
                gameEdges[(i * 2) - 1].push(false);
            }
            gameEdges.push([]);
            for (var j = 0; j < (width - 1) ; j++) {
                gameEdges[i*2].push(false);
            }
        }
    }

    player1Turn = true;

    squares = [];
    for (var i = 0; i < (height - 1) ; i++) {
        squares.push([]);
        for (var j = 0; j < (width - 1); j++) {
            squares[i].push(0);
        }
    }

    lastMove = null;

    render();
}

document.addEventListener('DOMContentLoaded', initGame, false);

การสาธิตดีมากจริงๆ! 3 x 3 นั้นน่าสนใจมากเพราะผู้ชนะจะเปลี่ยนไปมาเมื่อคุณเพิ่มความลึกของการค้นหา ฉันสามารถตรวจสอบได้มินิแม็กซ์ของคุณจะหยุดครึ่งทางเลยหรือเปล่า? สิ่งที่ฉันหมายถึงคือถ้ามีคนได้รับสแควร์มันจะขยายไปถึงจุดสิ้นสุดของพวกเขาเสมอ?

2x2 คือ 3 จุดโดย 3 คุณแน่ใจหรือว่ารหัสของคุณสามารถแก้ปัญหานั้นใน 20ms?

"ถ้ามีคนได้สี่เหลี่ยมจัตุรัสมันจะขยายไปถึงจุดสิ้นสุดของพวกเขาหรือไม่?" - หากผู้เล่นได้รับสี่เหลี่ยมจัตุรัสมันยังคงเคลื่อนที่ไปในเทิร์นถัดไป แต่เทิร์นถัดไปนั้นสำหรับผู้เล่นเดียวกันนั่นคือพวกเขาจะได้เทิร์นพิเศษสำหรับการจบสแควร์ "2x2 คือ 3 จุดโดย 3" - อ๊ะ ในกรณีนั้นคะแนนของฉันคือ 1x1
rdans
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.