Chomp'un optimum başlangıç ​​hareketini bulun


14

Chomp iki parçalı bir oyun ve dikdörtgen şeklinde bir oyundur. Her oyuncu, üstündeki ve sağındaki tüm taşlarla birlikte herhangi bir parçayı çıkarır. Sol alt parçayı alan kişi kaybeder. İlk oyuncunun her zaman kazanan bir hamleye sahip olduğu kolayca kanıtlanabilir (1'e 1 dikdörtgen hariç); bul onu.

  1. Girdi, dikdörtgenin boyutlarıdır (iki sayı)
  2. Çıktı kazanan hamlenin yeridir (iki sayı)
  3. Birden fazla kazanan hamle varsa, bunlardan herhangi birini çıktı alabilirsiniz.

Bu kod golf; en kısa kod (herhangi bir dil) kazanır.

Örnekler

Not: Çıkışlar sadece iki sayıdır; Aşağıdaki ASCII sanatı sadece sayıların ne anlama geldiğini göstermek içindir.

Giriş: 5 3 (sol alt köşeden başlayarak 1 tabanlı endeksler)

Çıktı: 4 3

XXX--
XXXXX
XXXXX

Giriş: 4 4

Çıktı: 2 2

X---
X---
X---
XXXX

Bonus

Kazanan tüm hamleleri çıkarırsanız, skorunuzdan 15 karakter çıkarın. Her sayı çifti bir satırsonu karakteriyle ayrılmalıdır.


İlk örneğinizde, çok fazla tire işareti olduğunu düşünüyorum
kitcar2000 18:14

@Kitcar Haklısın; sabit.
Mart'ta Ypnypn

Çıktı biçimini anlayamıyorum. Bu sayılar bu pozisyonlara nasıl karşılık gelir?
undergroundmonorail

@ undergroundmonorail 1 tabanlı dizin sol alttan. birinci indeks yatay eksendir ve ikinci indeks dikey endekstir.
Martin Ender

2
Ödülünüze yanıt olarak: Satranç'ta, herhangi bir zamanda (genellikle çok daha az) 119'dan az olası hamleye sahipsiniz ve bu güne kadar hiçbir süper bilgisayar en iyi bilinen algoritmaları bile kullanarak Satranç'ı çözmeye yaklaşmadı. 10 x 10 Chomp ızgarasında, 100 olası ilk hamle vardır ve bunların her biri 1-99 potansiyel ikinci hamleye sahiptir. Sizi kaba kuvvet kazanmanın kolay olacağını düşündüren nedir? Eğer kaba kuvvet cevapları istiyorsanız ızgara boyutunuzu sınırlandırmanızı tavsiye ederim. EDIT: Ama bunu yapma. Yarışma ortasında değişen gereksinimler kötüdür.
2014 tarihinde Rainbolt

Yanıtlar:


7

GolfScript, 82 ( 108 97 karakter - 15 bonus)

~),1/{{:F$0=),{F\+}/}%}@(*(0*\{1${1$\{\(@<},=},{1$\{\(@>},+(-!},:Y!{.,/+0}*;}/;Y{.-1=.@?)' '@)n}/

Herhangi bir buluşsal yöntem bilmediğim için bu çözüm, çözüm alanı üzerinde kapsamlı bir araştırma yapar. Kodu çevrimiçi deneyebilirsiniz . Uygulama çok verimli olmasına rağmen, artan girdi ile arama alanı çok hızlı büyür.

Örnekler:

> 5 3
4 3

> 5 4
3 3

> 6 6
2 2

Yukarıda belirtildiği gibi, uygulama özyinelemeye dayanmaz, ancak arama alanının her düğümünü yalnızca bir kez ziyaret eder. Aşağıda kodun, yapı taşlarını daha ayrıntılı olarak açıklayan açıklamalı bir versiyonunu bulabilirsiniz.

W * h boyutunda tek bir kartın temsili, 0 ila h aralığındaki w sayılarının bir listesi ile verilir . Her sayı, ilgili sütundaki parçaların miktarını verir. Bu nedenle, geçerli bir yapılandırma, sayıların baştan sona artmadığı bir listedir (herhangi bir hareketle sağdaki tüm sütunların en fazla seçilen sütun kadar yüksek olmasını sağlarsınız).

~                   # Evaluate the input (stack is now w h)

# BUILDING THE COMPLETE STATE SPACE
# Iteratively builds the states starting with 1xh board, then 2xh board, ...

),1/                # Generate the array [[0] [1] ... [h]] which is the space for 1xh
{                   # This loop is now ran w-1 times and each run adds all states for the 
                    # board with one additional column
  {                 # The {}/] block simply runs for each of the existing states
    :F$0=           #   Take the smallest entry (which has to be the last one)
    ),              #   For the last column all values 0..x are possible
    {F\+}/          #     Append each of these values to the smaller state
  }%
}@(*

# The order ensures that the less occupied boards are first in the list.
# Thus each game runs from the end of the list (where [h h ... h] is) to 
# the start (where [0 0 ... 0] is located).

# RUN THROUGH THE SEARCH SPACE
# The search algorithm therefore starts with the empty board and works through all
# possible states by simply looping over this list. It builds a list of those states
# which are known as non-winning states, i.e. those states where a player should 
# aim to end after the move

(                   # Skips the empty board (which is a winning configuration)
0*\                 # and makes an empty list out of it (which will be the list of
                    # known non-winning states (initially empty))
{                   # Loop over all possible states
  1$                #   Copy of the list of non-winning states
  {                 #   Filter those which are not reachable from the current state,
                    #   because at least one column has more pieces that the current
                    #   board has
    1$\{\(@<},=
  },
  {                 #   Filter those which are not reachable from the current state,
                    #   because no valid move exists
    1$\{\(@>},+     #     Filter those columns which are different between start and
                    #     end state
    (-!             #     If those columns are all of same height it is possible to move
  },
  :Y                #   Assign the result (list of all non-winning states which are
                    #   reachable from the current configuration within one move)
                    #   to variable Y

  !{                #   If Y is non-empty this one is a winning move, otherwise 
                    #   add it to the list
    .,/+
    0               #     Push dummy value
  }*;
}/
;                   # Discard the list (interesting data was saved to variable Y)

# OUTPUT LOOP
# Since the states were ordered the last one was the starting state. The list of 
# non-winning states were saved to variable Y each time, thus the winning moves 
# from the initial configuration is contained in this variable.

Y{                  # For each item in Y
  .-1=.@?)          #   Get the index (1-based) of the first non-h value
  ' '               #   Append a space
  @)                #   Get the non-h value itself (plus one)
  n                 #   Append a newline
}/

+1 Çözümün kendisi ve çok güzel yorumlanmış kod için
Xuntar

Yukarıdan aşağıya değil, aşağıdan yukarıya dinamik programlama. Güzel. Bunu yapmayı düşündüm, ancak aşağıdan yukarı geçiş için geçerli bir sırayla devletler oluşturmak, yinelemeli aramadan daha fazla iş ve kafa karıştırıcıydı. Kod çok uzun sürdü şaşırdım; Golfscript gibi kısa bir dilin çok daha kısa bir çözüm üretmesini bekliyordum.
user2357112 Monica

Çok güzel ve iyi düşünülmüş
Mouq

8

Python 2 3, 141-15 = 126

def win(x,y):w([y]*x)
w=lambda b,f=print:not[f(r+1,c+1)for r,p in enumerate(b)for c in range(p)if(r+c)*w(b[:r]+[min(i,c)for i in b[r:]],max)]

Kaba kuvvet minimax araması. Mümkün olan her hamle için, rakibin bu hamleyi yaptıktan sonra kazanıp kazanamayacağını tekrar tekrar görüyoruz. Oldukça zayıf golf; başka biri çok daha iyisini yapabilir. Bu APL için bir iş gibi geliyor.

  • wingenel arabirimdir. Yönetim kurulunun boyutlarını alır, yönetim kurulu temsiline dönüştürür ve bunu iletir w.
  • wminimax algoritmasıdır. Bir pano durumunu alır, tüm hamleleri dener, öğeleri kazanan hamlelere karşılık gelen bir liste oluşturur ve liste boşsa True değerini döndürür. Varsayılan olarak f=print, listeyi oluşturmanın kazanan hareketleri yazdırmanın bir yan etkisi vardır. İşlev adı, kazanan hareketlerin bir listesini döndürdüğünde daha mantıklıydı, ancak daha sonra yer kazanmak notiçin listenin önüne taşındım .
  • for r,p in enumerate(b)for c in xrange(p) if(r+c): Olası tüm hamleleri tekrarla. 1 1yasal bir hareket olarak görülmüyor ve temel davayı biraz basitleştiriyor.
  • b[:r]+[min(i,c)for i in b[r:]]: Koordinatlar rve ile gösterilen hareketten sonra tahtanın durumunu oluşturun c.
  • w(b[:r]+[min(i,c)for i in b[r:]],max): Yeni devletin kaybedilen bir devlet olup olmadığını görmek için geri bildirim. maxiki tamsayı argüman alacak ve şikayet değil bulabildiğim en kısa işlevdir.
  • f(r+1,c+1): fYazdırılıyorsa, hareketi yazdırır. Her neyse f, liste uzunluğunu doldurmak için bir değer üretir.
  • not [...]: boş listeler ve boş olmayanlar için notdöner .TrueFalse

Çok daha büyük girdileri işlemek için not dahil, tamamen çözülmemiş orijinal Python 2 kodu:

def win(x, y):
    for row, column in _win(Board([y]*x)):
        print row+1, column+1

class MemoDict(dict):
    def __init__(self, func):
        self.memofunc = func
    def __missing__(self, key):
        self[key] = retval = self.memofunc(key)
        return retval

def memoize(func):
    return MemoDict(func).__getitem__

def _normalize(state):
    state = tuple(state)
    if 0 in state:
        state = state[:state.index(0)]
    return state

class Board(object):
    def __init__(self, state):
        self.state = _normalize(state)
    def __eq__(self, other):
        if not isinstance(other, Board):
            return NotImplemented
        return self.state == other.state
    def __hash__(self):
        return hash(self.state)
    def after(self, move):
        row, column = move
        newstate = list(self.state)
        for i in xrange(row, len(newstate)):
            newstate[i] = min(newstate[i], column)
        return Board(newstate)
    def moves(self):
        for row, pieces in enumerate(self.state):
            for column in xrange(pieces):
                if (row, column) != (0, 0):
                    yield row, column
    def lost(self):
        return self.state == (1,)

@memoize
def _win(board):
    return [move for move in board.moves() if not _win(board.after(move))]

Demo:

>>> for i in xrange(7, 11):
...     for j in xrange(7, 11):
...         print 'Dimensions: {} by {}'.format(i, j)
...         win(i, j)
...
Dimensions: 7 by 7
2 2
Dimensions: 7 by 8
3 3
Dimensions: 7 by 9
3 4
Dimensions: 7 by 10
2 3
Dimensions: 8 by 7
3 3
Dimensions: 8 by 8
2 2
Dimensions: 8 by 9
6 7
Dimensions: 8 by 10
4 9
5 6
Dimensions: 9 by 7
4 3
Dimensions: 9 by 8
7 6
Dimensions: 9 by 9
2 2
Dimensions: 9 by 10
7 8
9 5
Dimensions: 10 by 7
3 2
Dimensions: 10 by 8
6 5
9 4
Dimensions: 10 by 9
5 9
8 7
Dimensions: 10 by 10
2 2

İçin 13x13take 2x2ve kazanırdım.
davidsbro

@davidsbro: Evet, bu 1x1'den büyük herhangi bir kare tahta için kazanan hamle, ancak kodum henüz hesaplamamıştı.
user2357112 Monica

2

Perl 6: 113108 karakter - 15 = 93 puan

Bu çok zordu! İşte teknik olarak doğru olan ancak önemsiz olmayan girişler için çok uzun sürecek önbelleğe alınmamış sürüm .

sub win(*@b){map ->\i,\j{$(i+1,j+1) if @b[i][j]&&!win @b[^i],@b[i..*].map({[.[^j]]})},(^@b X ^@b[0])[1..*]}

@ User2357112'nin Python uygulaması gibi çalışır (onu oyla, onun çalışması olmadan anlayamadım!) Win () bir genişlik ve uzunluk yerine bir Chomp panosu (dizi) alır. Gibi kullanılabilir:

loop {
    my ($y, $x) = get.words;
    .say for @(win [1 xx $x] xx $y)
}

Gerçekten iyi girişleri işleyebilen (yine de okunabilirlik için optimize edilmemiş) not içeren bir sürüm:

my %cache;
sub win (*@b) {
    %cache{
        join 2, map {($^e[$_]??1!!0 for ^@b[0]).join}, @b
    } //= map ->\i,\j{
        $(i+1,j+1) if @b[i][j] and not win
            @b[^i], @b[i..*].map({[.[^(* min j)]]}).grep: +*;
    },(^@b X ^@b[0])[1..*]
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.