When I play games I don’t really play anything new. Heck, the last game I bought as it was released was Final Fantasy X, the original version.
The one game I do play and perhaps play too much, is Intelligent Qube (or Kurushi since I’m a contrary European) on the original Playstation. I usually speedrun it, but then I found a high score for the game on Twin Galaxies of 1,244,800 with an IQ record of 506. This was apparently achieved on the default difficulty (which is the easiest), nevertheless if you’ve played it you’ll understand that they’re both pretty great scores.
The scoring mechanics for the game are relayed via the tutorial and otherwise easily observable so anybody doing a score attack can gauge how they’re progressing and what they need to do. How the IQ score is calculated on the other hand is not public knowledge. So seeing as I like to know how things work, can read PlayStation (MIPS) assembly, and some clever people have fitted debuggers into PlayStation emulators, I went to uncover it.
The results I found contained some rather unexpected information.
As you would expect from a) puzzle games and b) games in general, the nature of Intelligent Qube is that it gets harder as you progress. The puzzles start out four cubes wide by two cubes deep with the odd forbidden cube here and there and progress up to seven wide and nine deep with a myriad of black blocks sprinkled throughout.
Knowing that and that IQ is apparently a more considered measure of how proficient you are at the game than raw score, then perhaps you’d expect that as the puzzles get harder, the IQ you receive becomes larger. Because if you can do the harder puzzles you’re better at the game than someone who can’t, right? Nope!
As you can see with the following picture, with identical performance on each stage the IQ doled out tanks at a steady rate of 5 points per stage. It also gives a good indication of the algorithms in use.
The algorithms used by the games code suggests they are far more involved than they are. For instance, this is the direct C translation of the machine code that calculates the IQ for the first stage.
int CalcStage1IQ(int multipliedScore) { int onePercent = multipliedScore / 100; int fourPercent = onePercent * 4; int fivePercent = onePercent + fourPercent; int seventyFivePercent = fivePercent * 15; return (seventyFivePercent * 8) / 10000; // e.g. 600% of your score divided by 10000 }
Compilers can and usually do transform the operations you code so that they’re optimal for the processor to consume. Even with that, I don’t know why the score wasn’t just multiplied by six instead of going through all these intermediate steps before being divided by 10000 but either way the end result is the same. 0.06% of your score is your IQ on the first stage.
One thing to note is that the score used for each IQ calculation isn’t your total score that’s displayed on the screen – its the score accumulated on the current stage, which the game tracks separately. This stage score is then multiplied by a modifier according to the game speed level you’re currently playing on. These are:
Level 0 – 1x
Level 1 – 1.25x
Level 2 – 1.33x
Level 3 – 1.45x
Level 4 – 1.5x
This modified score is the one passed to the IQ calculation function for each stage.
So as seen, the IQ starts out at 0.06% of this multiplied stage score for the first stage and then decreases by 0.005% for each subsequent stage. Even with the impossible 100,000 score for the final stage as shown in the picture above, you still only get 1/3 of the IQ than you’d get for achieving the same score on the first stage. In context, 100,000 for a score attack on the first stage is between 30 to 50 thousand away from where you want to be. So you get three times the IQ for a mediocre stage 1 score than for an impossibly brilliant Final stage score! Go figure.
If you game over on any stage, the same calculations are made as if you’d completed the stage. Obviously though you’d have a lower stage score and produce a lower IQ than if you had finished it. Contiuing the game resets your IQ and score.
One more thing to note is that IQ caps at 999, though you won’t ever be reaching that on Level 0. I’ve only taken a quick look at the sequel I.Q/Kurushi Final, but searching for the magic numbers doesn’t bring up any similar code sequences suggesting the calculation is different.
That about wraps it up. It really wasn’t as interesting or involved as I’d hoped/expected but even so, it’s always nice to pin down things you’re interested in. Below is the original MIPS machine code and the C equivalent functions for all stages, and the IQ Tracker tool pictured above can be downloaded from the IQ Tracker tool link a few words ago. The source code for it and other tools are also available on github.
Oh and I did beat the Twin Galaxies score(s):
In summary:
Stage 1 IQ = 0.06% of multiplied stage score
Stage 2 IQ = 0.055% of multiplied stage score
Stage 3 IQ = 0.05% of multiplied stage score
Stage 4 IQ = 0.045% of multiplied stage score
Stage 5 IQ = 0.04% of multiplied stage score
Stage 6 IQ = 0.035% of multiplied stage score
Stage 7 IQ = 0.03% of multiplied stage score
Stage 8 IQ = 0.025% of multiplied stage score
Final Stage IQ = 0.02% of multiplied stage score
int CalcStage1IQ(int multipliedScore) { int onePercent = multipliedScore / 100; int fourPercent = onePercent * 4; int fivePercent = onePercent + fourPercent; int seventyFivePercent = fivePercent * 15; return (seventyFivePercent * 8) / 10000; // e.g. 600% of your score divided by 10000 } int CalcStage2IQ(int multipliedScore) { int onePercent = multipliedScore / 100; int sixteeenPercent = onePercent * 16; int seventeenPercent = onePercent + sixteeenPercent; int newScore = seventeenPercent * 4; // 68% newScore = newScore + onePercent; // 69% newScore = newScore * 4; // 276% newScore = newScore - onePercent; // 275% return (newScore * 2) / 10000; // 550% / 10000 } int CalcStage3IQ(int multipliedScore) { return multipliedScore / 2000; } int CalcStage4IQ(int multipliedScore) { int onePercent = multipliedScore / 100; int eightPercent = onePercent * 8; int sevenPercent = eightPercent - onePercent; int newScore = sevenPercent * 32; // 224% newScore = newScore + onePercent; // 225% return (newScore * 2) / 10000; // 450% / 10000 } int CalcStage5IQ(int multipliedScore) { return multipliedScore / 2500; } int CalcStage6IQ(int multipliedScore) { int onePercent = multipliedScore / 100; int twoPercent = onePercent * 2; int threePercent = twoPercent + onePercent; int newScore = threePercent * 4; // 12% newScore = newScore - onePercent; // 11% newScore = newScore * 16; // 176% newScore = newScore - onePercent; // 175% return (newScore * 2) / 10000; // 350% / 10000 } int CalcStage7IQ(int multipliedScore) { int onePercent = multipliedScore / 100; int fourPercent = onePercent * 4; int fivePercent = fourPercent + onePercent; int newScore = fivePercent * 16; // 80% newScore = newScore - fivePercent; // 75% return (newScore * 4) / 10000; // 300% / 10000 } int CalcStage8IQ(int multipliedScore) { return multipliedScore / 4000; } int CalcFinalStageIQ(int multipliedScore) { return multipliedScore / 5000; } int MultiplyStageScore(int score) { switch (currentLevel) { case 0: default: { // 1x the score return score; } break; case 1: // 1.25x the score { return score + (score / 4); } break; case 2: // 1.33x the score { return score + ((score * 3) / 10); } break; case 3: // 1.45x the score { return score + ((score * 9) / 20); } break; case 4: // 1.5x the score { return score + (score / 2); } break; } }
This is the actual MIPS machine code for CalculateIQ function from the PAL version rom. The US version is identical (though at a different code address). The original JP version uses the same formulas but uses different instructions – likely due to using an older version of the compiler
TEXT:80033968 CalculateIQ: # CODE XREF: sub_8001F520+118p TEXT:80033968 # sub_80031608+84p ... TEXT:80033968 lbu $a0, g_currentGameSpeedLevel # 8006D575 TEXT:80033970 nop TEXT:80033974 sltiu $v0, $a0, 5 TEXT:80033978 beqz $v0, loc_80033A24 # jumptable 80033990 default case TEXT:8003397C sll $v0, $a0, 2 TEXT:80033980 lw $v0, levelJumpTable($v0) TEXT:8003398C nop TEXT:80033990 jr $v0 # switch 5 cases TEXT:80033994 nop TEXT:80033998 # --------------------------------------------------------------------------- TEXT:80033998 TEXT:80033998 level0: # CODE XREF: CalculateIQ+28j TEXT:80033998 # DATA XREF: TEXT:levelJumpTableo TEXT:80033998 lw $v1, g_levelScore # jumptable 80033990 case 0 TEXT:800339A0 j loc_80033A24 # jumptable 80033990 default case TEXT:800339A4 nop TEXT:800339A8 # --------------------------------------------------------------------------- TEXT:800339A8 TEXT:800339A8 level1: # CODE XREF: CalculateIQ+28j TEXT:800339A8 # DATA XREF: TEXT:levelJumpTableo TEXT:800339A8 lw $v1, g_levelScore # jumptable 80033990 case 1 TEXT:800339B0 j loc_80033A20 TEXT:800339B4 srl $v0, $v1, 2 # 1.25 * the score TEXT:800339B8 # --------------------------------------------------------------------------- TEXT:800339B8 TEXT:800339B8 level2: # CODE XREF: CalculateIQ+28j TEXT:800339B8 # DATA XREF: TEXT:levelJumpTableo TEXT:800339B8 lw $a0, g_levelScore # jumptable 80033990 case 2 TEXT:800339C0 li $v1, 0xCCCCCCCD # mul by 0xcccccccd & shr 3 is short for divide by 10 TEXT:800339C8 sll $v0, $a0, 1 TEXT:800339CC addu $v0, $a0 TEXT:800339D0 multu $v0, $v1 # this multiplies the score by 3 and divides it by 10 TEXT:800339D4 mfhi $a1 TEXT:800339D8 srl $v0, $a1, 3 TEXT:800339DC j loc_80033A24 # jumptable 80033990 default case TEXT:800339E0 addu $v1, $a0, $v0 TEXT:800339E4 # --------------------------------------------------------------------------- TEXT:800339E4 TEXT:800339E4 level3: # CODE XREF: CalculateIQ+28j TEXT:800339E4 # DATA XREF: TEXT:levelJumpTableo TEXT:800339E4 lw $a0, g_levelScore # jumptable 80033990 case 3 TEXT:800339EC li $v1, 0xCCCCCCCD TEXT:800339F4 sll $v0, $a0, 3 TEXT:800339F8 addu $v0, $a0 TEXT:800339FC multu $v0, $v1 TEXT:80033A00 mfhi $a1 TEXT:80033A04 srl $v0, $a1, 4 TEXT:80033A08 j loc_80033A24 # jumptable 80033990 default case TEXT:80033A0C addu $v1, $a0, $v0 TEXT:80033A10 # --------------------------------------------------------------------------- TEXT:80033A10 TEXT:80033A10 level4: # CODE XREF: CalculateIQ+28j TEXT:80033A10 # DATA XREF: TEXT:levelJumpTableo TEXT:80033A10 lw $v1, g_levelScore # jumptable 80033990 case 4 TEXT:80033A18 nop TEXT:80033A1C srl $v0, $v1, 1 # Find half, below adds it on to the original TEXT:80033A1C # so 1.5x the score TEXT:80033A20 TEXT:80033A20 loc_80033A20: # CODE XREF: CalculateIQ+48j TEXT:80033A20 addu $v1, $v0 TEXT:80033A24 TEXT:80033A24 loc_80033A24: # CODE XREF: CalculateIQ+10j TEXT:80033A24 # CalculateIQ+38j ... TEXT:80033A24 lbu $a0, g_completedStages # jumptable 80033990 default case TEXT:80033A2C nop TEXT:80033A30 sltiu $v0, $a0, 9 TEXT:80033A34 beqz $v0, loc_80033BC8 # jumptable 80033A4C default case TEXT:80033A38 sll $v0, $a0, 2 TEXT:80033A3C lw $v0, stageJumpTable($v0) # v1 = modified score TEXT:80033A48 nop TEXT:80033A4C jr $v0 # switch 9 cases TEXT:80033A50 nop TEXT:80033A54 # --------------------------------------------------------------------------- TEXT:80033A54 TEXT:80033A54 stage1: # CODE XREF: CalculateIQ+E4j TEXT:80033A54 # DATA XREF: TEXT:stageJumpTableo TEXT:80033A54 li $v0, 0x51EB851F # jumptable 80033A4C case 0 TEXT:80033A5C multu $v1, $v0 TEXT:80033A60 mfhi $a1 TEXT:80033A64 srl $v1, $a1, 5 # mul & srl = divide score by 100 TEXT:80033A68 sll $v0, $v1, 2 # multiply divided score by 4 TEXT:80033A6C addu $v0, $v1 # add it back to the divided sore TEXT:80033A70 sll $v1, $v0, 4 # multiply that by 16 TEXT:80033A74 subu $v1, $v0 # subtract one lot (multiply by 15) TEXT:80033A78 j loc_80033B84 TEXT:80033A7C sll $v1, 3 # multiply the result by 8 TEXT:80033A80 # --------------------------------------------------------------------------- TEXT:80033A80 TEXT:80033A80 stage2: # CODE XREF: CalculateIQ+E4j TEXT:80033A80 # DATA XREF: TEXT:stageJumpTableo TEXT:80033A80 li $v0, 0x51EB851F # jumptable 80033A4C case 1 TEXT:80033A88 multu $v1, $v0 TEXT:80033A8C mfhi $a1 TEXT:80033A90 srl $v1, $a1, 5 TEXT:80033A94 sll $v0, $v1, 4 TEXT:80033A98 addu $v0, $v1 TEXT:80033A9C sll $v0, 2 TEXT:80033AA0 addu $v0, $v1 TEXT:80033AA4 sll $v0, 2 TEXT:80033AA8 subu $v0, $v1 TEXT:80033AAC li $v1, 0xD1B71759 TEXT:80033AB4 j loc_80033B8C TEXT:80033AB8 sll $v0, 1 TEXT:80033ABC # --------------------------------------------------------------------------- TEXT:80033ABC TEXT:80033ABC stage3: # CODE XREF: CalculateIQ+E4j TEXT:80033ABC # DATA XREF: TEXT:stageJumpTableo TEXT:80033ABC li $v0, 0x10624DD3 # jumptable 80033A4C case 2 TEXT:80033AC4 multu $v1, $v0 # from 1244800 result of this block = 622 TEXT:80033AC8 mfhi $a1 TEXT:80033ACC j loc_80033BC8 # jumptable 80033A4C default case TEXT:80033AD0 srl $v1, $a1, 7 TEXT:80033AD4 # --------------------------------------------------------------------------- TEXT:80033AD4 TEXT:80033AD4 stage4: # CODE XREF: CalculateIQ+E4j TEXT:80033AD4 # DATA XREF: TEXT:stageJumpTableo TEXT:80033AD4 li $v0, 0x51EB851F # jumptable 80033A4C case 3 TEXT:80033ADC multu $v1, $v0 TEXT:80033AE0 mfhi $a1 TEXT:80033AE4 srl $v1, $a1, 5 TEXT:80033AE8 sll $v0, $v1, 3 TEXT:80033AEC subu $v0, $v1 TEXT:80033AF0 sll $v0, 5 TEXT:80033AF4 addu $v0, $v1 TEXT:80033AF8 li $v1, 0xD1B71759 TEXT:80033B00 j loc_80033B8C TEXT:80033B04 sll $v0, 1 TEXT:80033B08 # --------------------------------------------------------------------------- TEXT:80033B08 TEXT:80033B08 stage5: # CODE XREF: CalculateIQ+E4j TEXT:80033B08 # DATA XREF: TEXT:stageJumpTableo TEXT:80033B08 li $v0, 0xD1B71759 # jumptable 80033A4C case 4 TEXT:80033B10 multu $v1, $v0 # from 1244800 result of this block = 497 TEXT:80033B14 mfhi $a1 TEXT:80033B18 j loc_80033BC8 # jumptable 80033A4C default case TEXT:80033B1C srl $v1, $a1, 0xB TEXT:80033B20 # --------------------------------------------------------------------------- TEXT:80033B20 TEXT:80033B20 stage6: # CODE XREF: CalculateIQ+E4j TEXT:80033B20 # DATA XREF: TEXT:stageJumpTableo TEXT:80033B20 li $v0, 0x51EB851F # jumptable 80033A4C case 5 TEXT:80033B28 multu $v1, $v0 TEXT:80033B2C mfhi $a1 TEXT:80033B30 srl $v1, $a1, 5 TEXT:80033B34 sll $v0, $v1, 1 TEXT:80033B38 addu $v0, $v1 TEXT:80033B3C sll $v0, 2 TEXT:80033B40 subu $v0, $v1 TEXT:80033B44 sll $v0, 4 TEXT:80033B48 subu $v0, $v1 TEXT:80033B4C li $v1, 0xD1B71759 TEXT:80033B54 j loc_80033B8C TEXT:80033B58 sll $v0, 1 TEXT:80033B5C # --------------------------------------------------------------------------- TEXT:80033B5C TEXT:80033B5C stage7: # CODE XREF: CalculateIQ+E4j TEXT:80033B5C # DATA XREF: TEXT:stageJumpTableo TEXT:80033B5C li $v0, 0x51EB851F # jumptable 80033A4C case 6 TEXT:80033B64 multu $v1, $v0 TEXT:80033B68 mfhi $a1 TEXT:80033B6C srl $v1, $a1, 5 TEXT:80033B70 sll $v0, $v1, 2 TEXT:80033B74 addu $v0, $v1 TEXT:80033B78 sll $v1, $v0, 4 TEXT:80033B7C subu $v1, $v0 TEXT:80033B80 sll $v1, 2 TEXT:80033B84 TEXT:80033B84 loc_80033B84: # CODE XREF: CalculateIQ+110j TEXT:80033B84 li $v0, 0xD1B71759 # this and the following divide the modified scores by 10000 TEXT:80033B8C TEXT:80033B8C loc_80033B8C: # CODE XREF: CalculateIQ+14Cj TEXT:80033B8C # CalculateIQ+198j ... TEXT:80033B8C multu $v1, $v0 TEXT:80033B90 mfhi $a1 TEXT:80033B94 j loc_80033BC8 # jumptable 80033A4C default case TEXT:80033B98 srl $v1, $a1, 0xD TEXT:80033B9C # --------------------------------------------------------------------------- TEXT:80033B9C TEXT:80033B9C stage8: # CODE XREF: CalculateIQ+E4j TEXT:80033B9C # DATA XREF: TEXT:stageJumpTableo TEXT:80033B9C li $v0, 0x10624DD3 # jumptable 80033A4C case 7 TEXT:80033BA4 multu $v1, $v0 TEXT:80033BA8 mfhi $a1 TEXT:80033BAC j loc_80033BC8 # jumptable 80033A4C default case TEXT:80033BB0 srl $v1, $a1, 8 # produces 311 for 1244800 TEXT:80033BB4 # --------------------------------------------------------------------------- TEXT:80033BB4 TEXT:80033BB4 finalStage: # CODE XREF: CalculateIQ+E4j TEXT:80033BB4 # DATA XREF: TEXT:stageJumpTableo TEXT:80033BB4 li $v0, 0xD1B71759 # jumptable 80033A4C case 8 TEXT:80033BBC multu $v1, $v0 TEXT:80033BC0 mfhi $a1 TEXT:80033BC4 srl $v1, $a1, 12 # modified score divided by 5000 TEXT:80033BC8 TEXT:80033BC8 loc_80033BC8: # CODE XREF: CalculateIQ+CCj TEXT:80033BC8 # CalculateIQ+164j ... TEXT:80033BC8 lw $v0, g_currentIQ # jumptable 80033A4C default case TEXT:80033BD0 sw $zero, g_levelScore TEXT:80033BD8 addu $v0, $v1 TEXT:80033BDC sw $v0, g_currentIQ TEXT:80033BE4 slti $v0, 0x3E8 TEXT:80033BE8 bnez $v0, locret_80033BF8 TEXT:80033BEC li $v0, 0x3E7 TEXT:80033BF0 sw $v0, g_currentIQ TEXT:80033BF8 TEXT:80033BF8 locret_80033BF8: # CODE XREF: CalculateIQ+280j TEXT:80033BF8 jr $ra TEXT:80033BFC nop TEXT:80033BFC # End of function CalculateIQ