diff --git a/ABOUT.md b/ABOUT.md deleted file mode 100644 index f92375eb7..000000000 --- a/ABOUT.md +++ /dev/null @@ -1,132 +0,0 @@ -## About gamemaking ## - -### CHALLENGING, NOT PUNISHING ### -https://www.youtube.com/watch?v=ea6UuRTjkKs - -1. CONSISTENT RULES - - No arbitrary unstoppable death - -2. Player's skill involved - - Can play around, not restart - -3. Usability of in-game tools - - Players should be able to 'regret' their strategy and adjust. - -4. Comfortable control - -5. Make players overcome the challenge, not defeating them - -6. Let players have "aha" moment when they failed. - - Make them hungry to retry with new strategies. - - Some small things they've could done differently - - e.g. "One-big-hit didn't worked, may I should've picked up high DPS one" - - -### MORE DEPTH, LESS COMPLEXITY ### -https://www.youtube.com/watch?v=jVL4st0blGU - -1. Memorise less! - - Less burden to, even starting the game - - Start with gentle learning curve, getting slowly steep - - Intuitive UX (UI, control, ...) - - Good tutorial = lessens complexity - -2. Intuitive! - -3. Calculations per second - - reduce! - -4. Players have to know everything to even begin the play ### FAIL (irreducible complexity) - - Make them get familiar with rules of the game - - Dwarf Fortress failed this! - - -### Lots of things players can play with (aka don't make them bored) ### - -- Combat, battle, building, mechanics, adventure, dungeon explore, spelunking -- Not scaled; easy combat, tough combat, tedious combat, etc. - - -### Achieving perfect imbalance ### -https://www.youtube.com/watch?v=e31OSVZF77w - -- Make sure no matter how you skilled, your playable character cannot be good at everything -- Give players __wide pool of options__ to solve problem - (kill the boss, defend their adobe, fast transportation, etc.) - - -**_What feeling do you want to convey?_** - - -### Always think WHY you want to add _something_ on the game ### - -- e.g. Why are you adding RPG leveling system? What it would do to the players? How would they play with? - - -See also: *HEARTS, CLUBS, DIAMONDS, SPADES: PLAYERS WHO SUIT MUDS* - - -## About this very game ## - -### Friendlier version of Dwarf Fortress Adventure mode ### - -- Yet _lots of fun_ -- Add Fortress mode features by 'make your own settling' -- Hard to actually die, but once you die, you're done. - + Config: imtooyoungtodie for easy mode - -- Genre: Adventure, Open world (towns in RPG, building, town managing (conquer existing one or - you build one and persuade existing people to move in) -> See Dwarf Fortress and Animal Crossing) - - * Adventure: adventure this vast—5,5 km wide—world, discover new (and good/horrible) things - - * Open world: - - Building: building your own houses, structures, etc. - - Town managing: - 1. Build your own little hamlet and manage it - or- - 2. Conquer existing one and become a ruler - The town is a special hamlet that can be tailored for your taste - - Survival: - mobs will trying to attack your assets (yourself, your hamlet, your people) - - - -### Side view ### - -### Interact menu w/ mouse right ### - -### Pixelated sprites ### -- Use 2x sprites if rotating does not work well - - -### User experience ### - -* Indicative mouse cursor - - -### Game mechanics ### - -* 24 pixels == 1 metre - - -### Purpose of the game ### - -* Boss - - Will be mentioned/shown as absolute _evil_. - - But actually is not. - -* Theme - - Is an evil really really is what we think? - - Is there a thing as 'absolute evil'? - -* Boss character - - From debugger character - - Name key: "Sigriðr hinn Dróttningin" (can be changed) - * Little setting - - A ruler, hated by people - -* Mechanics - - Beating boss does not ends the game, but grants an ability to - create new character as it. - \ No newline at end of file diff --git a/assets/modules/basegame/items/testpick.groovy b/assets/modules/basegame/items/testpick.groovy index 3b7dfd29f..f7c5a8d7a 100644 --- a/assets/modules/basegame/items/testpick.groovy +++ b/assets/modules/basegame/items/testpick.groovy @@ -33,8 +33,8 @@ class TestPick extends GameItem { boolean stackable = true int maxDurability = 147 float durability = maxDurability - int equipPosition = EquipPosition.HAND_GRIP - String inventoryCategory = Category.TOOL + int equipPosition = 9 //EquipPosition.HAND_GRIP + String inventoryCategory = "tool" //Category.TOOL // !! TEST MATERIAL !! Material material = new Material(0,0,0,0,0,0,0,0,1,0.0) diff --git a/assets/modules/dwarventech/virtualcomputer/arstars.kts b/assets/modules/dwarventech/virtualcomputer/arstars.kts new file mode 100644 index 000000000..8ffae8b04 --- /dev/null +++ b/assets/modules/dwarventech/virtualcomputer/arstars.kts @@ -0,0 +1,211 @@ +import java.io.InputStream +import java.io.OutputStream + +/** + * Just to make things slow down + * + * This version of Brainfuck fills memory with sanitised input program, and initialises + * memory pointer to be just right after your input program. This brings three major improvements: + * + * 1. Possibility of Self-modifying code + * 2. Fucks your brain even more + * 3. Forces you to enhance your calm + * + * Also note that program counter and memory pointer will wrap around when commands are executed, + * but not when program is being loaded (will throw OutOfMemoryException). + * + * If memory at Program Counter is equal to 0xFF, it is interpreted as termination. (0xFF is NOT a + * valid opcode for input program, however) + * + * Created by minjaesong on 17-04-29. + */ + +class BFVM( + val memSize: Int = 65536, + val stdout: OutputStream = System.out, + val stdin: InputStream = System.`in` +) { + private val ZERO = 0.toByte() + + private val INP = '>'.toByte() + private val DEP = '<'.toByte() + private val INC = '+'.toByte() + private val DEC = '-'.toByte() + private val PRN = '.'.toByte() + private val RDI = ','.toByte() + private val JPZ = '['.toByte() + private val JPN = ']'.toByte() + private val CYA = 0xFF.toByte() + + private val bfOpcodes = hashSetOf(43,44,45,46,60,62,91,93) + + private val instSet = hashMapOf Unit>( + Pair(INP, { INP() }), + Pair(DEP, { DEP() }), + Pair(INC, { INC() }), + Pair(DEC, { DEC() }), + Pair(PRN, { PRN() }), + Pair(RDI, { RDI() }), + Pair(JPZ, { JPZ() }), + Pair(JPN, { JPN() }) + ) + + + private var r1: Byte = ZERO // Register One (Data register) + private var r2 = 0 // Register Two (Scratchpad); theoretically I can use R1 but it limits bracket depth to 254 + private var mp = 0 // Memory Pointer + private var pc = 0 // Program Counter + private var ir = 0 // Instruction Register; does lookahead ahd lookbehind + + private val mem = ByteArray(memSize) + + + /* + Input program is loaded into the memory from index zero. + + Interrupts are hard-coded, 'cause why not? + + Code Mnemo. Desc. + ----|------|----- + INP > Increment pointer + DEP < Decrement pointer + INC + Increment memory + DEC - Decrement memory + PRN . Print as text + RDI , Read from input + JPZ [ Jump past to matching ] when mem is zero + JPN ] Jump back to matching [ when mem is non-zero + + [ Internal operations ] + CYA 0xFF Marks end of the input program + */ + + // NOTE: INC_PC is implied + private fun INP() { + INC_MP() + } + private fun DEP() { + DEC_MP() + } + private fun INC() { + r1 = mem[mp] + r1++ + mem[mp] = r1 + } + private fun DEC() { + r1 = mem[mp] + r1-- + mem[mp] = r1 + } + private fun PRN() { + stdout.write(mem[mp].toInt()) + } + private fun RDI() { + r1 = stdin.read().toByte() + mem[mp] = r1 + } + private fun JPZ() { + if (mem[mp] == ZERO) { + // lookahead + ir = pc + r2 = 0 + + while (r2 != -1) { + INC_IR() + if (JPZ == mem[ir]) { + r2++ + } + else if (JPN == mem[ir]) { + r2-- + } + } + + pc = ir + } + } + private fun JPN() { + if (mem[mp] != ZERO) { + // lookbehind + ir = pc + r2 = 0 + + while (r2 != -1) { + DEC_IR() + if (JPN == mem[ir]) { + r2++ + } + else if (JPZ == mem[ir]) { + r2-- + } + } + + pc = ir + } + } + // END OF NOTE (INC_PC is implied) + + + fun execute() { + while (mem[pc] != CYA) { + //println("pc = $pc, mp = $mp, inst = ${mem[pc].toChar()}, mem = ${mem[mp]}") + instSet[mem[pc]]?.invoke() // fetch-decode-execute in one line + INC_PC() + } + } + + fun loadProgram(program: String) { + val program = program.toByteArray(charset = Charsets.US_ASCII) + + pc = 0 // FOR NOW it's PC for input program + mp = 0 // where to dump input bytes + + while (pc < program.size) { + if (pc >= memSize - 1) { + throw OutOfMemoryError("Virtual Machine Out of Memory") + } + + r1 = program[pc] + + if (r1 in bfOpcodes) { + mem[mp] = r1 + INC_MP() + } + + INC_PC() + } + + + mem[program.size] = CYA + mp = (program.size + 1) mod memSize + pc = 0 + ir = 0 + } + + + private fun INC_PC() { pc = (pc + 1) mod memSize } + private fun INC_IR() { ir = (ir + 1) mod memSize } + private fun DEC_IR() { ir = (ir - 1) mod memSize } + private fun INC_MP() { mp = (mp + 1) mod memSize } + private fun DEC_MP() { mp = (mp - 1) mod memSize } + private infix fun Int.mod(other: Int) = Math.floorMod(this, other) +} + + +val vm = BFVM() + +val factorials = """ ++++++++++++ +>+>>>>++++++++++++++++++++++++++++++++++++++++++++ +>++++++++++++++++++++++++++++++++<<<<<<[>[>>>>>>+> ++<<<<<<<-]>>>>>>>[<<<<<<<+>>>>>>>-]<[>++++++++++[- +<-[>>+>+<<<-]>>>[<<<+>>>-]+<[>[-]<[-]]>[<<[>>>+<<< +-]>>[-]]<<]>>>[>>+>+<<<-]>>>[<<<+>>>-]+<[>[-]<[-]] +>[<<+>>[-]]<<<<<<<]>>>>>[+++++++++++++++++++++++++ ++++++++++++++++++++++++.[-]]++++++++++<[->-<]>++++ +++++++++++++++++++++++++++++++++++++++++++++.[-]<< +<<<<<<<<<<[>>>+>+<<<<-]>>>>[<<<<+>>>>-]<-[>>.>.<<< +[-]]<<[>>+>+<<<-]>>>[<<<+>>>-]<<[<+>-]>[<+>-]<<<-] +""" + +vm.loadProgram(factorials) +vm.execute() diff --git a/assets/modules/dwarventech/virtualcomputer/bfvt.kts b/assets/modules/dwarventech/virtualcomputer/bfvt.kts index c36eaa079..ca82d7311 100644 --- a/assets/modules/dwarventech/virtualcomputer/bfvt.kts +++ b/assets/modules/dwarventech/virtualcomputer/bfvt.kts @@ -25,6 +25,11 @@ class BFVM( val stdout: OutputStream = System.out, val stdin: InputStream = System.`in` ) { + annotation class Unsigned + + private val DEBUG = true + + private val ZERO = 0.toByte() private val INP = '>'.toByte() @@ -35,8 +40,16 @@ class BFVM( private val RDI = ','.toByte() private val JPZ = '['.toByte() private val JPN = ']'.toByte() + private val CYA = 0xFF.toByte() + private val LDZ = '0'.toByte() + private val ADM = 'M'.toByte() + private val ADP = 'P'.toByte() + private val SBM = 'm'.toByte() + private val SBP = 'p'.toByte() + + private val bfOpcodes = hashSetOf(43,44,45,46,60,62,91,93) private val instSet = hashMapOf Unit>( @@ -47,9 +60,16 @@ class BFVM( Pair(PRN, { PRN() }), Pair(RDI, { RDI() }), Pair(JPZ, { JPZ() }), - Pair(JPN, { JPN() }) + Pair(JPN, { JPN() }), + Pair(LDZ, { LDZ() }) ) + private val instOneArg = hashMapOf<@Unsigned Byte, (Int) -> Unit>( + Pair(ADM, { i -> ADM(i) }), + Pair(ADP, { i -> ADP(i) }), + Pair(SBM, { i -> SBM(i) }), + Pair(SBP, { i -> SBP(i) }) + ) private var r1: Byte = ZERO // Register One (Data register) private var r2 = 0 // Register Two (Scratchpad); theoretically I can use R1 but it limits bracket depth to 254 @@ -78,6 +98,14 @@ class BFVM( [ Internal operations ] CYA 0xFF Marks end of the input program + + [ Optimise operations ] + LDZ 0 Set memory to zero + ADM M Add immediate to memory (RLEing +) + ADP P Add immediate to memory pointer (RLEing >) + SBM m Subtract immediate to memory (RLEing -) + SBP p Subtract immediate to memory pointer (RLEing <) + */ // NOTE: INC_PC is implied @@ -98,7 +126,7 @@ class BFVM( mem[mp] = r1 } private fun PRN() { - stdout.write(mem[mp].toInt()) + stdout.write(mem[mp].toUint()) } private fun RDI() { r1 = stdin.read().toByte() @@ -111,7 +139,7 @@ class BFVM( r2 = 0 while (r2 != -1) { - INC_IR() + ir++ if (JPZ == mem[ir]) { r2++ } @@ -130,7 +158,7 @@ class BFVM( r2 = 0 while (r2 != -1) { - DEC_IR() + ir-- if (JPN == mem[ir]) { r2++ } @@ -142,22 +170,67 @@ class BFVM( pc = ir } } + // non-standard + private fun LDZ() { + mem[mp] = 0 + } + private fun ADM(i: Int) { + mem[mp] = (mem[mp] + i).toByte() + } + private fun SBM(i: Int) { + mem[mp] = (mem[mp] - i).toByte() + } + private fun ADP(i: Int) { + mp = (mp + i) mod memSize + } + private fun SBP(i: Int) { + mp = (mp - i) mod memSize + } // END OF NOTE (INC_PC is implied) fun execute() { + dbgp("Now run...") while (mem[pc] != CYA) { - //println("pc = $pc, mp = $mp, inst = ${mem[pc].toChar()}, mem = ${mem[mp]}") - instSet[mem[pc]]?.invoke() // fetch-decode-execute in one line + //dbgp("pc = $pc, mp = $mp, inst = ${mem[pc].toChar()} ${mem[pc+1]}, mem = ${mem[mp]}") + + r1 = mem[pc] + + if (r1 in instSet) { + instSet[r1]!!.invoke() // fetch-decode-execute in one line + } + else if (r1 in instOneArg) { + INC_PC() + r2 = mem[pc].toUint() + + instOneArg[r1]?.invoke(r2) + } + else { + dbgp("invalid: $r1") + } + INC_PC() } } - fun loadProgram(program: String) { + fun loadProgram(program: String, optimizeLevel: Int = NO_OPTIMISE) { + dbgp("Now load...") + + fun putOp(op: Byte) { + + //dbgp("${op.toChar()} ${op.toUint()}") + + mem[mp] = op + INC_MP() + } + val program = program.toByteArray(charset = Charsets.US_ASCII) + r1 = 0 // currently reading operation pc = 0 // FOR NOW it's PC for input program mp = 0 // where to dump input bytes + r2 = 0 // scratchpad + ir = 0 // lookahead pointer while (pc < program.size) { if (pc >= memSize - 1) { @@ -167,62 +240,109 @@ class BFVM( r1 = program[pc] if (r1 in bfOpcodes) { - mem[mp] = r1 - INC_MP() + if (optimizeLevel >= 1) { + // [-] + if (r1 == JPZ) { + if (program[pc + 1] == DEC && program[pc + 2] == JPN) { + pc += 3 + putOp(LDZ) + continue + } + } + // RLEing + + else if (INC == r1 && program[pc + 1] == r1) { + ir = 2 + while (INC == program[pc + ir] && ir < 255) ir++ + + pc += ir + putOp(ADM) + putOp(ir.toByte()) + continue + } + // RLEing - + else if (DEC == r1 && program[pc + 1] == r1) { + ir = 2 + while (DEC == program[pc + ir] && ir < 255) ir++ + + pc += ir + putOp(SBM) + putOp(ir.toByte()) + continue + } + // RLEing > + else if (INP == r1 && program[pc + 1] == r1) { + ir = 2 + while (INP == program[pc + ir] && ir < 255) ir++ + + pc += ir + putOp(ADP) + putOp(ir.toByte()) + continue + } + // RLEing < + else if (DEP == r1 && program[pc + 1] == r1) { + ir = 2 + while (DEP == program[pc + ir] && ir < 255) ir++ + + pc += ir + putOp(SBP) + putOp(ir.toByte()) + continue + } + } + + putOp(r1) } - INC_PC() + pc += 1 } - mem[program.size] = CYA - mp = (program.size + 1) mod memSize + mem[mp] = CYA + + dbgp("Prg size: $mp") + + INC_MP() pc = 0 ir = 0 } private fun INC_PC() { pc = (pc + 1) mod memSize } - private fun INC_IR() { ir = (ir + 1) mod memSize } - private fun DEC_IR() { ir = (ir - 1) mod memSize } private fun INC_MP() { mp = (mp + 1) mod memSize } private fun DEC_MP() { mp = (mp - 1) mod memSize } private infix fun Int.mod(other: Int) = Math.floorMod(this, other) + private fun Byte.toUint() = java.lang.Byte.toUnsignedInt(this) + + private fun dbgp(s: Any) { + if (DEBUG) println(s) + } + + val NO_OPTIMISE = 0 + + /* + Optimise level + 1 RLE, Set cell to zero + */ } val vm = BFVM() val factorials = """ -github (dot) com/saulpw/brainfuck/blob/master/tests/facto (dot) b - -++++++++++>>>+>>>>+>+<[[+++++[>++++ -++++<-]>.<++++++[>--------<-]+<<]<< -[<<]<.>>>>+<[->[<+>-[<+>-[<+>-[<+>- -[<+>-[<+>-[<+>-[<+>-[<+>-[<[-]>-+>[ -<->-]<[->>>[>>]<<[->[>>+<<-]>+<<<<] -<]>[-]+>+<<]]]]]]]]]]<[>+<-]+>>]<<[ -<<]>>[->>[>>]>>[-<<[<<]<<[<<]>[>[>> -]>>[>>]>>[>>]>+>+<<<<[<<]<<[<<]<<[< -<]>-]>[>>]>>[>>]>>[>>]>[<<<[<<]<<[< -<]<<[<<]>+>[>>]>>[>>]>>[>>]>-]<<<[< -<]>[>[>>]>+>>+<<<<<[<<]>-]>[>>]>[<< -<[<<]>+>[>>]>-]>>[<[<<+>+>-]<[>>>+< -<<-]<[>>+<<-]>>>-]<[-]>>+[>[>>>>]>[ ->>>>]>[-]+>+<[<<<<]>-]>[>>>>]>[>>>> -]>->-[<<<+>>+>-]<[>+<-]>[[<<+>+>-]< -[<->-[<->-[<->-[<->-[<->-[<->-[<->- -[<->-[<->-[<-<---------->>[-]>>>>[- -]+>+<<<<<]]]]]]]]]]<[>>+<<-]>>]<+<+ -<[>>>+<<<-]<<[<<<<]<<<<<[<<]+>>]>>> ->>[>>>>]+>[>>>>]<<<<[-<<<<]>>>>>[<< -<<]<<<<<[<<]<<[<<]+>>]>>[>>]>>>>>[- ->>>>]<<[<<<<]>>>>[>>>>]<<<<<<<<[>>> ->[<<+>>->[<<+>>-]>]<<<<[<<]<<]<<<<< -[->[-]>>>>>>>>[<<+>>->[<<+>>-]>]<<< -<[<<]<<<<<<<]>>>>>>>>>[<<<<<<<+>>>> ->>>->[<<<<<<<+>>>>>>>-]>]<<<<<<<<<] ++++++++++++ +>+>>>>++++++++++++++++++++++++++++++++++++++++++++ +>++++++++++++++++++++++++++++++++<<<<<<[>[>>>>>>+> ++<<<<<<<-]>>>>>>>[<<<<<<<+>>>>>>>-]<[>++++++++++[- +<-[>>+>+<<<-]>>>[<<<+>>>-]+<[>[-]<[-]]>[<<[>>>+<<< +-]>>[-]]<<]>>>[>>+>+<<<-]>>>[<<<+>>>-]+<[>[-]<[-]] +>[<<+>>[-]]<<<<<<<]>>>>>[+++++++++++++++++++++++++ ++++++++++++++++++++++++.[-]]++++++++++<[->-<]>++++ +++++++++++++++++++++++++++++++++++++++++++++.[-]<< +<<<<<<<<<<[>>>+>+<<<<-]>>>>[<<<<+>>>>-]<-[>>.>.<<< +[-]]<<[>>+>+<<<-]>>>[<<<+>>>-]<<[<+>-]>[<+>-]<<<-] """ -vm.loadProgram(factorials) +vm.loadProgram(factorials, optimizeLevel = 1) vm.execute() + diff --git a/src/net/torvald/terrarum/StateInGame.kt b/src/net/torvald/terrarum/StateInGame.kt index b1ef41409..1c330ca24 100644 --- a/src/net/torvald/terrarum/StateInGame.kt +++ b/src/net/torvald/terrarum/StateInGame.kt @@ -834,9 +834,9 @@ class StateInGame : BasicGameState() { } } - private fun ArrayList.binarySearch(actor: Actor) = this.binarySearch(actor.referenceID) + private fun ArrayList<*>.binarySearch(actor: Actor) = this.binarySearch(actor.referenceID) - private fun ArrayList.binarySearch(ID: Int): Int { + private fun ArrayList<*>.binarySearch(ID: Int): Int { // code from collections/Collections.kt var low = 0 var high = this.size - 1 @@ -844,11 +844,11 @@ class StateInGame : BasicGameState() { while (low <= high) { val mid = (low + high).ushr(1) // safe from overflows - val midVal = get(mid) + val midVal = get(mid)!! - if (ID > midVal.referenceID) + if (ID > midVal.hashCode()) low = mid + 1 - else if (ID < midVal.referenceID) + else if (ID < midVal.hashCode()) high = mid - 1 else return mid // key found diff --git a/work_files/GameDesign/ABOUT.md b/work_files/GameDesign/ABOUT.md new file mode 100644 index 000000000..486793235 --- /dev/null +++ b/work_files/GameDesign/ABOUT.md @@ -0,0 +1,61 @@ + +## Friendlier version of Dwarf Fortress Adventure mode + +- Yet lots of !!fun!! (wat that double exclamation mark!) +- You can build your own hamlet (Dwarf "Fortress" reference) +- You die, you lose "you"; the world will be there, some of your possession will be on the ground, but you got to start over by making new character from scratch + - Unless you have built Resurrection Shrine, which you won't going to easily build it + +- Genre: Adventure, Open world (towns in RPG, building, town managing (conquer existing one or + you build one and persuade existing people to move in) -> See Dwarf Fortress and Animal Crossing) + + * Adventure: adventure this vast—5,5 km wide—world, discover new (and good/horrible) things + + * Open world: + - Building: building your own houses, structures, etc. + - Town managing: + 1. Build your own little hamlet and manage it + or- + 2. Conquer existing one and become a ruler + The town is a special hamlet that can be tailored for your taste + - Survival: + mobs will trying to attack your assets (yourself, your hamlet, your people) + + + +## Side view + +## Interact menu w/ mouse right + + +## User experience + +* Indicative mouse cursor + + +## Game mechanics + +* 24 pixels == 1 metre + + +## Purpose of the game + +* Boss + - Will be mentioned/shown as absolute _evil_. + - But actually is not. + +* Theme + - Is an evil really really is what we think? + - Is there a thing as 'absolute evil'? + +* Boss character + - From debugger character + - Name key: "Sigriðr hinn Dróttningin" (can be changed) + * Little setting + - A ruler, hated by people + +* Mechanics + - Beating boss does not ends the game, but grants an ability to + create new character as it. + +...We need to re-think about it. —Torvald, 2017-04-30 \ No newline at end of file diff --git a/work_files/GameDesign/GAME_DESIGN_PRINCIPLE.md b/work_files/GameDesign/GAME_DESIGN_PRINCIPLE.md new file mode 100644 index 000000000..1f3315270 --- /dev/null +++ b/work_files/GameDesign/GAME_DESIGN_PRINCIPLE.md @@ -0,0 +1,66 @@ +(Warning — this article refers Extra Credits bit too much...) + +## CHALLENGING, NOT PUNISHING +https://www.youtube.com/watch?v=ea6UuRTjkKs + +1. CONSISTENT RULES + - No arbitrary unstoppable death + +2. Player's skill involved + - Can play around, not restart + +3. Usability of in-game tools + - Players should be able to 'regret' their strategy and adjust. + +4. Comfortable control + +5. Make players overcome the challenge, not defeating them + +6. Let players have "aha" moment when they failed. + - Make them hungry to retry with new strategies. + - Some small things they've could done differently + - e.g. "One-big-hit didn't worked, may I should've picked up high DPS one" + + +## MORE DEPTH, LESS COMPLEXITY +https://www.youtube.com/watch?v=jVL4st0blGU + +1. Memorise less! + - Less burden to, even starting the game + - Start with gentle learning curve, getting slowly steep + - Intuitive UX (UI, control, ...) + - Good tutorial = lessens complexity + +2. Intuitive! + +3. Calculations per second + - reduce! + +4. Players have to know everything to even begin the play ### FAIL (irreducible complexity) + - Make them get familiar with rules of the game + - Dwarf Fortress failed this! + + +## Lots of things players can play with (aka don't make them bored) + +- Combat, battle, building, mechanics, adventure, dungeon explore, spelunking +- Not scaled; easy combat, tough combat, tedious combat, etc. + + +## Achieving perfect imbalance +https://www.youtube.com/watch?v=e31OSVZF77w + +- Make sure no matter how you skilled, your playable character cannot be good at everything +- Give players __wide pool of options__ to solve problem + (kill the boss, defend their adobe, fast transportation, etc.) + + +**_What feeling do you want to convey?_** + + +## Always think WHY you want to add _something_ on the game + +- e.g. Why are you adding RPG leveling system? What it would do to the players? How would they play with? + + +See also: *HEARTS, CLUBS, DIAMONDS, SPADES: PLAYERS WHO SUIT MUDS* \ No newline at end of file