俄罗斯方块!最终身高(第3天)


19

来自我的大学代码挑战赛的挑战

这实际上是第0天,但昨天的挑战太容易了,可能是这里另一个问题的重复。


俄罗斯方块是一款在80年代流行的视频游戏。它包括将一系列不同形状的零件放置在板上,以便它们以最紧凑的方式放置。

在这个问题中,我们将假设一系列落下的碎片,每个碎片在某个位置和某个方向都无法改变。棋子掉落时会堆积起来,并且不会消除整行(就像在原始游戏中一样)。目的是确定所有零件掉落后板的每一列的最终高度。

共有7个不同的片段,如图所示:

形状

挑战

给定一个零件清单,在所有零件落下后输出木板上所有列的高度

一块包含三个数字:I,R和P。第一个数字I是块的标识符(1到7之间的数字,与图中的顺序相同)。第二个数字R是棋子的旋转。它可以取值0、90、180或270,并表示零件沿逆时针方向旋转的角度。第三个数字P表示棋子的位置。表示作品左侧的一列(可以是1或0索引。请指定)。

示例和测试用例(1个索引)

  • 给定 [[1, 0, 1], [4, 0, 1], [5, 90, 4]]

情况1

  • 输出量 [3, 3, 1, 3, 2]

  • 给定 [[6, 270, 4], [1, 180, 5], [1, 90, 6], [7, 0, 4]]

情况#2

  • 输出量 [0, 0, 0, 9, 9, 8, 3, 3]

  • 给定[[3,0,1],[3,180,3]]输出[1,1,4,4,4]

  • 给定[[2,180,1],[2,0,3]]输出[2,2,4,3,3]

笔记

  • 这是
  • 行/列可以是1或0索引。请明确说明。
  • 您可以重新定义输入值(也许您想将片段1称为A,依此类推。)。在这种情况下,请指定

问题

  • 我们可以使用任何4个不同的值来代替以度为单位的角度吗:

  • 如果一块不完全适合先前的孔,我们应该处理“孔”吗?:是的

  • 板的高度或宽度是否有界?否。宽度和高度均不受限制


感谢@Arnauld提供图片和测试用例*。*


IRP可以以不同的顺序输入?
尼尔

@尼尔是的。它可以按任何顺序排列
Luis felipe De jesus Munoz

如果我们可以重新定义输入值,我可以将零件编号作为代表零件形状(不旋转)的矩阵吗?
无知的体现

1
我认为由于两个原因,我们无法输入表示块形状的矩阵。明确定义了输入:1,2,3 ..或A,B,C.。这一挑战的一个基本部分是管理此约束。
AZTECCO

1
包含尾随0是否可以?
dana

Answers:


10

JavaScript(Node.js) 286284270266 字节

[0..3]

a=>a.map(([p,r,x])=>(g=y=>y>3?g(+!Y--):b[Y+y]&(m[y]=('0x'+`717433667233ff4717333327661${1e12+0x5e7056a566ffff57efa65n.toString(4)}`[(p*2+r*56+y*99+13)%113])<<x)?m.map(v=>(g=x=>v&&g(x+1,H[x]=v&1?Y:~~H[x],v>>=1))(0,b[++Y]|=v)):g(y+1))(Y=a.length*4),m=[b=[-1]],H=[])&&H

在线尝试! 或者尝试也显示最终版的增强版

形状编码

所有片段均存储为正好4个半字节(4x4位),行以相反顺序排序,最左边的像素映射到最低有效位。换句话说,形状的二进制表示形式在垂直和水平方向上都被镜像。

例:

形状编码的例子

哈希函数和查找表

p[0..6]r[0..3]y[0..3]n

n=(2p+56r+99y+13)mod113

仅前个条目被显式存储。其他所有设置为。820

这些条目打包为:

`717433667233ff4717333327661${1e12+0x5e7056a566ffff57efa65n.toString(4)}`

扩展为以下82个半字节:

"717433667233ff47173333276611000000000000113213001112221112123333333311133233221211"

仅对于件的两个水平表示形式,才需要以最终格式使用十六进制,因此在上面的字符串中使用。I"ff"

哈希函数的参数以优化前导零和尾随零的方式被强行使用。通过1e12在中间使用零可以对字符串进行更多压缩,并在右侧将其从base-16转换为base-4,这只是一个令人欢迎但意外的副作用。:-)

这是所有零件和所有旋转的开箱过程的演示

已评论

a => a.map(([p, r, x]) => (     // for each piece p with rotation r and position x:
  g = y =>                      //   g = recursive function taking y
    y > 3 ?                     //   if y is greater than 3:
      g(+!Y--)                  //     reset y to 0, decrement Y and try again
    :                           //   else:
      b[Y + y] & (              //     test if we have a collision of the board with
        m[y] =                  //     the y-th row m[y] of the current piece
          ('0x' + `717...`[     //     which is extracted from a lookup table
            (p * 2 + r * 56 +   //     using the hash function described in the
             y * 99 + 13) % 113 //     previous paragraph
          ]) << x               //     and shifted to the left according to x
      ) ?                       //     if we have a collision:
        m.map(v => (            //       we iterate again on the piece rows stored in m[]
          g = x =>              //         g = recursive function taking x
            v &&                //         if v is not equal to 0:
            g(                  //           do a recursive call:
              x + 1,            //             increment x
              H[x] =            //             update the height at x:
                v & 1 ?         //               if this bit is set:
                  Y             //                 set it to Y
                :               //               else:
                  ~~H[x],       //                 leave it unchanged or force it to 0
                                //                 if it was still undefined
              v >>= 1           //             shift v to the right
            )                   //           end of recursive call
          )(0,                  //         initial call to g with x = 0
               b[++Y] |= v)     //         increment Y and copy the piece row to the board
        )                       //     end of map()
      :                         //   else (no collision):
        g(y + 1)                //     do a recursive call to test the next row
  )(Y = a.length * 4),          //   initial call to g with y = Y = 4 * the number of pieces
                                //   (assuming the worst case: piled vertical I pieces)
  m = [b = [-1]], H = []        //   initialize m[], b[] and H[]
                                //   we set a full line at the bottom of b[]
) && H                          // end of map(); return H[]

3
打包/拆箱工作很好。这真是令人印象深刻:)
dana

7

C(铛)253个 239 221 212字节

t(*a,*b,c){char*z="VP\225TBUIUVAaUZ@AWVDeTf@EVWhU😎EQV😀RTYT😉UU";for(size_t*p,f,n,y,i;c--;b++){f=1<<(8-*b)/3;p=z+*b++*8+*b++%f*2;f=n=*p;for(y=i=0;i<=f%4;y=fmax(y,a[*b+i++]+n%4))n/=4;for(;i--;a[*b+i]=y+n%4)n/=4;}}

在线尝试!

ps实际上,由于以UTF-8编码的UNICODE字符,因此代码大小为221个字节(但为212个字符)。但是tio.run将其视为212字节代码...

我的计算机上的代码大小为209个字符(218个字节)。但是我无法\225tio.run中用可见的char 代替

非高尔夫代码

// a - output array (must be zeroed), b - array of block info, c - number of blocks

// Figure codes: 2->0, 3->1, 6->2, 1->3, 5->4, 7->5, 4->6 (0,1 are L-figures, 2 is is T-figure, 3 is a line 1x4; 4,5 are zigzags; 6 is a cube 2x2)
// Vertical and horizontal positions are zero-indexed, angles = 0..3

t(*a,*b,c)
{
  char*z="VP\225TBUIUVAaUZ@AWVDeTf@EVWhU😎EQV😀RTYT😉UU";  // UTF-8
//char*z="VP\225TBUIUVAaUZ@AWVDeTf@EVW\1hU😎\26EQV😀RTYT😉UU";  // 3 bytes longer (use it if you can't copy previous string correctly) :)
  // Blocks
  for(size_t*p,f,n,y,i;c--;b++){
    f=1<<(8-*b)/3;  // number of figure variants
    p=z+*b++*8+*b++%f*2;
    // Get top base line position (y)
    f=n=*p;  // figure width, TBLs and HATs
    for(y=i=0;i<=f%4;
      y=fmax(y,a[*b+i++]+n%4))
      n/=4;
    // Add heights (HATs)
    for(;i--;
      a[*b+i]=y+n%4)
      n/=4;
  }
}  // 215 chars (224 bytes)

描述

让我们找到每个图的顶部基线(TBL),并将其描述为每个水平位置在TBL下方的多个单元格。还让我们描述高于TBL(HAT)的像元数(高度)。

例如:

                       ________ ________
_ [] _____ HAT = 1,0,0 [] [] [] HAT = 0,0,0 ___ [] [] _ HAT = 0,1,1 [] [] [] HAT = 0,0,0
 [] [] [] TBL = 1,1,1 [] TBL = 2,1,1 [] [] TBL = 1,1,0 [] TBL = 1,2,1

让我们为每个图和每个旋转角度描述TBL和HAT:

宽度TBL HATs
----- ------- -------
L字:
  3 1 1 1 1 0 0 // 0°
  2 1 1 0 2 // 90°
  3 1 1 2 0 0 0 // 180°
  2 3 1 0 0 // 270°

  3 1 1 1 0 0 1 // 0°
  2 1 3 0 0 // 90°
  3 2 1 1 0 0 0 // 180°
  2 1 1 2 0 // 270°

T型图:
  3 1 1 1 0 1 0 // 0°
  2 1 2 0 1 // 90°
  3 1 2 1 0 0 0 // 180°
  2 2 1 1 0 // 270°

线:
  4 1 1 1 1 0 0 0 0 // 0°,180°
  1 4 0 // 90°,270°

之字形:
  3 1 1 0 0 1 1 // 0°,180°
  2 1 2 1 0 // 90°,270°

  3 0 1 1 1 1 0 // 0°,180°
  2 2 1 0 1 // 90°,270°

立方体:
  2 2 2 0 0 //任何角度

现在我们应该编码这些数字作为2位的序列,并把成阵列(代替4 0通过3 1为“线”,以适应在2位的90°角-结果将是相同的;可以通过1和减小宽度)。

我们将按顺序编码:宽度(以2 LSB为单位),TBLHAT(向后循环)。例如2 2 1 1 0 对于T-图的270°的角度将被编码为1 0 1 2 1(最后1宽度-1 ):0b0100011001 = 281

更新的12.02:

a)我已经将数组转换为字符串并保存了18个字符(您可以看到前面的239个字节的代码):))

b)更多优化,代码减少了9个字符。
这是我最后一次尝试(我是这么认为的,哈哈!) 😀


1
您可以使用来罢工<s> ... </s>
Jonathan Frech


哦,酷。谢谢。大声笑:))
Jin X

哇!低级俄罗斯方块
Rustem B.

TBL是最高线下的图形单元的数量,最高线下方和上方只有自由空间或单元块(没有自由空间,则没有单元)。TBL + HAT =图形的高度(在每个水平位置)。TBL> 0和HAT> 0也是如此。
Jin X

5

普通Lisp,634字节

(let((w(make-hash-table))(r 0))(defun z(c)(or(gethash c w)0))(defun x(c v)(setf r(max r c))(setf(gethash c w)v))(defun m(s)(dolist(c s)(apply(lambda(n u p)(let*((i(let*((j'(2 2 2))(k'(3 3))(l'(2 3))(m'(3 2))(o(case n(1(list'(1 1 1 1)'(4)))(2(list j k'(1 1 2)'(3 1)))(3(list j'(1 3)'(2 1 1)k))(4(list'(2 2)))(5(list'(2 2 1)l))(6(list j l'(1 2 1)m))(7(list'(1 2 2)m)))))(setf(cdr(last o))o)))(o(nth(+ u 2)i))(b(nth u i))(s(length o))(d 0)(h 0))(dotimes(i s)(let*((w(nth i b))(g(z(+ i p)))(m(+ g w)))(when(> m d)(setf d m)(setf h(- g(-(apply'max b)w))))))(dotimes(i s)(x(-(+ s p)i 1)(+(nth i o)h)))))c))(dotimes(i r)(print(z (+ i 1))))))

详细

(defun circular (list)
  (setf (cdr (last list)) list))

(defun get-piece (piece-number)
  (circular (case piece-number
              (1 (list '(1 1 1 1)
                       '(4)))
              (2 (list '(2 2 2)
                       '(3 3)
                       '(1 1 2)
                       '(3 1)))
              (3 (list '(2 2 2)
                       '(1 3)
                       '(2 1 1)
                       '(3 3)))
              (4 (list '(2 2)))
              (5 (list '(2 2 1)
                       '(2 3)))
              (6 (list '(2 2 2)
                       '(2 3)
                       '(1 2 1)
                       '(3 2)))
              (7 (list '(1 2 2)
                       '(3 2))))))

(let ((world (make-hash-table))
      (rightmost-column 0))
  (defun get-world-column (column)
    (or (gethash column world) 0))

  (defun set-world-column (column value)
    (setf rightmost-column (max rightmost-column column))
    (setf (gethash column world) value))

  (defun drop-piece (piece-number rotation position)
    (let* ((piece (get-piece piece-number))
           (top (nth (+ rotation 2) piece))
           (bottom (nth rotation piece))
           (size (length top))
           (max-combined-height 0)
           (contact-height 0))
      (dotimes (i size)
        (let* ((down-distance (nth i bottom))
               (height (get-world-column (+ i position)))
               (combined-height (+ height down-distance)))
          (when (> combined-height max-combined-height)
            (setf max-combined-height combined-height)
            (setf contact-height
                  (- height
                     (- (apply #'max bottom)
                        down-distance))))))
      (dotimes (i size)
        (set-world-column (- (+ size position) i 1)
                          (+ (nth i top) contact-height)))))

  (defun drop-pieces (pieces)
    (dolist (piece pieces)
      (apply #'drop-piece piece)))

  (defun print-world ()
    (loop for i from 1 to rightmost-column
          do (print (get-world-column i)))))

(defun play-tetris (pieces)
  (drop-pieces pieces)
  (print-world))

测试一下

这些是数字列表的循环列表。这些子列表分别代表形状的一面,数字表示它们与另一面相距多远。当该侧在底部时,它们从左到右;在顶部时,从右到左;在左侧时,从上到下;在右侧时,从下到上。这些设计选择消除了编写旋转代码的需要。不幸的是,缺少旋转代码似乎不能弥补冗长的形状表示,或者我用来计算新列高的逻辑有些复杂。

旋转是一个非负整数。0 = 0度,1 = 90度,2 = 180度,4 = 270度


5

C#(Visual C#交互式编译器),308个字节

a=>{var o=new int[a.Max(x=>x.Item3+4)];foreach(var(i,r,p)in a){var b="\"4TqzŒª!\0\0HSš	Ó\0$\n\0!“A“š š@";int m=0,n=b[i],t=0,u=n/8+r%(n%8),v=b[u*=2]<<8|b[u-1];for(;t<v/8%8;m=m>n?m:n)n=o[p+t]+v%8-(n=(u=v>>6+3*t++)/2&1)-(n&u);for(;t-->0;)o[p+t]=m-(n=(u=v>>6+3*t)/4&1)-(n&u);}return o;}

在线尝试!

好-那太疯狂了...我提交了一个使用常规代码高尔夫球技术的答案。但是当我看到其他人提交的内容时,我意识到有更好的方法。

每个(shape, rotation)元组被编码为C#字符串文字,删除了重复项。编码过程将每个配置捕获为2个字节。

最低的3位存储高度,接下来的3位存储宽度。由于这些值中的每一个都不大于4,因此可以直接从3位中读取它们,而无需进行任何转换。这里有些例子:

  W   H
010 010 (2x2)
010 011 (2x3)
001 100 (1x4)
011 010 (3x2)
100 001 (4x1)

接下来,每列以3位存储。对我而言,最有用的存储是该列顶部和底部缺少的正方形数。

// missing squares per column

+------ 0 top / 0 bottom
|+----- 0 top / 1 bottom
||+---- 0 top / 1 bottom
|||
HHH (L-Shape)         HH (Jagged-Shape)
H                    HH
                     |||
1 top / 0 bottom ----+||
0 top / 0 bottom -----+|
0 top / 1 bottom ------+

顶部或底部不超过2个正方形,并且同时不超过1个正方形。鉴于这组限制,我想出了以下编码:

// column encoding of missing squares per column

000: none missing
100: 1 missing on top
101: 2 missing on top
010: 1 missing on bottom
011: 2 missing on bottom
110: 1 missing on top and bottom

由于我们最多要考虑3列上下左右缺少正方形的列,因此可以将每个(shape, rotation)元组编码为15位。

 C3  C2  C1   W   H
000 000 000 010 010 - 2x2 with no missing squares
000 000 000 100 001 - 4x1 with no missing squares
100 000 100 011 010 - 3x2 with missings square on top of columns 1 and 3
000 110 000 010 011 - 2x3 with missing squares on top and bottom of column 2

最后,重复的形状已被删除。以下示例显示了多个(shape,rotation)元组如何在不同的旋转下为相同形状产生重复的输出:

// Square
HH  (0, 90, 180, 270)
HH
#-------------------------------#
// ZigZag
HH  (0, 180)    H  (90, 270)
 HH            HH
               H
#-------------------------------#
// T
 H  (0)        HHH  (180)
HHH             H

 H  (90)       H    (270)
HH             HH
 H             H

确定所有唯一输出,并将其保存到中,byte[]然后转换为C#字符串文字。为了快速查找形状基于I和的位置R,数组的前7个字节由编码的查找键组成。

下面是我用来压缩片段的程序的链接。

在线尝试!

减少打高尔夫球和注释的代码:

// a: input list of (i,r,p) tuples
a=>{
  // create an output array that 4 more than
  // the largest position. this may result
  // in some trailing 0's
  var o=new int[a.Max(x=>x.Item3+4)];

  // iterate over each (i,r,p) tuple
  foreach(var(i,r,p)in a){
    // escaped string
    var b="\"4Tqzª!\0\0HS   Ó\0$\n\0!A @";
    // declare several variables that will be used later
    int m=0,n=b[i],t=0,
      // u is the decoded index into b for the current (i,r) pair
      u=n/8+r%(n%8),
      // convert 2 bytes from b into an encoded (shape,rotation) pair
      v=b[u*=2]<<8|b[u-1];
    // iterate over the columns, determining the top of the current
    // piece. The formula here is:
    //   piece_top = max(column_height + shape_height - shape_space_bottom)
    for(;t<v/8%8;m=m>n?m:n)
      n=o[p+t]+v%8-(n=(u=v>>6+3*t++)/2&1)-(n&u);
    // iterate over the columns again, saving the the new height
    // in each column. The formula here is:
    //   new_column_height = piece_top - shape_space_top
    for(;t-->0;)
      o[p+t]=m-(n=(u=v>>6+3*t)/4&1)-(n&u);
  }
  return o;
}

4

木炭,98字节

Fθ«≔§⪪§⪪”)¶∧↷"e«↨U∧0%3;D∧⁼~h⊟⁵h/₂dΦ↗(EF”2⊟ι1⊟ιη≔⊟ιζW‹Lυ⁺ζLη⊞υ⁰≔⌈Eη⁻§υ⁺ζλ﹪Iκ³εFLη§≔υ⁺ζκ⁺ε⊕÷I§ηκ³»Iυ

在线尝试!链接是详细版本的代码。将输入作为[P,R,I]值的数组,其中I从0到6,R从0 o 3且P也是0索引。说明:

Fθ«

循环输入片段。

≔§⪪§⪪”)¶∧↷"e«↨U∧0%3;D∧⁼~h⊟⁵h/₂dΦ↗(EF”2⊟ι1⊟ιη

提取当前片段和旋转的描述。(见下文。)

≔⊟ιζ

提取位置。

W‹Lυ⁺ζLη⊞υ⁰

确保有足够的水平空间放置物品。

≔⌈Eη⁻§υ⁺ζλ﹪Iκ³ε

确保有足够的垂直空间放置物品。

FLη§≔υ⁺ζκ⁺ε⊕÷I§ηκ³

计算受影响的列的新高度。

»Iυ

处理完所有块后,在单独的行上输出列高的最终列表。

压缩的字符串表示原始字符串00001923001061443168200318613441602332034173203014614341642430137。这里2s是I分隔符,1s是R分隔符。因此,这些片段解码如下:

P\R  0    1    2    3
0    0000 9
1    300  06   443  68
2    003  86   344  60
4    33
5    034  73
6    030  46   434  64
7    430  37

缺失R值由木炭自动自动填充。然后根据下表将每个数字映射到两个值:悬垂和总高度:

\ O H
0 0 1
3 0 2
4 1 2
6 0 3
7 1 3
8 2 3
9 0 4

伸出量和总高度与列高度有关,如下所示:给定我们要放置在指定位置e的零件,即使其中一列的高度大于,也可以放置该零件e。剩余空间的大小由突出部分给出。放置零件后,柱子的新高度就是放置位置加上总高度。

示例:假设我们首先将5一块放置在第1列中。由于没有别的东西了,因此将该块放置在位置0上,第1列和第3列现在的高度为1,而第2列的高度为2。然后,我们要放置6一块用1在第0列在这里旋转我们实际上可以将此片在位置0; 尽管第1列的高度为1,但该部分的悬垂值为1,因此有足够的空间放置它。列0的高度为2,列1的高度为3。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.