最小的NetHack


64

NetHack是一款类似流氓的游戏,玩家必须从地牢的最低级别取回Yendor的护身符。通常通过telnet进行游戏,整个游戏均以ASCII图形表示。游戏非常具有挑战性,需要许多游戏技巧的知识才能成功。

出于此挑战的目的,假定整个地牢是一个单独的级别,并且只有5×16个字符。此外,假设这是一个“安全”地牢,或者您只是在实现一个原型-不会有怪物,对饥饿的担忧等。实际上,您只能跟踪角色,护身符和游戏的位置当玩家到达护身符的相同位置时,将有效结束。

挑战要求

  • 将有一个5×16的地牢(单层)。
  • 给玩家一个开​​始的位置(可以选择随机),而护身符一个单独的随机(每次运行该程序都不同)在地牢内的开始位置。即,不允许护身符与玩家在同一方块上开始。
  • 接受四个输入键,这些输入键可一次将玩家移动一个方块(四个基本方向)。允许读取/处理其他输入(需要按下“ enter”等的readline()函数)。
  • 禁止在地牢范围内旅行。例如,如果玩家在地牢的右边缘,则按向右不应该执行任何操作。
  • 在初始生成之后和每次移动之后,请打印游戏的状态。由于这是代码打法,并且打印相当无趣,因此假设状态没有变化,请忽略打印函数和函数调用的字符数。空单元格应显示为句点(.),护身符应显示为双引号("),字符应显示为符号(@)。
  • 当玩家“发现”护身符时游戏结束(到达同一方块)

获奖

这是高尔夫挑战赛的代码,从今天起一周内满足要求的最短代码将被宣布为获胜者。

这是C#(无溶剂)的示例解决方案,以显示基本要求和示例输出。

using System;

namespace nh
{
    class Program
    {
        static Random random = new Random();

        // player x/y, amulet x/y
        static int px, py, ax, ay;

        static void Main(string[] args)
        {
            px = random.Next(0, 16);
            py = random.Next(0, 5);

            // amulet starts on a position different from the player
            do { ax = random.Next(0, 16); } while (px == ax);
            do { ay = random.Next(0, 5); } while (py == ay); 

            print();

            do
            {
                // reads a single keypress (no need to press enter)
                // result is cast to int to compare with character literals
                var m = (int)Console.ReadKey(true).Key;

                // Move the player. Here standard WASD keys are used.
                // Boundary checks for edge of dungeon as well.
                if (m == 'W')
                    py = (py > 0) ? py - 1 : py;
                if (m == 'S')
                    py = (py < 5) ? py + 1 : py;
                if (m == 'A')
                    px = (px > 0) ? px - 1 : px;
                if (m == 'D')
                    px = (px < 16) ? px + 1 : px;

                // print state after each keypress. If the player doesn't
                // move this is redundant but oh well.
                print();

            // game ends when player is on same square as amulet
            } while (px != ax || py != ay);
        }

        static void print()
        {
            Console.Write('\n');
            for (int y=0; y<5; y++)
            {
                for (int x = 0; x < 16; x++)
                {
                    if (x == px && y == py)
                        Console.Write('@');
                    else if (x == ax && y == ay)
                        Console.Write('"');
                    else
                        Console.Write('.');
                }
                Console.Write('\n');
            }
        }
    }
}

总字符数为1474,但是忽略对print函数及其定义的调用,最终字符数为896

程序运行时的输出:

................
...."...........
..........@.....
................
................

两次按“ a”键后的输出(包括以上内容):

................
...."...........
..........@.....
................
................

................
...."...........
.........@......
................
................

................
...."...........
........@.......
................
................

10
我觉得@Doorknob会对此感兴趣。
Alex A.

10
Rogue是最初的类Roguelike游戏,玩家必须从地牢的最低级别检索Yendor的护身符。为什么不将其称为最小的流氓?
吉尔斯2015年

5
@吉尔斯(Gilles)为什么不称这是一条小蛇?
Casey Kuball

26
psshh,没有对角线运动吗?没有yubnhjkl吗?得到护身符后连爬楼梯都不爬吗?:P(无论如何还是愤怒地投票
Doorknob

2
@tolos:我还不清楚什么是计算随机这里。如果程序运行了80次,那么每次运行程序的要求不可能完全满足……特别是,在此答案中,护身符只能占据所有79个位置中的9个。这算吗?
丹尼斯

Answers:


37

TI-BASIC,42 41 38 36 35字节

适用于TI-83或84+系列图形计算器。

int(5irand→A                          //Randomize amulet position
6log(ie^(6→C                          //15.635 + 4.093i
Repeat Ans=A                          //Ans holds the player pos. (starts bottom right)
iPart(C-iPart(C-Ans-e^(igetKey-i      //Boundary check, after adjusting player position
prgmDISPLAY
End

----------
PROGRAM:DISPLAY
For(X,0,15
For(Y,0,4
Output(Y+1,X+1,".
If A=X+Yi
Output(Y+1,X+1,"¨
If Ans=X+Yi
Output(Y+1,X+1,"@
End
End

播放器的前进方向取决于所按下键的键控代码,但是最上面的四个键确实起作用:

Key        [Y=]  [WINDOW]  [ZOOM]  [TRACE]  [GRAPH]
           -------------------------------------------
Key code    11      12       13               15
Direction  Left     Up     Right             Down

护身符从第一列的五个方块中的一个开始,并且玩家从右下角的方块开始。例如,可能的安排是:

................
¨...............
................
................
...............@

说明

玩家的位置存储为从0+0i到的复数15+4i,其中实数部分在右侧,而虚数部分在下降。这样可以方便地在顶部和左侧进行边界检查:我们只需将数字略微偏移并朝零舍入即可。例如,如果偏移为,0.5而我们的位置为-1+3i(在屏幕左侧),则该位置将被校正为iPart(-0.5+3.5i)=0+3i,应该在该位置。检查底部和右侧边界稍微复杂一些;我们需要从常量减去这个数字,该常量C大约是15.635 + 4.093i(这是我在15+4i和之间可以找到的最短的整数16+5i),取整,C再次从减去将该数字取反,然后再次取整。

当按下一个键时,未调整的演奏者位置将沿某个方向移动1个单位,但是整数部分仅在按下某些键时才会更改。幸运的是,所有有效的键都位于第一行。下图是在按下键11、12、13和15以及未按下键的情况下的偏移量的图表(中间方形内的点没有按下,导致整数部分保持不变;四次按下键'偏移量具有不同的整数部分)。C是圆圈中心的红叉。

在此处输入图片说明

旧代码(42个字节):

int(9irand→A                     // 0≤rand≤1, so int(9irand) = i*x where 0≤x≤8
1                                //set "Ans"wer variable to 1+0i
Repeat Ans=A                     
Ans-iPart(i^int(48ln(getKey-1    //add -i,-1,i,1 for WASD respectively (see rev. history)
Ans-int(Ans/16+real(Ans/7        //ensure player is inside dungeon
prgmDISPLAY                      //display on top 5 rows of the homescreen   
                                 //(for this version switch X+Yi with Y=Xi in prgmDISPLAY)
End

局限性

无法转义"字符,因此"无法在程序内部生成带有的字符串。因此,这将使用umlaut标记¨而不是引号(如果已经存在带引号的字符串,则可以显示该标记)。要获取¨@加入程序,需要外部工具;但是,它是有效的TI-BASIC。


2
只是为了好玩,我制作了一个片段,将diaeresis和@符号放在Str1中(与“外部工具”相对)。挑选在你的计算器相匹配的顶线,就进入到一个新的程序,并与ASM运行该程序(
MI赖特

哦,我忘了在计算器上用机器代码编码。不想用错误的类型使我的崩溃,但是我相信它可以工作。
lirtosiast

44

CHIP-8,48字节

这可能不被认为是合法的,但是为什么不合法。我用CHIP-8(一种基于字节码的虚拟游戏机编程语言)编写程序。您可以使用我编写的名为Octo的仿真器/调试器在浏览器中尝试完整的程序(99字节):

屏幕截图

http://johnearnest.github.io/Octo/index.html?gist=1318903acdc1dd266469

该完整程序的十六进制转储如下:

0x60 0x14 0x61 0x04 0xC4 0x3C 0xC5 0x08
0x22 0x36 0xF6 0x0A 0x22 0x52 0x40 0x00
0x12 0x16 0x46 0x07 0x70 0xFC 0x40 0x3C
0x12 0x1E 0x46 0x09 0x70 0x04 0x41 0x00
0x12 0x26 0x46 0x05 0x71 0xFC 0x41 0x10
0x12 0x2E 0x46 0x08 0x71 0x04 0x22 0x52
0x3F 0x01 0x12 0x0A 0x00 0xFD 0xA2 0x58
0xD4 0x54 0x22 0x52 0x62 0xFF 0xA2 0x5B
0xD2 0x34 0x72 0x04 0x32 0x3F 0x12 0x40
0x62 0xFF 0x73 0x04 0x33 0x14 0x12 0x40
0x00 0xEE 0xA2 0x5F 0xD0 0x14 0x00 0xEE
0xA0 0xA0 0x40 0x00 0x00 0x20 0x00 0xF0
0x90 0x90 0xD0

您可以使用ASWD键或原始CHIP-8键盘上的7589键移动播放器。如果我删除了所有用于绘制背景和播放器的代码和数据,那么我将得到这个48字节的转储:

0x60 0x14 0x61 0x04 0xC4 0x3C 0xC5 0x08
0xF6 0x0A 0x40 0x00 0x12 0x12 0x46 0x07
0x70 0xFC 0x40 0x3C 0x12 0x1A 0x46 0x09
0x70 0x04 0x41 0x00 0x12 0x22 0x46 0x05
0x71 0xFC 0x41 0x10 0x12 0x2A 0x46 0x08
0x71 0x04 0x3F 0x01 0x12 0x08 0x00 0xFD

该程序的完整版本使用高级汇编语言编写,如下所示:

:alias px v0
:alias py v1
:alias tx v2
:alias ty v3
:alias ax v4
:alias ay v5
:alias in v6

: main
    px := 20
    py := 4
    ax := random 0b111100
    ay := random 0b001000
    draw-board
    loop
        in := key
        draw-player
        if px != 0 begin
            if in == 7 then px += -4
        end
        if px != 0x3C begin
            if in == 9 then px +=  4
        end
        if py != 0 begin
            if in == 5 then py += -4
        end
        if py != 16 begin
            if in == 8 then py +=  4
        end
        draw-player
        if vf != 1 then
    again
    exit

: draw-board
    i := amulet
    sprite ax ay 4
    draw-player
    tx := -1
    i := ground
    : draw
    loop
        sprite tx ty 4
        tx += 4
        if tx != 63 then jump draw
        tx := -1
        ty += 4
        if ty != 20 then
    again
;

: draw-player
    i := player
    sprite px py 4  
;

: amulet  0xA0 0xA0 0x40
: ground  0x00 0x00 0x20 0x00
: player  0xF0 0x90 0x90 0xD0

请注意,编译后的字节本身就是CHIP-8编程语言。汇编程序只是组成此类程序的一种更方便的方法。


19
正确的工作工具。
丹尼斯

6
+1是为了让我浪费大量时间来反复玩游戏。
Alex A.

4
@AlexA。如果您想浪费更多的时间,则应尝试使用Cave Explorer。超越世界中的ASWD移动和QE用于重置/移动平台游戏级别中的块。
JohnE'7

4
太好了,我周末去了。
Alex A.

1
一开始我很怀疑,但是Cave Explorer很有趣,而且CHIP-8的运行时间比我想象的要长得多。所以我想我必须学习这一点。

10

Python 3,86个字节

def d():
    import sys
    for y in range(5):
        line = []
        for x in range(16):
            line.append('@' if y*16+x == p else \
                        '"' if y*16+x == a else \
                        '.')
        print(''.join(line))
    print()
    sys.stdout.flush()

p=79;a=id(9)%p
while p-a:d();p+=[p%16<15,16*(p<64),-(p%16>0),-16*(p>15)][ord(input())%7%5]

仅计算底部的两行,然后删除d();


我通过启动在右下角的播放器(“最后一个”平方),然后随机取样保存的另一个字节 79个平方。
林恩

糟糕,修复!我想我手动计数字节并不奇怪。:<
Lynn

1
我认为您可以替换a=id(9)%79为来保存另一个字符a=id(9)%p
kirbyfan64sos 2015年

@ kirbyfan64sos很棒!谢谢。
林恩

1
另外,如果您是针对Python 3创建的,则可以将raw_input调用更改为just input
kirbyfan64sos

10

C,122个 121 115 104 102 101字节

#define o ({for(int t=0;t<80;++t)t%16||putchar(10),putchar(t^p?t^a?46:34:64);})
p;main(a){for(a=1+time(0)%79;p^a;o,p+=(int[]){-16*(p>15),16*(p<64),-!!(p%16),p%16<15}[3&getchar()/2]);}

第一次在这里发布!我希望你喜欢它 :)

o是印刷,erm,功能。我们勇敢的英雄可以左右移动2、4、6和8,但要注意不要发送任何其他输入(没有换行符!)。

更新1:aimain的参数。

更新2: OP 确认输入的单个字符串是可以的,所以我放弃了该操作scanf(我曾经跳过了换行符)。

更新3:使用复合数组文字并修改了输入布局。现在,如果您输入了无效的方向,程序就会变成麻烦;)

更新4:注意打印功能的调用不计算在内。记笔记以更仔细地阅读规则。

更新5:感谢Mikkel Alan Stokkebye Christia,节省了一个字节。


难道!!(p%16)p%16>0?我不记得我的操作顺序。
lirtosiast

确实是@ThomasKwa,但是一元作者-不禁坚持p,因此无论哪种方式都需要括号。双重爆炸只是令人困惑:)
Quentin

@Quentin 3&getchar()/ 2 <getchar()/ 2-25
Mikkel Alan Stokkebye Christia

@MikkelAlanStokkebyeChristia谢谢你:)
Quentin

9

CJam,46 45 44 40 39 37字节

{'.80*W$Gb'"t1$Gb'@tG/W%N*oNo}:P;
5,G,m*:Dmr~{P_l~4b2fm.+_aD&!$_W$=!}gP];

第一行(定义了打印当前游戏状态的函数),第二行上的P(称为函数)对字节数没有贡献。

起始位置和护身符位置都是伪随机选择的。分布是均匀的,并且基础PRNG允许。

输入是E69B向上向下向左向右,与Caps Lock活化,接着进行Enter

替代版本

{'.80*W$Gb'"t1$Gb'@tG/W%N*oNo}:P;
5,G,m*:Dmr~{P_ZYm*l~(=:(.+_aD&!$_W$=!}gP];

以另外四个字节为代价,大大改善了输入格式:

  • 这个版本接受8246向上向下向左向右,它匹配的数字键盘对应的方向键。
  • 它也接受7913为相应的对角线移动。

测试中

由于I / O是交互式的,因此您应该使用Java解释器尝试此代码。

下载最新版本并按以下方式运行程序:

java -jar cjam-0.6.5.jar nethack.cjam

为了避免Enter在每个键之后按下,以及就地更新输出,可以使用以下包装器:

#!/bin/bash

lines=5
wait=0.05

coproc "$@"
pid=$!

head -$lines <&${COPROC[0]}

while true; do
    kill -0 $pid 2>&- || break
    read -n 1 -s
    echo $REPLY >&${COPROC[1]}
    printf "\e[${lines}A"
    head -$lines <&${COPROC[0]}
    sleep $wait
done

printf "\e[${lines}B"

像这样调用:

./wrapper java -jar cjam-0.6.5.jar nethack.cjam

主要版本

5,G,   e# Push [0 1 2 3 4] and [0 ... 15].
m*:D   e# Take the Cartesian product and save in D.
mr~    e# Shuffle and dump the array on the stack.
       e# This pushes 80 elements. We'll consider the bottommost the amulet's
       e# position and the topmost the player's.
{      e# Do:
  P    e#   Call P.
  _    e#   Copy the player's position.
  l~   e#   Read and evaluate one line of input.
       e#      "6" -> 6, "9" -> 9, "B" -> 11, "E" -> 14 
  4b   e#   Convert to base 4.
       e#     6 -> [1 2], 9 -> [2 1], 11 -> [2 3], 14 -> [3 2]
  2f-  e#   Subtract 2 from each coordinate.
       e#     [1 2] -> [-1 0], [2 1] -> [0 -1], [2 3] -> [0 1], [3 2] -> [1 0]
  .+   e#   Add the result to the copy of player's position.
  _aD& e#   Push a copy, wrap it in an array and intersect with D.
  !    e#   Logical NOT. This pushes 1 iff the intersection was empty.
  $    e#   Copy the corresponding item from the stack.
       e#     If the intersection was empty, the new position is invalid
       e#     and 1$ copies the old position.
       e#     If the intersection was non-empty, the new position is valid
       e#     and 0$ copies the new position.
  _W$  e#   Push copies of the new position and the amulet's position.
  =!   e#   Push 1 iff the positions are different.
}g     e# While =! pushes 1.
P      e# Call P.
];     e# Clear the stack.

替代版本

ZYm*   e# Push the Cartesian product of 3 and 2, i.e.,
       e#   [[0 0] [0 1] [0 2] [1 0] [1 1] [1 2] [2 0] [2 1] [2 2]].
l~     e#   Read and evaluate one line of input.
(=     e# Subtract 1 and fetch the corresponding element of the array.
:(     e# Subtract 1 from each coordinate.

功能P

'.80*  e# Push a string of 80 dots.
W$Gb   e# Copy the amulet's position and convert from base 16 to integer.
'"t    e# Set the corresponding character of the string to '"'.
1$Gb   e# Copy the player's position and convert from base 16 to integer.
'@t    e# Set the corresponding character of the string to '@'.
G/     e# Split into chunks of length 16.
W%     e# Reverse the chunks (only needed for the alternate program).
N*     e# Join the chunks, separating by linefeeds.
oNo    e# Print the resulting string and an additional linefeed.

2
我对TI-BASIC的支持恰到好处!验证解决方案后,我会发布。
lirtosiast 2015年

8

Java,231个字节(如果为函数则为196个字节)

这是完整的程序代码,位于342:

class H{public static void main(String[]a){int p=0,y=79,c;y*=Math.random();y++;p(p,y);do p((p=(c=new java.util.Scanner(System.in).next().charAt(0))<66&p%16>0?p-1:c==68&p%16<15?p+1:c>86&p>15?p-16:c==83&p<64?p+16:p),y);while(p!=y);}static void p(int p,int y){for(int i=0;i<80;i++){System.out.print((i==p?'@':i==y?'"':'.')+(i%16>14?"\n":""));}}}

没有打印功能,231:

class H{public static void main(String[]a){int p=0,y=79,c;y*=Math.random();y++;p(p,y);do p((p=(c=new java.util.Scanner(System.in).next().charAt(0))<66&p%16>0?p-1:c==68&p%16<15?p+1:c>86&p>15?p-16:c==83&p<64?p+16:p),y);while(p!=y);}}

如果只是一个功能还可以(我从规格中不清楚),那么我可以将其进一步减少到196:

void m(){int p=0,y=79,c;y*=Math.random();y++;p(p,y);do p((p=(c=new java.util.Scanner(System.in).next().charAt(0))<66&p%16>0?p-1:c==68&p%16<15?p+1:c>86&p>15?p-16:c==83&p<64?p+16:p),y);while(p!=y);}

加上一些换行符以使内容更清晰...

class H{
    public static void main(String[]a){
        int p=0,y=79,c;
        y*=Math.random();
        y++;p(p,y);
        do 
            p(
                (p=(c=new java.util.Scanner(System.in).next().charAt(0))<66&p%16>0?
                    p-1:
                    c==68&p%16<15?
                        p+1:
                        c>86&p>15?
                            p-16:
                            c==83&p<64?
                                p+16:
                                p)
                ,y);
        while(p!=y);
    }

    static void p(int p,int y){
        for(int i=0;i<80;i++){
            System.out.print((i==p?'@':i==y?'"':'.')+(i%16>14?"\n":""));
        }
    }
}

请注意,我没有在计算print函数p(p,y)本身,而是计算对它的调用,因为我在call语句中进行了一些更改。

它使用大写字母ASDW。由于它检查这些字符的方式,其他一些字母也可能起作用,但是该规范并未真正说明如果我按不同的键会发生什么。


我更喜欢在缩进级别设置嵌套三元运算符的格式,就像将if / else if链组成一样。更具可读性。
lirtosiast

@ThomasKwa是的,我有时会同时做到。这更像是嵌套的if语句,而不是链接的。对我来说感觉更好,因为每个三元组的两个半部分处于同一水平,但彼此不同。
Geobits,2015年

如果可以接受匿名函数,则可以将答案void m()()->
缩小

@ dohaqatar7是的,但是我不使用Java的anon函数进行代码高尔夫。从原则上来说,无论其他人是否也有同感,这对我来说是可耻的。
Geobits,2015年

你可以p+=吗?
lirtosiast

5

Java,574字节

import java.util.*;public class N{static Random r=new Random();static int v,b,n,m;public static void main(String[] a){v=r.nextInt(16);b=r.nextInt(5);n=r.nextInt(16);m=r.nextInt(5);p();do{Scanner e=new Scanner(System.in);char m=e.next().charAt(0);if(m=='w')b=b>0?b-1:b;if(m=='s')b=b<5?b+1:b;if(m=='a')v=v>0?v-1:v;if(m=='d')v=v<16?v+1:v;p();}while(v!=n || b!=m);}static void p(){System.out.println();for(int y=0;y<5;y++){for(int x=0;x<16;x++){if(x==z && y==x)System.out.print('@');else if(x==n && y==m)System.out.print('"');else System.out.print('.');}System.out.println();}}}

基本上与C#版本相同,但经过混淆和最小化。


这么多不必要的空间...;)
2015年

@Will我正在修正:P
阶段

1
另外,请使用一个字母的名称和三进制。
lirtosiast

@ThomasKwa固定:D
阶段

6
仍然有很多不必要的空格和缺乏ternaries的;)
威尔

5

朱莉娅,161字节

用途was,和d分别向上,左,下,右,。

完整代码,包括打印内容(330字节):

B=fill('.',5,16)
a=[rand(1:5),rand(1:16)]
B[a[1],a[2]]='"'
c=[1,a==[1,1]?2:1]
B[c[1],c[2]]='@'
for i=1:5 println(join(B[i,:]))end
while c!=a
B[c[1],c[2]]='.'
m=readline()[1]
c[2]+=m=='a'&&c[2]>1?-1:m=='d'&&c[2]<16?1:0
c[1]+=m=='w'&&c[1]>1?-1:m=='s'&&c[1]<5?1:0
m∈"wasd"&&(B[c[1],c[2]]='@')
for i=1:5 println(join(B[i,:]))end
end

评分代码,不包括打印内容(161字节):

a=[rand(1:5),rand(1:16)]
c=[1,a==[1,1]?2:1]
while c!=a
m=readline()[1]
c[2]+=m=='a'&&c[2]>1?-1:m=='d'&&c[2]<16?1:0
c[1]+=m=='w'&&c[1]>1?-1:m=='s'&&c[1]<5?1:0
end

区别在于我们不将游戏状态保存为矩阵。所有相关信息都包含在数组c和中a。而且,当然,什么也不会打印。一旦玩家到达护身符,将不再提示用户输入。


取消+说明(完整代码):

# Initialize a 5x16 matrix of dots
B = fill('.', 5, 16)

# Get a random location for the amulet
a = [rand(1:5), rand(1:16)]

# Put the amulet in B
B[a[1], a[2]] = '"'

# Start the player in the upper left unless the amulet is there
c = [1, a == [1,1] ? 2 : 1]

# Put the player in B
B[c[1], c[2]] = '@'

# Print the initial game state
for i = 1:5 println(join(B[i,:])) end

# Loop until the player gets the amulet
while c != a

    # Put a dot in the player's previous location
    B[c[1], c[2]] = '.'

    # Read a line from STDIN, take the first character
    m = readline()[1]

    # Move the player horizontally within the bounds
    if m == 'a' && c[2] > 1
        c[2] -= 1
    elseif m == 'd' && c[2] < 16
        c[2] += 1
    end

    # Move the player vertically within the bounds
    if m == 'w' && c[1] > 1
        c[1] -= 1
    elseif m == 's' && c[1] < 5
        c[1] += 1
    end

    # Set the player's new location in B
    if m ∈ "wasd"
        B[c[1], c[2]] = '@'
    end

    # Print the game state
    for i = 1:5 println(join(B[i,:])) end

end

我认为还不错

3
+ 1 /压缩/混淆版本看起来与我记得的实际nethack源代码非常相似。
本杰克逊

您可以通过使用更差的随机性来节省一些字节:a=[rand(1:5),1] c=a+1
lirtosiast 2015年

@ThomasKwa:如果总是在第一行,那有什么乐趣?:)
Alex A.

3

批处理,329字节

@echo off
set e=goto e
set f= set/a
%f%a=0
%f%b=0
%f%c=%random%*3/32768+1
%f%d=%random%*16/32768+1
:et
call:p %a% %b% %c% %d%
if %a%%b% EQU %c%%d% exit/b
choice/C "wasd"
goto %errorlevel%
:1
%f%a-=1
%e%
:2
%f%b-=1
%e%
:3
%f%a+=1
%e%
:4
%f%b+=1
:e
if %a% GTR 4%f%a=4
if %a% LSS 0%f%a=0
if %b% GTR 15%f%b=15
if %b% LSS 0%f%b=0
%e%t

:p
setlocal enabledelayedexpansion
::creating a new line variable for multi line strings
set NL=^


:: Two empty lines are required here
cls
set "display="
for /l %%r in (0,1,4) do (
    set "line="
    for /l %%c in (0,1,15) do (
        set "char=."
        if %3 EQU %%r (
            if %4 EQU %%c (
                set char="
            )
        )
        if %1 EQU %%r (
            if %2 EQU %%c (
                set "char=@"
            )
        )
        set "line=!line!!char!"
    )
    set "display=!display!!line!!NL!"
)
echo !display!
exit /b

这是非常令人印象深刻的。我很惊讶这样做是可能的。
eis 2015年

这对我不显示地牢,仅提示[W,A,S,D]的一系列行?它似乎确实在“起作用”-行走未显示的地牢最终退出。win7 cmd.exe
Dan Pritts 2015年

它对我有用Win7 cmd.exe-当我键入ver时,我会得到Microsoft Windows [Version 6.1.7601]
Jerry Jeremiah

@DanPritts这很奇怪。它在Windows 8(Microsoft Windows [Version 6.2.9200])上非常适合我
ankh-morpork 2015年

h,我没有复制并粘贴整个内容,也没有在窗口中向下滚动。做得好。
丹·普里兹

3

Perl,228 222个字符(不计算不是代码工作所必需的换行符)-207(如果不计算用于打印但未添加到游戏逻辑中的printprint if语句部分);144,如果还考虑将字段表示形式的生成代码作为打印的一部分(如Yakk在评论中所建议)

此代码使用小写的wasd进行控制;输入必须使用Enter确认。用Perl 5.14.2测试。

($a=$==rand(79))+=($a>=($==rand(80)));
print $_=("."x80)=~s/(.{$=})./\1@/r=~s/(.{$a})./\1"/r=~s/(.{16})/\1\n/gr;
%r=qw(w s/.(.{16})@/@\1./s a s/.@/@./ s s/@(.{16})./.\1@/s d s/@./.@/);
while(/"/){print if eval $r{getc STDIN}}

请注意,对于此代码,不可能将计算和打印分开,因为操作是使用正则表达式直接在打印的表示形式上完成的。

说明:

($a=$==rand(79))+=($a>=($==rand(80)));

这条线确定玩家和护身符的位置。玩家的位置由玩家决定,$==rand(80)并且实际上很容易理解:在5×16的板上,玩家可以放置80个不同的位置。位置存储在$=变量中,该变量将存储的值强制为整数;这样就节省了一些字节,无需将结果显式转换为整数(rand提供浮点值)。

由于玩家已经占据了其中一个位置,因此只剩下79个位置用于护身符,因此使用了护身符的位置$a=$==rand(79)。再次,将$=强制转换为整数的赋值,但是我进一步将其赋值$a给以重$=用于玩家的位置。

现在,为了避免护身符占据与玩家相同的位置,如果护身符的位置至少与玩家的位置一样大,则将护身符前进一个位置,从而在玩家未占据的位置上均匀分配。这是通过$a = ($a >= $=)在那里$=这里保存玩家的位置。现在,通过$a$ and the only在此表达式中插入两个初始赋值而不是第一个$ =` 来生成第一行。

print $_=("."x80)=~s/(.{$=})./\1@/r=~s/(.{$a})./\1"/r=~s/(.{16})/\1\n/gr;

这将生成初始字段,然后进行打印。("."x80)只会生成80个点的字符串。=~s/(.{$=})./\1@/r然后替换$=个字符用@,并=~s/(.{$=})./\1@/r$a同个字符"。由于使用了r修饰符,它们不会尝试就地进行修改,而是返回已修改的字符串,这就是为什么可以将它们应用于以前的表达式的原因。最后,=~s/(.{16})/\1\n/gr每16个字符插入一个换行符。请注意,该字段存储在特殊变量中$_,该变量可以在以后的语句中隐式使用。

%r=qw(w s/.(.{16})@/@\1./s a s/.@/@./ s s/@(.{16})./.\1@/s d s/@./.@/);

这将创建一个散列,其中包含不同动作的替换规则。更具可读性的版本是

%r = ( 'w' => 's/.(.{16})@/@\1./s',
       'a' => 's/.@/@./',
       's' => 's/@(.{16})./.\1@/s',
       'd' => 's/@./.@/' );

键是移动的字符,值是包含相应替换规则的字符串。

while(/"/){print if eval"\$_=~$r{getc STDIN}"}

这是主循环。while(/"/)检查(在该字段中)是否仍然有"字符$_。如果我们走到护身符上,其角色将被玩家角色替换,因此它会从场上消失。

eval $r{getc STDIN}从标准输入中读取一个字符,从has中查找相应的替换规则%r,并将其应用于$_,即字段。如果实际进行了替换,则结果为true(也就是说,在哈希中找到了密钥,并且可以移动;在替换规则中,不可能的移动不匹配)。在那种情况下print被执行。由于不带参数地调用它,所以它打印$_,即修改后的字段。


1
仅仅因为包含换行符以提高可读性并不意味着您必须对它们进行计数。我看到228个字节。另外,根据该问题的特定规则,代码的打印部分不会增加字节数。
丹尼斯

@Dennis:对于打印部分,请参阅我现在添加的说明:您不能在代码中有意义地分离打印和评估。我现在按照您的建议更改了计数。
celtschk 2015年

您的打印代码中是否有任何状态更改?没有?好吧,我认为,将打印代码的输出重新用于逻辑不应该对您造成不利影响。移动代码(不同!)应该计算在内,但是生成“显示字符串”的代码不应该计算在内。
Yakk

@Yakk:您认为我的代码的哪一部分是印刷代码?(实际上,我的观点是从计数中排除打印代码是一个坏主意,正是因为并不总是很好地定义“打印代码”是什么。)
celtschk 2015年

("."x80)=~s/(.{$=})./\1@/r=~s/(.{$a})./\1"/r=~s/(.{16})/\1\n/gr乍一看,@ celtschk 非常接近,但是我的perl-fu几年前就生锈了。我本可以错过那里的州变更。
Yakk 2015年

2

C#,256 248 234 227 226 225字节

在启用NumLock的情况下使用NumPad箭头移动。

为了清楚起见,缩进并添加了注释:

using System;
class P{
    static void Main(){
        int a=0,b=0,c,d,e;
        var r=new Random();
        while(0<((c=r.Next(16))&(d=r.Next(5))));
        Draw(a,b,c,d); // Excluded from the score.
        while(a!=c|b!=d){
            e=Console.ReadKey().KeyChar-48;
            a+=e==4&a>0?-1:e==6&a<15?1:0;
            b+=e==8&b>0?-1:e==2&b<4?1:0;
            Draw(a,b,c,d); // Excluded from the score.
        }
    }
    // The following method is excluded from the score.
    static void Draw(int a, int b, int c, int d){
        Console.Clear();
        for (int y = 0; y < 5; y++)
        {
            for (int x = 0; x < 16; x++)
            {
                Console.Write(
                    x == a && y == b ? '@' :
                    x == c && y == d ? '"' :
                                       '.'
                );
            }
            Console.WriteLine();
        }
    }
}

1
我认为C#int隐式初始化为零。另外(目前无法检查)是否可以强制转换,您可以将字符文字转换为整数,或者将“ a”至少转换为97(我认为),尽管其他数字都是三位数。

默认情况下,仅初始化类字段,并且在这种情况下要求将它们声明为静态。方法变量必须在首次使用前进行初始化。这需要更少的字符:4对7
手工E-食品

感谢@tolos提供了将char隐式转换为int的技巧!更好的是,如果将ConsoleKey枚举转换为int,则可以使用2位数的值。
Hand-E-Food

从技术上讲,Main不必调用该方法Main,因此您可以删除另外三个字符。
罗安2015年

@卢安,我想你错了。C#文档:msdn.microsoft.com/en-us/library/acy3edy3.aspx
Hand-E-Food

2

HTML + JavaScript(ES6),得分可能为217

太宽松了,但是可以在下面的摘要中在线播放。

第6行(T.value ...)用于输出而不是计数(但为简单起见,我也计算了textarea打开和关闭标签,即使它也是输出)

至于随机性:护身符总是在网格的右半部分,而玩家总是从左半部分开始。

单击textArea(放大后)以启动和重新启动游戏。

<textarea id=T onclick='s()' onkeyup='m(event.keyCode)'></textarea>
<script>
R=n=>Math.random()*n|0,
s=e=>m(y=R(5),x=R(8),a=R(5)*17+R(8)+8),
m=k=>(x+=(x<15&k==39)-(x>0&k==37),y+=(y<4&k==40)-(y>0&k==38),p=y*17+x,
T.value=p-a?(t=[...('.'.repeat(16)+'\n').repeat(5)],t[a]='X',t[p]='@',t.join('')):t='Well done!'
)
</script>

EcmaScript 6代码段(仅Firefox)

R=n=>Math.random()*n|0
s=e=>m(y=R(5),x=R(8),a=R(5)*17+R(8)+8)
m=k=>(
  x+=(x<15&k==39)-(x>0&k==37),
  y+=(y<4&k==40)-(y>0&k==38),
  p=y*17+x,
  T.value=p-a?(t=[...('.'.repeat(16)+'\n').repeat(5)],t[a]='"',t[p]='@',t.join('')):t='Well done!'
)
<textarea id=T onclick='s()' onkeyup='m(event.keyCode)'></textarea>

EcmaScript 5代码段(在Chrome中测试)

function R(n) { return Math.random()*n|0 }

function s() { m(y=R(5),x=R(8),a=R(5)*17+R(8)+8) }

function m(k) {
  x+=(x<15&k==39)-(x>0&k==37)
  y+=(y<4&k==40)-(y>0&k==38)
  p=y*17+x
  T.value=p-a?(t=('.'.repeat(16)+'\n').repeat(5).split(''),t[a]='"',t[p]='@',t.join('')):t='Well done!'
}
<textarea id=T onclick='s()' onkeyup='m(event.keyCode)'></textarea>


2

动作3:267个字节

一个在线工作示例

var a:int,p:int,t;function g(){var r=Math.random;while(p==a){a=r()*80;p=r()*80}addEventListener("keyDown",function(e){if(a==p)return;if(e.keyCode==87&&p>15)p-=16if(e.keyCode==83&&p<64)p+=16if(e.keyCode==65&&p%16>0)p--if(e.keyCode==68&&(p+1)%16>0)p++print()});print()}

这是使用游戏功能的完整程序(为了可读性而包含空格):

package
{
    import flash.display.Sprite;
    import flash.text.TextField;
    import flash.text.TextFormat;

    public class MiniRogue extends Sprite
    {
        var a:int, p:int, t;

        public function MiniRogue()
        {
            g();
        }

        function g(){
            var r=Math.random;
            while(p==a){
                a=r()*80;
                p=r()*80
            }
            addEventListener("keyDown",function(e){
                if(a==p)
                    return;
                if(e.keyCode==87&&p>15)
                    p-=16
                if(e.keyCode==83&&p<64)
                    p+=16
                if(e.keyCode==65&&p%16>0)
                    p--
                if(e.keyCode==68&&(p+1)%16>0)
                p++
                print()
            });
            print()
        }

        var old:int = -1;
        private function print():void {
            if (!t) {
                t = new TextField()
                t.defaultTextFormat = new TextFormat("_typewriter", 8)
                t.width=500;
                t.height=375;
                addChild(t)
            }
            var board:String = "";
            for (var i:int=0; i<80;i++) {
                if (i == p) {
                    board += "@";
                } else if (i == a) {
                    board += '"';
                } else {
                    board += ".";
                }
                if ((i + 1) % 16 == 0) {
                    board += "\n";
                }
            }
            if (a==p) {
                board += "Win!";
            }
            if (p == old) {
                board += "Bump!";
            }
            old = p;
            t.text = board;
        }
    }
}

2

使用Javascript:307 216

您可以在下面的代码段中玩!左侧的数字仅是为了使控制台(至少镶边1)不会合并行。

要运行代码:

  1. 点击“运行代码段”
  2. 点击ctrl-shift-j打开控制台
  3. 点击结果部分
  4. 使用箭头键播放

var x=y=2,m=Math,b=m.floor(m.random()*5),a=14,i,j,t,c=console,onload=d;function d(){c.clear();for(i=0;i<5;i++){t=i;for(j=0;j<16;j++){t+=(i==y&&j==x)?"@":(i==b&&j==a)?'"':".";if(a==x&&b==y)t=":)";}c.log(t);}}onkeydown=function(){switch(window.event.keyCode){case 37:if(x>0)x--;break;case 38:if(y>0)y--;break;case 39:if(x<15)x++;break;case 40:if(y<4)y++;break;}d();};

未打高尔夫球:

var px=py=2,m=Math,ay=m.floor(m.random()*5),ax=14,i,j,t,c=console,onload=draw;
function draw() {
  c.clear();
  for(i=0;i<5;i++) {
    t=i;
    for(j=0;j<16;j++) {
      t+=(i==py&&j==px)?"@":
         (i==ay&&j==ax)?'"':".";
      if(ax==px&&ay==py)t=":)";
    }
    c.log(t);
  }
}
onkeydown=function() {
  switch (window.event.keyCode) {
    case 37:
      if(px>0)px--;
      break;
    case 38:
      if(py>0)py--;
      break;
    case 39:
      if(px<15)px++;
      break;
    case 40:
      if(py<4)py++;
      break;
  }
  draw();
};

编辑1:更仔细地阅读规则,并相应地重写我的代码

  • 护身符值现在随机化
  • 玩家无法逃脱房间
  • 我不再计算绘图函数或调用函数中的字符

1

SpecBAS - 428 402(不包括印刷,466 425开始计数时)

使用Q / A / O / P分别向上/向下/向左/向右移动。

在第1行打印地牢的行是唯一可以忽略的行,但也打了一下。

1 PRINT ("."*16+#13)*5
2 LET px=8: LET py=3
3 LET ax=INT(RND*16): LET ay=INT(RND*5): IF ax=px AND ay=py THEN GO TO 3
4 PRINT AT ay,ax;#34;AT py,px;"@": LET ox=px: LET oy=py: PAUSE 0: LET k$=INKEY$
5 LET px=px+(k$="p")-(k$="o")
6 IF px<0 THEN LET px=0
7 IF px>15 THEN LET px=15
8 LET py=py+(k$="a")-(k$="q")
9 IF py<0 THEN LET py=0
10 IF py>4 THEN LET py=4
11 PRINT AT oy,ox;"."
12 IF SCREEN$(px,py)<>#34 THEN GO TO 4

对#34的引用只是将CHR $(34)放入代码中的简便方法。

感谢@Thomas Kwa,我没有注意到玩家的开始位置随机是可选的。还使用单独的IF语句删除了一些字符。


您可能可以通过减少随机性来节省一些字符:2 LET px=1: LET py=1: LET ax=2: LET ay=INT(RND*5)并使用IF instead of ELSE IF
lirtosiast 2015年

1

另一个C#,221 171 170

这是C#中两个位置都随机的另一种方式。想要显示这一点,即使该部分比Hand-E-Food的解决方案长7个字节。
Hand-E-Food使用Console.Read()的答案当然会更短。
Consol.Read的缺点是,按所需的Enter键会使该字段再打印2次。
但我认为没有必要仅在(实际)输入上打印。

如Hand-E-Foods解决方案中所述,导航由8426完成。

using System;
class P
{
static void Main()
{
Func<int> n=new Random().Next;
int x=n()%16,y=n()%5,a=n()%16,b,m;
while(y==(b=n()%5));

while(x!=a|y!=b)
{
Printer.Print(a, b, x, y);  // Excluded from the score.
m=Console.Read()-48;
y+=m==8&y>0?-1:m==2&y<4?1:0;
x+=m==4&x>0?-1:m==6&x<15?1:0;
}
}
}


编辑:(添加的新溶液和移动PrinterClass到结束)
EDIT2: (改变了14到15和由起始于右底部保存在字节)

适应Mauris的方法就有可能熔化它下降到在C#171个字节(当然,现在两个位置都不随机):

using System;
class P
{
static void Main()
{
int p=79,a=new Random().Next()%p,m;
while(p!=a){
Printer.Print(p,a);  // Excluded from the score.
m=Console.Read()-48;
p+=m==4&p/5>0?-5:m==6&p/5<15?5:m==8&p%5>0?-1:m==2&p%5<4?1:0;
}
}
}

打印机类几乎相同,只是新的打印过载...

class Printer
{
    public static void Print(int ax, int ay, int px, int py)
    {
        Console.Write('\n');
        for (int y = 0; y < 5; y++)
        {
            for (int x = 0; x < 16; x++)
            {
                if (x == px && y == py)
                    Console.Write('@');
                else if (x == ax && y == ay)
                    Console.Write('"');
                else
                    Console.Write('.');
            }
            Console.Write('\n');
        }
    }

    public static void Print(int p, int a)
    {
        Print(p/5,p%5,a/5,a%5);
    }
}

1

露比(185)

这也是一个Ruby示例。
我是Ruby的新手,也许有人知道如何做得更好:)

我将lineFeeds数为1,因为该程序会崩溃,否则...

导航由8462完成。您每次需要使用enter输入发送输入。

def display(ax,ay,px,py)
    puts
    for y in 0..4
        for x in 0..15
            if (x == px && y == py)
                print "@"
            elsif (x == ax && y == ay)
                print '"'
            else
                print '.'
            end
        end
        puts
    end
end


x=y=0
a=Random.rand(16) while y==(b=Random.rand(5))
while x!=a or y!=b
display(a,b,x,y)  # Excluded from the score.
m=gets.chomp.to_i
y-=m==8?1:0 if y>0
y+=m==2?1:0 if y<4
x-=m==4?1:0 if x>0
x+=m==6?1:0 if x<15
end

0

QBasic,103个字节

根据质询的规则,Show子程序不包含在字节数中,Show p, q, a, b调用也不包含(使用以下换行符)。

b=1+TIMER MOD 9
1Show p, q, a, b
INPUT m
p=p-(m=2)*(p>0)+(m=4)*(p<4)
q=q-(m=1)*(q>0)+(m=3)*(q<15)
IF(p<>a)+(q<>b)GOTO 1


SUB Show (playerRow, playerCol, amuletRow, amuletCol)
CLS
FOR row = 0 TO 4
  FOR col = 0 TO 15
    IF row = playerRow AND col = playerCol THEN
      PRINT "@";
    ELSEIF row = amuletRow AND col = amuletCol THEN
      PRINT CHR$(34);    ' Double quote mark
    ELSE
      PRINT ".";
    END IF
  NEXT
  PRINT
NEXT
END SUB

要移动,输入一个号码,然后按Enter键:1向左走,2往上走,3向右走,并且4走下来。

当玩家找到护身符时,此代码最后不会输出游戏状态。为此,请Show p, q, a, bIF语句后添加另一个。

说明

ab代表了护身符的坐标pq玩家的坐标。播放器从(0,0)开始,护身符从第0行开始,其列基于当前时间的1到1和9之间(包括1和9)。

其余的只是一堆带有条件的数学。要记住的重要一点是,QBasic中的条件返回0false,结果-1为true。让我们看一下播放器行更新语句:

p=p-(m=2)*(p>0)+(m=4)*(p<4)

如果m=2,我们想通过从减去1拉升p,只要p>0。同样,如果m=4我们想要通过添加1向下移动p,只要p<4。我们可以通过相乘获得所需的行为。如果两个因素都是-1,它们的乘积将是1,我们可以从中减去或加p。如果任一条件为0,则乘积将为0,而无效。

同样,确定玩家是否找到护身符的条件是:

IF(p<>a)+(q<>b)GOTO 1

如果两个条件中的任何一个为true,则它们的总和将为非零值(-1or或-2),因此为true,程序将返回到第1行。一旦p等于aqequals b,两个条件都将为0,因此它们的总和将为0并且控制流可以达到程序结束。

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.