Description

这是一款不错的老游戏,但有一点点记忆:每次选择错误的对时,您选择的两个图块将切换它们的位置。因此,有时您可能会认为某个瓷砖实际上在某个位置,但是它已经移动了。并且可能感觉好像您不知道它在哪里。

该游戏的作者对键盘,屏幕和/或鼠标设备的任何损坏概不负责。

我正在使用半Java8兼容版本的GWT。它不支持Stream API。

在哪里玩?

可以在这里玩游戏:http://www.zomis.net/codereview/memory/MemoryGWT .html

类摘要



MemoryGWT.java,MemoryGWT.html:主要的GWT入口点。
MemoryGWT.css:只是一些简单的CSS。
MemoryBoard.java:用于主内存板的视图类
FieldView.java:每个图块的视图。 。仅包含shuffle的实现,因为Collections.shuffle在GWT中不起作用。

代码

FieldView.java:(39行,760字节)

public class FieldView implements IsWidget {

    private static final String HIDDEN_LABEL = "";
    private final Button widget;
    private int value;

    public FieldView(int value) {
        this.value = value;
        widget = new Button(HIDDEN_LABEL);
        widget.setStyleName("game-button", true);
    }

    @Override
    public Button asWidget() {
        return widget;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public void showValue() {
        widget.setText(String.valueOf(value));
    }

    public void hideValue() {
        widget.setText(HIDDEN_LABEL);
    }

}


MemoryBoard.java:(86行,2081字节)

public class MemoryBoard implements IsWidget {

    private final Grid grid;
    private final Random random = new Random();
    private FieldView previousClicked;
    private boolean timerRunning;

    public MemoryBoard(int width, int height) {
        if ((width * height) % 2 != 0) {
            throw new IllegalArgumentException("width * height must be an even number");
        }
        grid = new Grid(height, width);
        grid.setStyleName("game");
        List<Integer> ints = new ArrayList<>();
        for (int i = 0; i < width * height / 2; i++) {
            ints.add(i);
            ints.add(i);
        }
        ListUtils.shuffle(ints, random);

        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                int value = ints.remove(ints.size() - 1);
                FieldView view = new FieldView(value);
                view.asWidget().addClickHandler(e -> clicked(view));
                grid.setWidget(y, x, view);
            }
        }

    }

    private void clicked(FieldView view) {
        if (view == previousClicked) {
            return;
        }
        if (timerRunning) {
            return;
        }
        if (previousClicked != null) {
            boolean same = previousClicked.getValue() == view.getValue();
            view.showValue();
            if (!same) {
                Timer timer = new Timer() {
                    @Override
                    public void run() {
                        // switch the two values
                        int previous = previousClicked.getValue();
                        previousClicked.setValue(view.getValue());
                        view.setValue(previous);
                        view.hideValue();
                        previousClicked.hideValue();
                        previousClicked = null;
                        timerRunning = false;
                    }
                };
                timerRunning = true;
                timer.schedule(2000);
            }
            else {
                previousClicked = null;
            }
        }
        else {
            view.showValue();
            previousClicked = view;
        }

    }

    @Override
    public Widget asWidget() {
        return grid;
    }

}


MemoryGWT.java:(15行,345字节)

public class MemoryGWT implements EntryPoint {

    @Override
    public void onModuleLoad() {
        final MemoryBoard memory = new MemoryBoard(6, 6);

        RootPanel.get("gameContainer").add(memory);

    }
}


MemoryGWT.css

.game-button {
    width: 42px;
    height: 42px;
}


MemoryGWT.html

<!doctype html>
<!-- The DOCTYPE declaration above will set the     -->
<!-- browser's rendering engine into                -->
<!-- "Standards Mode". Replacing this declaration   -->
<!-- with a "Quirks Mode" doctype is not supported. -->

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <link type="text/css" rel="stylesheet" href="MemoryGWT.css">
    <title>Memory Extreme</title>

    <script type="text/javascript" src="memorygwt/memorygwt.nocache.js"></script>
  </head>

  <body>

    <h1>Memory</h1>
    <p>Good old memory with a twist: When you have picked a non-matching pair, the two tiles you have chosen switch.</p>

    <!-- OPTIONAL: include this if you want history support -->
    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>

    <noscript>
      <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
        Your web browser must have JavaScript enabled
        in order for this application to display correctly.
      </div>
    </noscript>

    <div id="gameContainer"></div>
  </body>
</html>


问题

我主要关心的是:是否可以通过调试Javascript变量来预先弄清楚瓦片的值来作弊?我正在考虑制造更多GWT物品,但是如果可能作弊,那么我将不得不改变制造方式(例如,在实际行动之前动态生成物品)。

除此之外:我如何使用GWT?我不是很关注HTML和CSS内容,只是为了完整性而将它们包括在内。

未混淆的Javascript

可在此处找到未混淆的已编译Javascript:http://www.zomis.net/codereview/memory/memorygwt/

评论

太神奇了---几年前,我在学习编程时创建了一个完全相同的游戏名称。 vivatropolis.org/istvan/MemoryTwist.xhtml

建议轻微改善可用性。选择两个磁贴后,必须等待单击下两个磁贴。单击第三个磁贴应隐藏选定的两个并显示第三个。这样可以加快游戏速度。

#1 楼


游戏是可作弊的。
像大多数软件一样,只要有调试器,游戏就可作弊。
在这种情况下,我已经在Firefox中加载了FireBug,并学到了一些技巧。以下是游戏的作弊方式。
请注意,这是基于您在以下位置从您的来源玩的游戏:

可以在此处进行游戏:http://www.zomis.net/ codereview / memory / MemoryGWT.html


进程


启用FireBug


加载游戏(单击链接)


在Firebug的“脚本”选项卡中,选择script[9]并滚动到第273行。这是用于填充初始网格的JavaScript代码....:< br q


在该行附近启用断点


按f5刷新页面。这会导致页面部分加载,并且断点将在您设置的地方....


然后,在“监视”选项卡中,我们可以检查ints数组,这是初始化网格时单元格值的“混洗”集合。数组中的每个成员都记录了放置在每个位置的精确值。可以用来选择精确对,而不必担心错误。



利润?


注意事项
它我花了一点时间来精确地查找GWT内容的存储位置,大约..... 30分钟找到了FireBug,安装,学习了它的工作原理,追踪了事情的进展,了解了GWT如何从一系列数组中加载脚本分别加载的文本等。
更熟悉JavaScript,浏览器中的执行模型和调试工具的人可能会更快地完成它。难度更大,但概念仍然相同。

#2 楼

嗯,非常好!记忆力十足的练习(尽管有一点点,有点儿,虐待狂),
编写的代码很好,很难挑选:最终的可以是最终的,
参数经过验证,没有魔术数字或重复的字符串文字,格式正确,清晰易懂。我注意到您从改组后的列表中选择了最后一个元素,甚至HTML都通过了w3c验证程序。

Random对象仅使用一次,因此它也可以是局部变量,或者更好,只需将其嵌入到使用它的语句中即可。您想使其可测试,可以考虑将其添加为构造函数参数。无论如何,在当前代码中没有明显的理由使其成为成员字段。

尽管从末尾弹出ints的元素很聪明,完全可以:

for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
        int value = ints.get(x * width + height);


尽管从理论上讲这是“高效的”,但在您的用例中,这实际上可以忽略不计。
您可以很好地使用短而甜美且效率低下的ints.remove(0)
仍然没有实际的区别。

较小的可用性注意事项:存储卡上的数字从0开始,
在示例页面上的范围是0到17。
对极客来说很好,但是普通人可能会期望使用1-18。令人上瘾的是增加了许多试验。这样,我就会知道自己是否有所好转,否则很难说。在此基础上,最近完成的游戏的得分历史也将是伟大的。

评论


\ $ \ begingroup \ $
您关于从匹配的图块中删除点击侦听器的建议不仅是“次要优化”,而且还解决了一个错误;)
\ $ \ endgroup \ $
–西蒙·福斯伯格
14-10-19在19:08

\ $ \ begingroup \ $
我认为普通人根本不会指望记忆游戏中的数字,而是美好的图画。因此,我认为以0开头的数字比较合适。
\ $ \ endgroup \ $
–蒂姆
14-10-19在19:53

#3 楼

切换否定的if语句

我将在代码中切换if语句,如下所示:



我这样做有两个原因:读previousClicked != null!same容易,并且因为较短的代码位于顶部,所以嵌套后if(condition)语句完成的内容也变得更容易语句。

可用性

我将添加一项功能,如果用户单击任意位置,则可以立即隐藏两个打开的图块,而不是忽略单击并让计时器结束。可能只是我不耐烦,但是让我等待计时器非常烦人。
,view);


应该是if(!condition)吗?


是否可以通过调试Javascript变量来预先弄清楚瓦片的值来作弊?使用非混淆代码更容易测试。如果您更改示例,我敢肯定有人会尝试作弊。

评论


\ $ \ begingroup \ $
关于grid.setWidget:不,应该那样。 (我知道,感觉很奇怪...)
\ $ \ endgroup \ $
–西蒙·福斯伯格
14-10-19在20:08

\ $ \ begingroup \ $
至于混淆代码:我也可以提供非混淆版本(稍后提供),但是我将在诸如此类的任何游戏中实际使用的是混淆代码。
\ $ \ endgroup \ $
–西蒙·福斯伯格
14-10-19在20:11

#4 楼

我确信可以找到正确的Javascript文件并使用Cache重新创建数字网格,然后再实际单击任何游戏作品,但是,如果有人真的花时间欺骗了异常的硬盘游戏,那是很认真的,并且花时间找到文件,然后通读它们,甚至构建一个解析器将其弄清楚.....

真的有区别吗?你在卖这个游戏吗?您打算从这款游戏中赚钱吗?

如果您想证明它是傻瓜,那么应该使用服务器端变量来保存数字格,以保持秘密,除非您“翻过来”游戏来查看它是什么。


我将对此代码进行一些更改


private void clicked(FieldView view) {
    if (view == previousClicked) {
        return;
    }
    if (timerRunning) {
        return;
    }
    if (previousClicked != null) {
        boolean same = previousClicked.getValue() == view.getValue();
        view.showValue();
        if (!same) {
            Timer timer = new Timer() {
                @Override
                public void run() {
                    // switch the two values
                    int previous = previousClicked.getValue();
                    previousClicked.setValue(view.getValue());
                    view.setValue(previous);
                    view.hideValue();
                    previousClicked.hideValue();
                    previousClicked = null;
                    timerRunning = false;
                }
            };
            timerRunning = true;
            timer.schedule(2000);
        }
        else {
            previousClicked = null;
        }
    }
    else {
        view.showValue();
        previousClicked = view;
    }

}



我将使用以下方法组合前两个if语句一个“或”运算符,然后将guard子句移至if语句,以消除否定。因为它仅使用一次并被丢弃,所以我只将等式放入if语句中。 >我还在if / else语句周围使用了适当的括号。我喜欢那样做,有时希望C#标准可以让我以同样的方式做。 (嘘,不要告诉任何人我说过的话)

评论


\ $ \ begingroup \ $
谢谢您的回答,但是您的回答并没有触及我最初悬赏的目标:我仍在通过调试Javascript变量来寻找有关游戏是否“可玩”的答案
\ $ \ endgroup \ $
–西蒙·福斯伯格
2014-12-17 15:48

\ $ \ begingroup \ $
不,我不出售这款游戏。但是我正在考虑用GWT重写我的扫雷游戏-这就是为什么我想知道它是否可作弊。我知道使用服务器端是使其完全无法加热的唯一方法,我正在为Minesweeper使用服务器,但我也希望拥有一个独立的仅客户端版本。
\ $ \ endgroup \ $
–西蒙·福斯伯格
2014-12-18 15:58