is there a better way to structure data? how do you guys do it?

agolangf · · 761 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I am building a game and the code is getting grosser and grosser. I&#39;m wondering if theres a better way to structure data than my method. </p> <p>What do you guys/gals prefer: Custom types and helper functions, Nested interfaces and their methods, or just lists and helper functions, or what?</p> <p>I have something like this so far:</p> <pre><code>type Roomorlevel { players []int thingsplayerssaid []string thingsplayerscontrol map[int]int thingsplayersdid []event turn int board [][]int occupiedboard [][]bool boardevents []event playerbonuses []int .... x2 - about double this amt of structures so far } </code></pre> <p>I havent abstracted it to further types b/c its double the boilerplate code/work and then i have to remember all the method names... However my current code is like 2-3 loops of the various arrays every time a player does something, and its getting pretty gross... (Particularly because every array needs something different, for example when someone disconnected I have to loop and remove one item, when someone moves I have to loop and get the next item that has a powerup etc, (times 10))</p> <p>which way should I go from here?</p> <hr/>**评论:**<br/><br/>simon-whitehead: <pre><p>This is why people pull that sort of code apart into objects/methods though - for clarity. Its much easier to know what <code>PlayerDidXYZAction()</code> does just by looking at the method name than it is to mentally pull apart a heap of nested loops. It might seem like boilerplate but you&#39;re helping your future self/anyone else who looks at it.</p></pre>icub3d: <pre><p>If you are worried about readability some boiler plate is good. Those additional types will also help with testing. You might consider the work an investment on any work you might have to do in the future. Games generally have substantially more complexity and state than their designs may initially suggest, so an initial focus on readability isn&#39;t a bad thing. You can then optimize just the areas that need it after the fact.</p></pre>jerf: <pre><p>Well, a full answer depends a <em>lot</em> on things you don&#39;t discuss, and I do not have The One Answer, but there&#39;s definitely some little things we can discuss.</p> <p>First, for an OO design, you are leaving a lot of stuff on the table by using so many primitive types directly. Certainly step one is some more custom types. Board is an obvious one:</p> <pre><code>type Square struct { index [2]int occupied bool owner PlayerID // see below } type Board [][]Square </code></pre> <p>Manipulations of the board should become methods on Board, Square should grow methods too. <code>index</code> is actually something you should add only when you need it, in general you&#39;re better off not having that sort of reverse link, but it may be unavoidable.</p> <p>You also clearly have a Player object struggling to get out:</p> <pre><code>type PlayerID int type Player struct { id int history []event // a guess, depends on you &#34;events&#34; bonuses []int // probably some other type is called for here, BTW } </code></pre> <p>which has further methods.</p> <p>As I sit here and type this, the next problem I see is a bit more subtle; you&#39;ve got some concept smearing going on here. You&#39;ve got a board, which probably has some bits of what players own what (or, if not now, will after a good refactoring), but you&#39;ve got a separate &#34;thingsplayercontrol&#34; map that has some other elements of what they control, depending on how the &#34;events&#34; are working here they may also have some of that information in them. Probably part of the reason &#34;everything&#34; requires iterating over three arrays is that you&#39;ve got the relevant concepts smeared across three arrays.</p> <p>On that note, this suggests that you lack some indexing; even with our powerful modern computers you can&#39;t afford to iterate several times over even small multidimensional arrays too many times per second before you run out of cycles. Whatever tasks you&#39;re trying to do could probably be helped with some sort of pointer or index-based system storing something somewhere. Or, alternatively, if you really are iterating over and changing the entire board, you need to arrange things so you loop through once. &#34;Collect all events&#34; -&gt; &#34;Perform all the events&#34; -&gt; &#34;Present board for next move&#34; is a pretty powerful loop and you may literally be able to write that somewhere.</p> <p>Your types also suggest you have some sort of chat room available; that can be pulled out too:</p> <pre><code>type ChatMessage { speaker PlayerID text string } type PlayerChat struct { roomID int thingssaid []ChatMessage } </code></pre> <p>This is... not 100% accepted, but... I consider seeing an &#34;int&#34; or other primitive type in a struct to be a statement that there is <em>nothing</em> else this element is, that there is no other constraint I can express on this value other than that it is a number (and usually for <code>int</code> we don&#39;t even assume the user is truly assuming the size limits implied by int). For int, for instance, is it OK if I just stuff the value 2,000,000 into it? If not, it probably isn&#39;t an int. The reason why that&#39;s valid leads you to what it really is. For instance, if the answer is that 2,000,000 is unlikely to be a valid ID for a player, then you have a PlayerID. While I do end up with primitive values here and there in my structs (<code>string</code> is especially frequent as &#34;this is just a string of stuff that came from somewhere and I don&#39;t really know anything about it&#34; is fairly common), they are sort of the exception for me rather than the rule. In Go, I find this is particularly so because almost the instant I declare something even so simple as a <code>type PlayerID int</code> I find at least three methods that make sense to put on it.</p> <p>Last, but not least, I see:</p> <pre><code>type Roomorlevel { </code></pre> <p>It is not clear to me if the name of the game is &#34;Roomor&#34;, or if you mean <code>RoomOrLevel</code>. If the latter, well, while it isn&#39;t necessarily the end of the world to jam both in the same struct, the inevitable pressures of development will tend to drive the two concepts either ever further apart, in which case they really ought to be different structs, or further together until it is once again just <code>type Room</code> or something. As I suspect the former is more likely, you probably want to declare an interface:</p> <pre><code>type Location interface { // all the things a location can do. // This interface may get large. Do not be afraid of that; // &#34;library&#34; interfaces should generally be small but // I think end-user apps often end up needing a few // large interfaces, especially for places where you&#39;re // sort-of implementing some &#34;inheritance&#34; } </code></pre> <p>You then declare a <code>type Room</code> and <code>type Level</code>, which are both free to be themselves and not be carrying around the baggage of the other thing. You will likely find that a lot of the previous little objects I broke the system up into may be reused between the two of them, resulting in less reimplementation than you might think.</p> <p>One last note. If you are not using source control, do it now. Once you start doing this, do one object at a time. There&#39;s a temptation to do all of this at once but it&#39;s really very dangerous. :) Do one object extraction at a time, and commit after each one. If you are not unit testing, this is a great way to fold it back in; every time you extract one object and give it some methods, that&#39;s a great time to put the testing in. It is likely that if you just start doing these two things (extraction &amp; testing) that you&#39;ll find a virtuous cycle results, where the act of extracting out the code also reveals places where you can reuse things, the extraction of some concepts out into objects will further reveal where some objects were smeared into too many places, and show further things to extract out, which can then be cleanly tested. Run around this loop a few times and you&#39;ll have a completely different program on your hands.</p> <p>(If you&#39;re feeling <em>really</em> saucy, here&#39;s a <a href="https://github.com/thejerf/suture/blob/master/pre-commit" rel="nofollow">precommit hook</a> I use with git to ensure all my test pass before I commit. You may want to whack the golint part. Put this in .git/hooks, or symlink it there if appropriate. Other source control systems have different options for this.)</p></pre>buth3r: <pre><p>yep, there is :) try to study some existing open source code and learn from that.</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

761 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传