Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Steampunk Mario
Aug 12, 2004

DIAGNOSIS ACQUIRED
"Reduce dependencies by forcing your user to have essentially the same number of includes, only spread across many files and much more difficult to maintain!"

What a great idea.

Adbot
ADBOT LOVES YOU

notMordecai
Mar 4, 2007

Gay Boy Suicide Pact?
Sucking Dick For Satan??

Drx Capio posted:

If that's your entire file, then you're just dumping executable code in no-man's land. It needs to be in an execution path, like your main function for instance.

Well, obviously it's not the whole file, but thanks for pointing out I am a moron! For some reason I thought it was inside main this entire time... goddamn I need sleep. Jesus gently caress, thanks. :suicide:

ColdPie
Jun 9, 2006

Drx Capio posted:

"Reduce dependencies by forcing your user to have essentially the same number of includes, only spread across many files and much more difficult to maintain!"

What a great idea.

Yeah, not to mention they must be kept in a precise order. One.h must come before Two.h! Hope you don't forget!

Seems like a good idea on the surface, but yeah, that just doesn't work. Though it does seem like there should be a way to prevent this (actual picture from one of my recent projects).

ColdPie fucked around with this message at 05:22 on Mar 27, 2008

floWenoL
Oct 23, 2002

ColdPie posted:

Yeah, not to mention they must be kept in a precise order. One.h must come before Two.h! Hope you don't forget!

Seems like a good idea on the surface, but yeah, that just doesn't work. Though it does seem like there should be a way to prevent this (actual picture from one of my recent projects):

You know what else probably seemed like a good idea on the surface? You uploading an 800kb png and thumbnailing it.

floWenoL fucked around with this message at 05:24 on Mar 27, 2008

tyrelhill
Jul 30, 2006
Thats probably the most disorganized UML diagram I've ever seen.

ColdPie
Jun 9, 2006

floWenoL posted:

You know what else probably seemed like a good idea on the surface? You uploading an 800kb png and thumbnailing it.

Fair enough. Linked it, if you want to take it out of your post.

tyrelhill posted:

Thats probably the most disorganized UML diagram I've ever seen.

Doxygen :toot: I just had it analyze my project out of curiosity and saw that monster pop up. I figure there should be a way to prevent that kind of thing but hey v:)v

floWenoL
Oct 23, 2002

ColdPie posted:

I figure there should be a way to prevent that kind of thing but hey v:)v

No programming language will ever prevent bad design. :colbert:

ColdPie
Jun 9, 2006

floWenoL posted:

No programming language will ever prevent bad design. :colbert:

What would you suggest? It includes common utilities, constants, and structs that are used in a whole bunch of sources. What's the alternative? I'm seriously asking here, I would love to avoid that mess in future projects.

JoeNotCharles
Mar 3, 2005

Yet beyond each tree there are only more trees.

ColdPie posted:

What would you suggest? It includes common utilities, constants, and structs that are used in a whole bunch of sources. What's the alternative? I'm seriously asking here, I would love to avoid that mess in future projects.

Is the mess causing any concrete problems? Makefiles slow due to dependency analysis, etc? If not, don't bother trying to avoid it - the compiler's perfectly capable of dealing with crap like that. It's not 1989 anymore.

ColdPie
Jun 9, 2006

JoeNotCharles posted:

Is the mess causing any concrete problems? Makefiles slow due to dependency analysis, etc? If not, don't bother trying to avoid it - the compiler's perfectly capable of dealing with crap like that. It's not 1989 anymore.

Absolutely not, it works fine. It just feels messy and "bad designy" like he mentioned. If it's normal, all right, just doesn't seem like the best way to do things.

JoeNotCharles
Mar 3, 2005

Yet beyond each tree there are only more trees.

ColdPie posted:

Absolutely not, it works fine. It just feels messy and "bad designy" like he mentioned. If it's normal, all right, just doesn't seem like the best way to do things.

Well, one thing is that you're doing a global dependency track. That's always going to be both messy and useless. Look at each individual C++ file and you'll see something that looks a lot more like a plain tree for each (until they join together at that one file that's included by everyone).

In fact, even for the global imposing some order on that graph will help a lot. It looks like it was drawn with graphviz behind the scenes - graphviz's default format always leads to messy looking graphs. If you assigned ranks to the nodes so that all .cpp files were at the bottom and the layout favoured vertical rather than horizontal arrangements you'd get a much more pleasing lattice.

TSDK
Nov 24, 2003

I got a wooden uploading this one

Plastic Jesus posted:

I came across this a year or two ago:
Good christ, that guy's an idiot. My rule is that for each and every header, it should be possible to have a 2-line source file with:
code:
#include "HeaderFile.h"

That compiles without errors.

The reason why header files often get unruly is that people often #include when a simple forward declaration would be enough, there's not enough seperation of concepts in the source code structure, and people all too often get lazy with includes and form a single "IncludeFeckingEverything.h" header.

Plastic Jesus
Aug 26, 2006

I'm cranky most of the time.

TSDK posted:

Good christ, that guy's an idiot.

Yes, Rob Pike is an idiot.

Hey, at least my post generated more comments in this thread in 1 day than in the last week!

What I generally do now is have a monolithic include file to distribute with my libraries, but within each project I adhere to Pike's Law. One distinct advantage that's come of this is is a better understanding of which components use which other components, making bad design is much more blatant. Note also that 95% of my code is c, not c++, so it's easier to get away with this approach.

The real question is, how pissed off is the next developer to take over my projects going to be? Very, I assume.






Well of course he's not infallible. I was just responding to TSDK casually dismissing the dude as an idiot when he's one of the better engineers of his time.
vvvvvvvvvvvvvvvvvvvvvv

Plastic Jesus fucked around with this message at 16:45 on Mar 27, 2008

Avenging Dentist
Oct 1, 2005

oh my god is that a circular saw that does not go in my mouth aaaaagh

Plastic Jesus posted:

Yes, Rob Pike is an idiot.

Just because he's a famous programmer doesn't mean that he's right. Besides, like people have said, compilers are quite a bit smarter than they used to be, and the performance difference should be negligible (especially compared to template instantiation :xd:), while the maintenance difference is huge.

That Turkey Story
Mar 30, 2003

Plastic Jesus posted:

Yes, Rob Pike is an idiot.

I don't care who said it, good programming is not authoritative and that is some of the worst advice I've ever heard. It could come from the mouths of K&R and it'd still be garbage. In fact, it is so terrible that when I first read the quote I assumed it had to be a joke!

Plastic Jesus posted:

What I generally do now is have a monolithic include file to distribute with my libraries, but within each project I adhere to Pike's Law. One distinct advantage that's come of this is is a better understanding of which components use which other components, making bad design is much more blatant. Note also that 95% of my code is c, not c++, so it's easier to get away with this approach.

You don't think that adhering to this "law" is a bad design on it's own!?

:psyduck:

You don't force users to manually include all dependencies for lots of reasons, even apart from the obvious tediousness and room for error. Forcing other programmers to include the dependencies also means that the user has to worry about changing all of the files that include your header if dependencies are added or removed, not to mention the fact that you are requiring the user to know details that are specific to an implementation when in actuality they can be made entirely transparent. I can only imagine what would happen if you were working on a cross-platform project where different system headers had to be included depending on the operating system -- now all of a sudden the user of the header file has to do a preprocessor check for what OS they are targeting to figure out what additional header files they need to include. Dependencies should be automatically included, especially when they potentially vary depending on implementation.

Plastic Jesus posted:

The real question is, how pissed off is the next developer to take over my projects going to be? Very, I assume.

Most likely they would hunt you down and kill you. At least that's what I would probably do.

Plastic Jesus
Aug 26, 2006

I'm cranky most of the time.

That Turkey Story posted:

You don't think that adhering to this "law" is a bad design on it's own!?

You don't force users to manually include all dependencies for lots of reasons, even apart from the obvious tediousness and room for error.

I just said that I don't do this for libraries for exactly the reasons you just listed. I do it within my own projects for exactly the reasons I just listed. I will probably flatten things when they go to release so that you do not kill me. But during development it is _nice_ to know where dependencies lie.

TSDK
Nov 24, 2003

I got a wooden uploading this one

Plastic Jesus posted:

Yes, Rob Pike is an idiot.
TSDK's Law: Everyone is an idiot.

It doesn't matter how smart someone is, there is always some topic or opinion about which that person is an idiot. Of course, people often don't realise that they're an idiot about that particular thing. If they did, then by definition they would not be an idiot about it.

The smartest people are the ones who realise that there are things about which they are an idiot (see also: Socrates ;))

haveblue
Aug 15, 2005



Toilet Rascal

TSDK posted:

TSDK's Law: Everyone is an idiot.

It doesn't matter how smart someone is, there is always some topic or opinion about which that person is an idiot.

Yeah, but you wouldn't think that for someone with his resume that topic would be C.

Presto
Nov 22, 2002

Keep calm and Harry on.

floWenoL posted:

And compile time is a major bottleneck for large C/C++ programs. Pike's advice might be outdated, but including (non-system) headers willy-nilly is a good way to kill compile performance.
I have about 750,000 lines of code at work (C/C++/Fortran) that builds in 4 1/2 minutes on a fairly old dual 2.2 GHz Xeon machine with parallel make, so compile time isn't a real concern.

And anyway, everyone knows long compiles are an excuse to goof off for a while. :)

floWenoL
Oct 23, 2002

Plastic Jesus posted:

I just said that I don't do this for libraries for exactly the reasons you just listed. I do it within my own projects for exactly the reasons I just listed. I will probably flatten things when they go to release so that you do not kill me. But during development it is _nice_ to know where dependencies lie.

It is, but there are tools like makedepend for that. It is retarded to do something so tedious and error-prone manually for such little benefit.

floWenoL
Oct 23, 2002

Presto posted:

I have about 750,000 lines of code at work (C/C++/Fortran) that builds in 4 1/2 minutes on a fairly old dual 2.2 GHz Xeon machine with parallel make, so compile time isn't a real concern.

Honestly that's not a very large project; have you tried building one of Firefox, KDE, or OpenOffice lately? Anyway, the issue is not the from-scratch build time, but the one-line-header change build time and from making sure that that isn't approximately equal to the from-scratch build time.

more falafel please
Feb 26, 2005

forums poster

floWenoL posted:

Honestly that's not a very large project; have you tried building one of Firefox, KDE, or OpenOffice lately? Anyway, the issue is not the from-scratch build time, but the one-line-header change build time and from making sure that that isn't approximately equal to the from-scratch build time.

On my last project (about 1.5M lines of C++), cleaning up headers reduced our full build from about 4 hours to about 20 minutes, and reduced the frequency of full builds from about once a day on average to about once a week on average. Incremental builds were still about 5 minutes, mostly because of linking, but when you have that much code that needs to be statically linked, there's not much you can do about link times.

Most of this was due to the braindead way that the Unreal Engine generates headers from script classes -- all script classes belong to a "package," which is just a directory containing each of those script classes. For a package such as "Engine," there are about 100 script classes. Instead of generating one header for each of these, stock UE3 generates "EngineClasses.h" with all the declarations in one place (presumably so they don't have to worry about those pesky "dependencies" everyone keeps going on about). So you can guess how many C++ files include EngineClasses.h. We changed the script compiler so it generated a header for each class, and then started including only the headers necessary for each C++ file. That alone reduced our build times by about 90%. YMMV, obviously.

lmao zebong
Nov 25, 2006

NBA All-Injury First Team
I'm still pretty new to C++ (first-year student), and I have some quick questions for a personal program of mine.

I'm writing a program that will randomly generate a number between 1 and 20 for a specified number of players, and then team up the players based on what number they got. The pairings are based on highest number is teamed up with lowest number, and the two middle numbers are paired together. How would I go about doing this? I can code most of the program just fine, but don't really have the programming knowledge to do the 'teaming up' aspect of the program.

Also, after generating these random numbers, I need to set the order of who goes first, based on how biggest number to smallest. How could I do that?
But, just to complicate things up a bit, the teams must alternate on turns. For example, if the two players on team one gets a 20 and a 19, and team two gets a 1 and a 2, then the turns must go team one, then team two, then team one again, even though team one got both the highest numbers. I'm still fairly new when it comes to programming, so help is very much appreciated.

ShoulderDaemon
Oct 9, 2003
support goon fund
Taco Defender

Sarah Sherman posted:

I'm still pretty new to C++ (first-year student), and I have some quick questions for a personal program of mine.

You need to do a better job of describing what you want your program to do.

Sarah Sherman posted:

I'm writing a program that will randomly generate a number between 1 and 20 for a specified number of players, and then team up the players based on what number they got. The pairings are based on highest number is teamed up with lowest number, and the two middle numbers are paired together.

OK, so let's say there are four players: Alice, Bob, Carol, and Dennis. Alice rolls 20. Bob rolls 3. Carol rolls 7. Dennis rolls 14. The teams are (Alice, Bob) and (Carol, Dennis), right? What happens if Dennis rolled a 3? What happens if there are some number of players other than four?

Sarah Sherman posted:

How would I go about doing this? I can code most of the program just fine, but don't really have the programming knowledge to do the 'teaming up' aspect of the program.

How you would "team up" players depends on what you're going to do with the teams later on. We can discuss appropriate data structures after we have the rules of the game down, because they tend to depend on the application logic.

Sarah Sherman posted:

Also, after generating these random numbers, I need to set the order of who goes first, based on how biggest number to smallest. How could I do that?

If the teams are decided as you said above, then the team with (High roller,Low roller) always goes first, and the team with (Middle roller,Middle roller) always goes second.

Sarah Sherman posted:

But, just to complicate things up a bit, the teams must alternate on turns. For example, if the two players on team one gets a 20 and a 19, and team two gets a 1 and a 2,

As I understand your rules for how teams are formed, this is impossible.

Sarah Sherman posted:

then the turns must go team one, then team two, then team one again, even though team one got both the highest numbers. I'm still fairly new when it comes to programming, so help is very much appreciated.

I think the problem you're having is likely a problem very typical of new programming students: You think you understand what you want the computer to do, but you haven't yet set it down step by step, so you don't know how to program it. You're focusing on the "little" problems of your application instead of the "big picture", which makes everything more difficult because you aren't yet sure how the parts all need to fit together.

Don't worry, it's easy to fix! For new programmers, I always suggest a top-down approach, which is a little slower than "real" programming, but will help you get used to thinking the right way. (And, by the time you are dealing with larger projects, you'll be better prepared to use a more streamlined approach.) Do this:

1) Get a notebook. Seriously. Programming is mostly about writing down ideas in a very detailed way. The programming language is just a way that happens to be something the computer can run, but most of the work you're doing should be written down in English, at least until a year or more from now when you find yourself doing it automatically in your head. Make notes to yourself constantly -- what works, what doesn't work, what problems come up in debugging and how you fixed them, critiques you get from your TA or Prof, everything. It'll help you learn more about your personal strengths and weaknesses as a programmer, and you'll never be in a situation where you're looking at some code that you wrote and you can't understand how it works, or how it fits into the whole, or how to change it to do something new. All good programmers keep notes to themselves in some form, and a paper notebook is the best way to do it for new programmers because you don't have to learn to use some software tool and it's easy to just take it out whenever you have an idea, instead of having to wait until you can get to your computer. As you become more familiar with the kinds of notes that work well for you, and you get better at intuitively seeing how a program should be structured, you'll take fewer notes and may migrate to something more powerful like issue-tracking software or a personal wiki or something, but I know many professional programmers who maintain a paper notepad for every project they work on, and keep it by their side whenever they are working.

2) Write down the largest phases of your project. You're writing a turn-based game, so those should probably be something like "Set up a new game", "Take turns playing", and "Decide who won".

3) For each phase, break it down into the next largest phases. "Set up a new game", for example, may have phases like "Decide how many teams there will be", "Decide who is on which team". Continue this refinement process, breaking down phases into smaller and smaller parts, until the instructions are specific enough that someone reading them will understand all the rules of your game. Don't break them down past where it's easy to describe each step with a single normal English sentence.

4) What you have produced is pseudocode, a formal description of your program in a language that is easily readable, but very unambiguous. You should now work out how to organize your instructions into code and begin writing it, starting from the outermost phases and gradually filling in the inner phases.

As an example of the process, I'm going to make up a simple betting game and go through the steps of describing it here:

code:
Set up the game.
  There are two players, A and B.
  Both players start with $100.
  Players each roll a die to decide who plays first.
    Player with higher roll plays first.
    If both roll the same, each rolls again.
Take turns until one player runs out of money.
  Current player rolls a die, in secret.
  Current player announces some amount of money.
    Cannot be more than what either player has.
  Opposing player may either pay half (rounded down) the amount, or accept the bet.
    If opposing player pays, then the turn is over.
    If opposing player accepts, then they roll a die, and winner takes the money.
      Opposing player rolls a die.
      Both die rolls are revealed.
      If one die is higher than the other, loser pays winner the announced price.
      If both die are the same, then the turn is over, and neither player pays.
To make this, I started with just "Set up the game" and "Take turns until one player runs out of money", then added each further layer until I couldn't easily add more detail.

I now need to check over it a little. I need to keep track of the amount of money each player has, and I also need to know who's turn it is. Those are going to be pretty important variables.

To code this, I would start from the top again:

code:
int main ( int argc, char *argv[] ) {

  setup_game( );

  do {
    take_turn( );
  } while ( /* both players have money */ );

  return 0;

}
And, gradually, I fill it in...

code:
int player_money[2];

void setup_game ( void ) {

  player_money[0] = 100;
  player_money[1] = 100;

  // roll dice to decide who starts

}

int main ( int argc, char *argv[] ) {

  setup_game( );

  do {
    take_turn( );
  } while ( player_money[0] > 0 && player_money[1] > 0 );

  return 0;

}
and then refining it...

code:
int player_money[2];
int whos_turn;

void setup_game ( void ) {

  int player_die[2];

  player_money[0] = 100;
  player_money[1] = 100;

  do {

    player_die[0] = roll_die( );
    player_die[1] = roll_die( );

    if ( player_die[0] > player_die[1] ) {
      whos_turn = 0;
    } else if ( player_die[0] < player_die[1] ) {
      whos_turn = 1;
    };

  } while ( player_die[0] != player_die[1] );

}
And then I might add...

code:
void take_turn ( void ) {

  int secret_die;
  int bet;
  int bet_accepted;

  secret_die = roll_die( );

  tell_current_player( secret_die );

  bet = get_bet_from_current_player( );

  tell_opposing_player( bet );

  bet_accepted = ask_opposing_player_to_accept( );

  if ( bet_accepted ) {
    roll_to_see_who_wins( secret_die, bet );
  } else {
    player_money[whos_turn] += bet / 2;
    player_money[whos_turn] -= bet / 2;
  };

}
And so on, until I was finished. At each step of refinement, I'm trying to add in the code for only a few small steps of my pseudocode, so that I don't get confused and everything stays organized. If I need to refer to a step that I don't want to write the code for just this second, I replace it with a comment or a call to a function I haven't yet written. These are replaced or defined one by one, until the program is finished.

Once the code is finished, then it's on to the task of debugging and potentially refactoring. (say, deciding that some variables that you made global should really be parameters, or that your functions should be broken up differently.) Unfortunately, both debugging effectively and refactoring code are skills that are best taught through exposure and practice; I can't give you easy rules for them, but I strongly suggest that while you are learning to program, find a TA or Professor or somebody who can look over your code once you get to this point and suggest refinements or talk about how they would implement it.

I hope all this helps! If you can make a more clear description of your game, I'd be happy to help you with any particular parts you can't get implemented.

Incoherence
May 22, 2004

POYO AND TEAR

ShoulderDaemon posted:

I think the problem you're having is likely a problem very typical of new programming students: You think you understand what you want the computer to do, but you haven't yet set it down step by step, so you don't know how to program it. You're focusing on the "little" problems of your application instead of the "big picture", which makes everything more difficult because you aren't yet sure how the parts all need to fit together.
This is excellent advice, and if you read nothing else of his reply read that.

To say it another way, there are two steps involved in programming: specification (precisely stating what you want the program to do), and implementation (mapping that into code). New programmers often make the mistake of underestimating the former.

lmao zebong
Nov 25, 2006

NBA All-Injury First Team

ShoulderDaemon posted:

:words:
Thanks a lot for the help. I thought that I had this program under control, but now looking at your post I see that there is a lot more going on that I previously expected. Let me try to write some 'pseudocode' for you to explain how I'm thinking it should work.

code:
Program asks how many people are playing.
  If there are 4 players, ask if if it is a team game or no teams.
  Each player rolls a dice to see what number they get.
  If a player rolls the same number as another player, they both re-roll.
Teams are assigned based on (lowest, highest) and (medium, medium).
Order of turns is assigned based on highest roll to lowest roll.
I hope that is enough information about what I'm trying to do. I don't want the program to go any further, this is just so me and my friends can easily assign teams for a game we play without going through the motions of rolling actual dice.
The output that I'm looking for (if it was a 4 player game with teams) would simply be:
code:
Enter the number of players: 4
Is this a team game or free-for-all?
Enter T for team game, or enter F for free-for-all: T 

Player 1 rolled a 2!
Player 2 rolled a 14!
Player 3 rolled a 20!
Player 4 rolled a 7!

Team 1: Player 3 and Player 1
Team 2: Player 2 and Player 4

Order of turns is: Player 3, Player 2, Player 4, Player 1
I understand now what you mean about not looking at the 'bigger picture' of the program. When I was trying to think out this program before I got to a computer, I was just focusing on the smaller parts of the program, like rolling the dice, and when I finally got to a computer realized I was stuck because I don't know how to do the other aspects of the program. I've never used the rand( ) function before, so I don't really know how to access the number it will spit out and use that to set up teams, and order of turns.

bcrules82
Mar 27, 2003

Sarah Sherman posted:

I've never used the rand( ) function before, so I don't really know how to access the number it will spit out and use that to set up teams, and order of turns.

http://www.cplusplus.com/reference/clibrary/cstdlib/rand.html
http://www.cplusplus.com/reference/clibrary/cstdlib/srand.html
http://www.cplusplus.com/reference/clibrary/ctime/time.html

ShoulderDaemon
Oct 9, 2003
support goon fund
Taco Defender

Sarah Sherman posted:

I hope that is enough information about what I'm trying to do. I don't want the program to go any further, this is just so me and my friends can easily assign teams for a game we play without going through the motions of rolling actual dice.

OK, that makes this pretty easy.

Sarah Sherman posted:

The output that I'm looking for (if it was a 4 player game with teams) would simply be:
code:
Enter the number of players: 4
Is this a team game or free-for-all?
Enter T for team game, or enter F for free-for-all: T 

Player 1 rolled a 2!
Player 2 rolled a 14!
Player 3 rolled a 20!
Player 4 rolled a 7!

Team 1: Player 3 and Player 1
Team 2: Player 2 and Player 4

Order of turns is: Player 3, Player 2, Player 4, Player 1

That's a Team 1, Team 2, Team 2, Team 1 turn order; is that right? That makes this relatively simple, because we don't have to care about teams when deciding turn order...

Here's an implementation of this in C. It's all in the main function, which is terrible but saves me some typing. I tried to implement simple algorithms instead of "good" algorithms, so it does things in a pretty naive manner.

code:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main ( int argc, char *argv[] ) {

  int ret;
  int players;
  int teams;

  // Prepare the random number generator by seeding with the time.
  srandom( time( NULL ) );

  // Ask how many players are involved.
  fprintf( stdout, "How many players total? " );
  ret = fscanf( stdin, "%i\n", &players );
  assert( ret == 1 );
  assert( players <= 20 ); // If there are more than 20 players, then
                           // we will never not have duplicate rolls.

  // Ask if this is a team game.
  // Teams only make sense with an even number of players greater than 2.
  if ( players > 2 && players % 2 == 0 ) {

    char yesno;

    do { 
      fprintf( stdout, "Is this a team game? (y/n) " );
      ret = fscanf( stdin, "%c\n", &yesno );
    } while ( yesno != 'y' && yesno != 'Y' && yesno != 'n' && yesno != 'N' );

    if ( yesno == 'y' || yesno == 'Y' ) {
      teams = 1;
    } else {
      teams = 0;
    };

  } else {
    teams = 0;
  };

  int die[players];

  // This die-rolling logic is a little bit complicated...
  // I'm trying to preserve exactly the "if two people have the same roll,
  // then only they reroll" behaviour.

  // First, I set each die as "not-yet-rolled", for which I use the value -1.
  for ( int i = 0; i < players; ++i )
    die[i] = -1;

  // Now, for each player, I roll a die...
  for ( int i = 0; i < players; ++i ) {

    if ( die[i] == -1 ) {
      // This player needs to roll.
      die[i] = random( ) % 20;
      fprintf( stdout, "Player %i rolled %i.\n", i + 1, die[i] + 1 );
    };

    // Check to see if this roll is the same as any other roll before it.
    for ( int j = 0; j < i; ++j ) {

      if ( die[i] == die[j] ) {
        // These players rolled the same, so they both need to reroll.
        fprintf( stdout, "Player %i has the same roll as player %i, so they both reroll.\n", i + 1, j + 1 );
        // This is probably the subtlest part of the code I've written.
        die[i] = -1;
        die[j] = -1;
        i = j - 1;
        break; // Goes back to the outer loop.
      };

    };

  };

  // Print out the order of the players.
  // This would be better done by sorting a list of the players;
  // the algorithm I have here is equivalent to a bubble sort, but I'm
  // using it because it's simple and speed probably doesn't matter too much.
  // This is marginally subtle and kind of fun to prove why it works.

  int highest_score = 20; // Die rolls are 0..19, so this is higher than any of them.

  // This is a loop over play positions.
  for ( int i = 0; i < players; ++i ) {

    int next_highest_score = -1;
    int next_player;

    // And this is a loop over players.
    for ( int j = 0; j < players; ++j ) {

      // We are searching for the player with the largest die roll that is
      // still smaller than highest_score.
      if ( die[j] < highest_score && die[j] > next_highest_score ) {
        next_highest_score = die[j];
        next_player = j;
      };

    };

    fprintf( stdout, "Position %i: Player %i\n", i + 1, next_player + 1 );

    highest_score = next_highest_score;

  };

  // Print out the teams, if we're in team play mode.
  if ( teams ) {

    // The algorithm here mangles the die array,
    // which is why I'm doing it after printing the turn order.

    // For each team, I'm going to select the players with the highest and lowest
    // rolls that haven't yet been chosen. This loop is over teams.
    for ( int i = 0; i < players / 2; ++i ) {

      int lowest_player, lowest_roll;
      int highest_player, highest_roll;

      // Out of bounds values.
      lowest_roll = 20;
      highest_roll = -1;

      // And this loop is over players.
      for ( int j = 0; j < players; ++j ) {

        // If this player hasn't yet been chosen...
        if ( die[j] != -1 ) {

          // Check if they are the highest roll we've seen so far.
          if ( die[j] > highest_roll ) {
            highest_roll = die[j];
            highest_player = j;
          };

          // Check if they are the lowest roll we've seen so far.
          if ( die[j] < lowest_roll ) {
            lowest_roll = die[j];
            lowest_player = j;
          };

        };

      };

      // Mark the two players as chosen.
      die[highest_player] = -1;
      die[lowest_player] = -1;

      fprintf( stdout, "Team %i: Player %i and Player %i\n", i + 1, highest_player + 1, lowest_player + 1 );

    };

  };

  return 0;

}

Sarah Sherman posted:

I understand now what you mean about not looking at the 'bigger picture' of the program. When I was trying to think out this program before I got to a computer, I was just focusing on the smaller parts of the program, like rolling the dice, and when I finally got to a computer realized I was stuck because I don't know how to do the other aspects of the program. I've never used the rand( ) function before, so I don't really know how to access the number it will spit out and use that to set up teams, and order of turns.

I used random(3) and srandom(3) instead of rand(3) and srand(3) by force of habit because on some systems they're higher-quality; the usage for what you're doing is the same. There are a few tricks I used: most significantly, at one point I manipulate the index of a loop I'm in, to force it to go back over earlier work. I also felt free to scribble over data that I didn't need anymore; this is how I wrote the team-selection code without storing a lot of extra data about what teams I've already made.

If you need to alternate teams in play order instead of just going by die rolls, then you need to come up with a rule for exactly how that's done, and you'll need to store the teams instead of just printing them out, as well as calculating them before the turn order and without trashing the die rolls. This might be simpler if you fix the teams so they are only in the 4-player case instead of the n-player case as I did.

Incoherence
May 22, 2004

POYO AND TEAR

ShoulderDaemon posted:

code:
  // This die-rolling logic is a little bit complicated...
  // I'm trying to preserve exactly the "if two people have the same roll,
  // then only they reroll" behaviour.

  // First, I set each die as "not-yet-rolled", for which I use the value -1.
  for ( int i = 0; i < players; ++i )
    die[i] = -1;

  // Now, for each player, I roll a die...
  for ( int i = 0; i < players; ++i ) {

    if ( die[i] == -1 ) {
      // This player needs to roll.
      die[i] = random( ) % 20;
      fprintf( stdout, "Player %i rolled %i.\n", i + 1, die[i] + 1 );
    };

    // Check to see if this roll is the same as any other roll before it.
    for ( int j = 0; j < i; ++j ) {

      if ( die[i] == die[j] ) {
        // These players rolled the same, so they both need to reroll.
        fprintf( stdout, "Player %i has the same roll as player %i, so they both reroll.\n", i + 1, j + 1 );
        // This is probably the subtlest part of the code I've written.
        die[i] = -1;
        die[j] = -1;
        i = j - 1;
        break; // Goes back to the outer loop.
      };

    };

  };
I'm not convinced this is particularly fair (you resolve ties as they happen, instead of having everyone roll then resolving ties). Not that it terribly matters for this application, but you might get around this by having everyone roll once, then making another pass through the whole loop to resolve ties, and then you might end up doing the same thing again recursively... okay, never mind, we're getting way off the scope of Sarah Sherman's problem.

ShoulderDaemon
Oct 9, 2003
support goon fund
Taco Defender

Incoherence posted:

I'm not convinced this is particularly fair (you resolve ties as they happen, instead of having everyone roll then resolving ties). Not that it terribly matters for this application, but you might get around this by having everyone roll once, then making another pass through the whole loop to resolve ties, and then you might end up doing the same thing again recursively... okay, never mind, we're getting way off the scope of Sarah Sherman's problem.

I didn't want to do a separate pass, because it introduces the possibility of ties with arity greater than two and dealing with simultaneous ties, both of which somewhat complicate the logic. Although I suppose my effective ordering on the rolls is essentially cheating in that the "real world" does deal with those situations.

Of course, the correct approach is to just stick the player indices in an array and shuffle that with something like a Mersenne twister. I was more interested in trying to show how to go from a spec to code; Sarah Silverman should find a local mentor or TA or someone who can go over their code with them and point out where a more general common algorithm would be better suited for what they're doing.

lmao zebong
Nov 25, 2006

NBA All-Injury First Team

Thanks for those links. I was a bit confused as to how the rand function worked, but these references really helped. I'll be sure to bookmark that website if I have any other questions about other aspects of C++.

ShoulderDaemon posted:

code:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main ( int argc, char *argv[] ) {

  int ret;
  int players;
  int teams;

  // Prepare the random number generator by seeding with the time.
  srandom( time( NULL ) );

  // Ask how many players are involved.
  fprintf( stdout, "How many players total? " );
  ret = fscanf( stdin, "%i\n", &players );
  assert( ret == 1 );
  assert( players <= 20 ); // If there are more than 20 players, then
                           // we will never not have duplicate rolls.

  // Ask if this is a team game.
  // Teams only make sense with an even number of players greater than 2.
  if ( players > 2 && players % 2 == 0 ) {

    char yesno;

    do { 
      fprintf( stdout, "Is this a team game? (y/n) " );
      ret = fscanf( stdin, "%c\n", &yesno );
    } while ( yesno != 'y' && yesno != 'Y' && yesno != 'n' && yesno != 'N' );

    if ( yesno == 'y' || yesno == 'Y' ) {
      teams = 1;
    } else {
      teams = 0;
    };

  } else {
    teams = 0;
  };

  int die[players];

  // This die-rolling logic is a little bit complicated...
  // I'm trying to preserve exactly the "if two people have the same roll,
  // then only they reroll" behaviour.

  // First, I set each die as "not-yet-rolled", for which I use the value -1.
  for ( int i = 0; i < players; ++i )
    die[i] = -1;

  // Now, for each player, I roll a die...
  for ( int i = 0; i < players; ++i ) {

    if ( die[i] == -1 ) {
      // This player needs to roll.
      die[i] = random( ) % 20;
      fprintf( stdout, "Player %i rolled %i.\n", i + 1, die[i] + 1 );
    };

    // Check to see if this roll is the same as any other roll before it.
    for ( int j = 0; j < i; ++j ) {

      if ( die[i] == die[j] ) {
        // These players rolled the same, so they both need to reroll.
        fprintf( stdout, "Player %i has the same roll as player %i, so they both reroll.\n", i + 1, j + 1 );
        // This is probably the subtlest part of the code I've written.
        die[i] = -1;
        die[j] = -1;
        i = j - 1;
        break; // Goes back to the outer loop.
      };

    };

  };

  // Print out the order of the players.
  // This would be better done by sorting a list of the players;
  // the algorithm I have here is equivalent to a bubble sort, but I'm
  // using it because it's simple and speed probably doesn't matter too much.
  // This is marginally subtle and kind of fun to prove why it works.

  int highest_score = 20; // Die rolls are 0..19, so this is higher than any of them.

  // This is a loop over play positions.
  for ( int i = 0; i < players; ++i ) {

    int next_highest_score = -1;
    int next_player;

    // And this is a loop over players.
    for ( int j = 0; j < players; ++j ) {

      // We are searching for the player with the largest die roll that is
      // still smaller than highest_score.
      if ( die[j] < highest_score && die[j] > next_highest_score ) {
        next_highest_score = die[j];
        next_player = j;
      };

    };

    fprintf( stdout, "Position %i: Player %i\n", i + 1, next_player + 1 );

    highest_score = next_highest_score;

  };

  // Print out the teams, if we're in team play mode.
  if ( teams ) {

    // The algorithm here mangles the die array,
    // which is why I'm doing it after printing the turn order.

    // For each team, I'm going to select the players with the highest and lowest
    // rolls that haven't yet been chosen. This loop is over teams.
    for ( int i = 0; i < players / 2; ++i ) {

      int lowest_player, lowest_roll;
      int highest_player, highest_roll;


      // Out of bounds values.
      lowest_roll = 20;
      highest_roll = -1;

      // And this loop is over players.
      for ( int j = 0; j < players; ++j ) {

        // If this player hasn't yet been chosen...
        if ( die[j] != -1 ) {

          // Check if they are the highest roll we've seen so far.
          if ( die[j] > highest_roll ) {
            highest_roll = die[j];
            highest_player = j;
          };

          // Check if they are the lowest roll we've seen so far.
          if ( die[j] < lowest_roll ) {
            lowest_roll = die[j];
            lowest_player = j;
          };

        };

      };

      // Mark the two players as chosen.
      die[highest_player] = -1;
      die[lowest_player] = -1;

      fprintf( stdout, "Team %i: Player %i and Player %i\n", i + 1, highest_player + 1, lowest_player + 1 );

    };

  };

  return 0;

}
Holy poo poo, after looking over this code I understand (vaguely) what is happening and what is going on in each section of the code, but I definitely do not have the programming skill to actually write a program like this. Thanks a lot for the help and discussion guys, but it looks like I'm going to have to scrap this project until I know more. Once I think I have the skills to actually write this program, then I'll for sure speak up again if I need some help. This thread is a great resource, and I enjoy keeping up with this thread (and this subforum in general).

JoeNotCharles
Mar 3, 2005

Yet beyond each tree there are only more trees.

Sarah Sherman posted:

Holy poo poo, after looking over this code I understand (vaguely) what is happening and what is going on in each section of the code, but I definitely do not have the programming skill to actually write a program like this. Thanks a lot for the help and discussion guys, but it looks like I'm going to have to scrap this project until I know more. Once I think I have the skills to actually write this program, then I'll for sure speak up again if I need some help. This thread is a great resource, and I enjoy keeping up with this thread (and this subforum in general).

If you're just doing this for yourself, and not a school project, try Python. The major structure of the program will be the same, but you won't have to spend so much time worrying about C syntax and arcane function naming.

Steampunk Mario
Aug 12, 2004

DIAGNOSIS ACQUIRED
I'd like to add that generally if you want a set of unique numbers randomly selected from another set, it's cheaper to simply sort your set of available numbers with a random metric, then just pull your 'rolls' from the front of the randomly sorted array.

(pseudocode, may not compile as it's a mix of C99, C++, and garbage)
code:
int availableNums[20];
for( unsigned i = 0; i < 20; ++i )
  availableNums[i] = i + 1;

int RandPred( int num1, int num2 )
{
  return rand()%3 - 1;
}

std::sort( availableNums, availableNums + 20, RandPred );

int rolls[NUM_PLAYERS];
for( unsigned i = 0; i < NUM_PLAYERS; ++i )
  rolls[i] = availableNums[i];

ShoulderDaemon
Oct 9, 2003
support goon fund
Taco Defender

Drx Capio posted:

I'd like to add that generally if you want a set of unique numbers randomly selected from another set, it's cheaper to simply sort your set of available numbers with a random metric, then just pull your 'rolls' from the front of the randomly sorted array.

Sorting with a random metric is O(n ln2 n) and potentially has problems if your sorting algorithm is stable, but you can shuffle an array in linear time with, for example, the Fisher-Yates shuffle:

code:
int nums[20];
int j;
int temp;

for ( int i = 0; i < 20; ++i ) {
  j = random( ) % (i + 1);
  temp = nums[i];
  nums[i] = nums[j];
  nums[j] = temp;
};

Steampunk Mario
Aug 12, 2004

DIAGNOSIS ACQUIRED

ShoulderDaemon posted:

Sorting with a random metric is O(n ln2 n) and potentially has problems if your sorting algorithm is stable, but you can shuffle an array in linear time with, for example, the Fisher-Yates shuffle:

code:
int nums[20];
int j;
int temp;

for ( int i = 0; i < 20; ++i ) {
  j = random( ) % (i + 1);
  temp = nums[i];
  nums[i] = nums[j];
  nums[j] = temp;
};

I think the complexity of sorting an array of 20 numbers is pretty insignificant, and I only gave the pseudocode as an example. The person writing the program would probably prioritize good random numbers over a fast shuffle. Besides, I would probably just use random_shuffle anyway.

floWenoL
Oct 23, 2002

Drx Capio posted:

I think the complexity of sorting an array of 20 numbers is pretty insignificant, and I only gave the pseudocode as an example. The person writing the program would probably prioritize good random numbers over a fast shuffle. Besides, I would probably just use random_shuffle anyway.

I think the person writing the program would probably prioritize "having my program not crash" over anything else, which sorting with a random metric would do. To fix that, you'd have to make sure that your predicate returns consistent results if you pass it the same pair twice, at which point your level of complexity is way beyond just doing the shuffle.

ShoulderDaemon
Oct 9, 2003
support goon fund
Taco Defender

Drx Capio posted:

Besides, I would probably just use random_shuffle anyway.

That uses the algorithm I posted.

fret logic
Mar 8, 2005
roffle
Where's a good place to start for programming simple graphics in C? Nothing fancy, just lines or pixels to get me started. I've tried to decipher the source code for nethack, as far as how they print the ASCII characters in different places, and it's a bit above me heh.

haveblue
Aug 15, 2005



Toilet Rascal

fret logic posted:

Where's a good place to start for programming simple graphics in C? Nothing fancy, just lines or pixels to get me started. I've tried to decipher the source code for nethack, as far as how they print the ASCII characters in different places, and it's a bit above me heh.

Manipulating a character display the way NetHack does isn't really the same as "lines or pixels".

The best way to quickly make something appear on the screen is probably OpenGL with GLUT. A program that opens a window and draws some simple shapes can come in under 30 lines. Once you're comfortable with that, you can leave GLUT behind to do your own context setup, and you're off and running.

Adbot
ADBOT LOVES YOU

Steampunk Mario
Aug 12, 2004

DIAGNOSIS ACQUIRED

floWenoL posted:

I think the person writing the program would probably prioritize "having my program not crash" over anything else, which sorting with a random metric would do. To fix that, you'd have to make sure that your predicate returns consistent results if you pass it the same pair twice, at which point your level of complexity is way beyond just doing the shuffle.

Hey, I didn't even guarantee that it would compile, so getting far enough to crash is pretty ambitious for my pseudocode! v:)v

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply