Revisiting Rock Paper Scissors

Rock paper scissors was one of the first programs I’ve ever written, and I think it would be fun to see how different it would be if I coded it today. Preferably I would like to avoid nested ifs. So, here’s what I came up with:

#include <iostream>
#include <array>
#include <algorithm>
#include <cstdlib>
using namespace std;
int main()
{
  int player_points = 0;
  int computer_points = 0;
  const int max_points = 3;
  const char* winner = nullptr;
  const array< char, 3 > choices = { 'r', 'p', 's' };
  while( player_points < max_points && computer_points < max_points )
  {
    cout << "r p s?";
    char player_choice;
    cin >> player_choice;
    if( std::find(
      choices.begin(),
      choices.end(),
      player_choice ) == choices.end() )
    {
      cout << "invalid input" << endl;
      continue;
    }
    char computer_choice = choices[ rand() % choices.size() ];
    cout << "computer choice: " << computer_choice << endl;
    auto get_choice_index = [&]( char choice ){ return std::find(
      choices.begin(),
      choices.end(),
      choice ) - choices.begin(); };
    int player_index = get_choice_index( player_choice );
    int computer_index = get_choice_index( computer_choice );
    if( player_index == computer_index )
    {
      winner = nullptr;
    }
    else if( ( player_index + 1 ) % choices.size() == computer_index )
    {
      winner = "computer";
      computer_points++;
    }
    else
    {
      winner = "player";
      player_points++;
    }
    if( winner )
      cout << "point to " << winner << endl
    else
      cout << "tie" << endl
    cout << "score" << endl
      << "player: " << player_points << endl
      << "computer: " << computer_points << endl << endl;
  }
  cout << "winner: " << winner << endl
    << "game over" << endl;
  return 0;
}

Can copy paste errors be avoided?

I ran into this bug while coding last night

mRegisteredComponents[ TacComponentType::Stuff ] = TacRegisterComponent(
  "Say",
  [](){ return new TacSay(); } );
mRegisteredComponents[ TacComponentType::Say ] = TacRegisterComponent(
  "Stuff",
  [](){ return new TacStuff(); } );

That made me think:
How can I avoid this mistake in the future?
I’ll just… be more careful?

Then two hours later I found out that I had written this:

mRegisteredComponents[ TacComponentType::Stuff ] = TacRegisterComponent(
  "Stuff",
  [](){ return new TacStuff(); },
  TacStuffBits );
mRegisteredComponents[ TacComponentType::Say ] = TacRegisterComponent(
  "Say",
  [](){ return new TacSay(); },
  TacStuffBits );
mRegisteredComponents[ TacComponentType::Model ] = TacRegisterComponent(
  "Model",
  [](){ return new TacModel(); },
  TacModelBits );

For lack of a better solution, that function now enforces correctness through ridicule, containing the following comment:

// copy paste error count: 2

 

Picking in 3D

I’m currently working on Tac5Cool engine, the 5th iteration of Tac engine. Iteration might be too strong a word here, since it implies that each Tac engine is better than the last. Anyway, I’m at the stage where I need to select objects and move them about. Given my mouse pixel coordinates, object 3D world space coordinates, and camera info, i need to find the first out what’s being clicked.

I think the future method of deriving math is through Photoshop, so I’m ditching paper. Also, for now let’s assume that every object is a unit sphere.

Finding the world space mouse direction

auto ab = gameInterface->mRenderer->GetPerspectiveProjectionAB( instance.f, instance.n );
auto a = ab.A;
auto b = ab.B;

auto fboTexture = instance.mFBOTexture;
const v2 fboDimensions(
  ( r32 )fboTexture->mWidth,
  ( r32 )fboTexture->mHeight );
const r32 fboAspectRatio
  = ( r32 )fboTexture->mWidth
  / ( r32 )fboTexture->mHeight;

m4 proj;
ComputePerspectiveProjMatrix( ab, proj, instance.fovYRad, fboAspectRatio );
auto sx = proj( 0, 0 );
auto sy = proj( 1, 1 );

r32 mouseZViewSpace = -b / a; // <-- near plane. far plane is -b / ( a + 1 )
r32 mouseXViewSpace = -mouseZViewSpace * mouseNDC[ 0 ] / sx;
r32 mouseYViewSpace = -mouseZViewSpace * mouseNDC[ 1 ] / sy;

v4 mouseViewSpace( mouseXViewSpace, mouseYViewSpace, mouseZViewSpace, 1 );

m4 invview;
ComputeInverseViewMatrix( invview, instance.camPos, instance.camViewDir, instance.camWorldUp );

v3 mouseNearPlanePosWorldSpace = ( invview * mouseViewSpace ).xyz();
v3 mouseDirWorldSpace = Normalize( mouseNearPlanePosWorldSpace - instance.camPos );

Ray-sphere intersection

TacRaycastResult TacRaySphere(
 v3 rayPos,
 v3 rayDir,
 v3 spherePos,
 r32 sphereRad )
{
  TacRaycastResult result;

  auto sphereRadSq = Square( sphereRad );
  if( DistanceSq( rayPos, spherePos ) < sphereRadSq )
    return result;

  v3 v = spherePos - rayPos;
  // ax^2 + bx + c = 0
  // x = ( -b +/= sqrt( b^2 - 4ac ) ) / 2a
  r32 a = Dot( rayDir, rayDir );
  r32 b = -2 * Dot( rayDir, v );
  r32 c = Dot( v, v ) - sphereRadSq;

  r32 t = -b - std::sqrt( Square( b ) - 4 * a * c );
  t /= 2 * a;

  if( t > 0 )
  {
    result.mIntersected = true;
    result.mDistance = t;
  }
  return result;
}

Where the cross product comes from

Thanks to David Eberly for teaching me.
The cross product comes from the determinant.

Let’s take a step back.
Say we have 2D vector a = ( Xa, Ya ), and we want to find a vector perpendicular to it. If we stick the X basis vector, Y basis vector, and a into a 2×2 matrix, the determinant results in a vector perpendicular to a.


X = ( 1, 0 )
Y = ( 0, 1 )
a = ( Xa, Ya )

det| X  Y  | = X * Ya -
   | Xa Ya |   Y * Xa
             = ( 1, 0 ) * Ya -
               ( 0, 1 ) * Xa
             = ( Ya, -Xa )

2dperp

As you can see, this is a clockwise rotation by 90 degrees.

Moving to 3D, in order to have a 3×3 matrix, we need another vector.
Sticking the basis vectors X, Y, and Z into our matrix along with a = ( Xa, Ya, Za ) and b = ( Xb, Yb, Zb ) and taking the determinant results in a vector perpendicular to both a and b – our friend the cross product.


let X = ( 1 , 0 , 0  )
let Y = ( 0 , 1 , 0  )
let Z = ( 0 , 0 , 1  )
let a = ( Xa, Ya, Za )
let b = ( Xb, Yb, Zb )

det| X  Y  Z  | = X * ( Ya * Zb - Za * Yb ) -
   | Xa Ya Za |   Y * ( Xa * Zb - Za * Xb ) +
   | Xb Yb Zb |   Z * ( Xa * Yb - Ya * Xb )
                = ( 1, 0, 0 ) * ( Ya * Zb - Za * Yb ) -
                  ( 0, 1, 0 ) * ( Xa * Zb - Za * Xb ) +
                  ( 0, 0, 1 ) * ( Xa * Yb - Ya * Xb )
                = ( Ya * Zb - Za * Yb,
                    Za * Xb - Xa * Zb,
                    Xa * Yb - Ya * Xb )

Some vim tips

Incrementing numbers
C-a

and

C-x

  increments/decrements the next number.

:help CTRL-a

shows how to macro a numbered list. r/vim points out that your cursor can be before the number and 

C-a

 will still increment it. Similarly, your cursor can be before the quotes for

ci"

 to edit it. This works with

da{

, etc.

Scrolling
C-d

/

C-u

scroll by default by half the screen, but can be customized like

7C-u

to scroll 7 lines at once. In my opinion, scrolling with 

1C-d

 is superior to holding down

j

 because it allow me to scroll while my cursor remains centered in the middle of the screen, so I know where I’m going and don’t need to

zz

after scrolling.

Fixing typos

If you make a typo in insert mode, 

C-w

 deletes the previous word, so you can keep typing as if nothing ever happened.